From 1c67d44c2ab52f45fd0e19621e93b0b238c07c25 Mon Sep 17 00:00:00 2001 From: straccio Date: Wed, 23 Sep 2015 16:46:14 +0200 Subject: [PATCH 001/504] Corrected event subscriptions Corrections for event subscriptions. This change need this commit in spark-protocol https://github.com/straccio/spark-protocol/commit/0cc9a5a266834c56805861 f62e99015e3ddc83bb --- js/lib/CoreController.js | 9 ++------- js/views/EventViews001.js | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/js/lib/CoreController.js b/js/lib/CoreController.js index 807f2f64..c7b4d726 100644 --- a/js/lib/CoreController.js +++ b/js/lib/CoreController.js @@ -131,18 +131,13 @@ CoreController.prototype = { core.on(that.socketID, handler); }, - subscribe: function (isPublic, name, userid) { - if (userid && (userid != "")) { - name = userid + "/" + name; - } - - + subscribe: function (isPublic, name, userid,coreid) { // if (!sock) { // return false; // } //start permitting these messages through on this socket. - global.publisher.subscribe(name, this); + global.publisher.subscribe(name,userid,coreid, this); return false; }, diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index 27f1f942..6e6f9b59 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -235,8 +235,8 @@ var EventsApi = { //----------------------------------- //get core events //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); + socket.subscribe(true, name, userid,coreid); + socket.subscribe(false, name, userid,coreid); //----------------------------------- //filter to core id From 52b3a49d18c622d06fe70e0bc1adace775004a86 Mon Sep 17 00:00:00 2001 From: straccio Date: Wed, 14 Oct 2015 12:33:28 +0200 Subject: [PATCH 002/504] Double subscribe problem Double subscribe problem --- js/views/EventViews001.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index 6e6f9b59..03511b4f 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -194,7 +194,7 @@ var EventsApi = { //----------------------------------- //get firehose and my private events. //socket.subscribe(true, name); - socket.subscribe(true, name); + //socket.subscribe(true, name); socket.subscribe(false, name, userid); From ce165b1085dc1042b1abb5c0fbc0476613954853 Mon Sep 17 00:00:00 2001 From: straccio Date: Mon, 25 Jan 2016 11:20:09 +0100 Subject: [PATCH 003/504] ignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index da23d0d4..4d998bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ build/Release # Deployed apps should consider commenting this line out: # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git node_modules + +js/build/config.gypi + +js/package.json.ori From 3821c319141c07827ac2854a80aeb86de9f9a563 Mon Sep 17 00:00:00 2001 From: straccio Date: Mon, 25 Jan 2016 11:24:05 +0100 Subject: [PATCH 004/504] Credentials auth corrected. --- js/lib/AccessTokenViews.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/lib/AccessTokenViews.js b/js/lib/AccessTokenViews.js index 991e84e8..9f280332 100644 --- a/js/lib/AccessTokenViews.js +++ b/js/lib/AccessTokenViews.js @@ -34,7 +34,9 @@ AccessTokenViews.prototype = { app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); }, index: function (req, res) { - var credentials = AccessTokenViews.basicAuth(req); + //var credentials = AccessTokenViews.basicAuth(req); + //var credentials = this.basicAuth(req); + var credentials= AccessTokenViews.prototype.basicAuth(req); if (!credentials) { return res.json(401, { ok: false, @@ -56,7 +58,8 @@ AccessTokenViews.prototype = { }, destroy: function (req, res) { - var credentials = AccessTokenViews.basicAuth(req); + //var credentials = AccessTokenViews.basicAuth(req); + var credentials= AccessTokenViews.prototype.basicAuth(req); if (!credentials) { return res.json(401, { ok: false, From fa3b2a8bf5c4cc4288ae40d245d7fd977227c24a Mon Sep 17 00:00:00 2001 From: Flavio Ferrandi Date: Fri, 5 Aug 2016 18:21:38 +0200 Subject: [PATCH 005/504] update README - instruction for install and config --- README.md | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ffbb3cb7..8e8ff1bc 100644 --- a/README.md +++ b/README.md @@ -40,52 +40,50 @@ node main.js Your server IP address is: 192.168.1.10 ``` - -3.) Load your server public key and IP address onto your cores with the [Spark-CLI](https://github.com/spark/spark-cli) - -First, put your Core in DFU mode by holding the MODE and RESET buttons on the Core, then releasing RESET while continuing to hold MODE for 3 seconds until the LED starts blinking yellow. +3.) We will now create a new server profile on Particle-CLI using the command: ``` -spark keys server default_key.pub.pem 192.168.1.10 +particle config profile_name apiUrl "http://DOMAIN_OR_IP" ``` -Note! The CLI will turn your PEM file into a DER file, but you can also do that yourself with the command: -``` - openssl rsa -in default_key.pem -pubin -pubout -outform DER -out default_key.der -``` +For the local cloud, the port number 8080 needs to be added behind: http://domain_or_ip:8080 + +This will create a new profile to point to your server and switching back to the spark cloud is simply particle config particle and other profiles would be particle config profile_name + +4.) We will now point over to the local cloud using particle config profile_name -4.) Edit your Spark-CLI config file to point at your Spark-server. Open ~/.spark/spark.config.json in your favorite text editor, and add: +5.) On a separate CMD from the one running the server, type ``` -{ - "apiUrl": "http://192.168.1.10:8080" -} +particle setup ``` -For beginners: note that you have to add in a `,` at the end of the previous line +This will create an account on the local cloud -5.) Put your core into listening mode, and run `spark identify` to get your core id. +Perform CTRL + C once you logon with Particle-CLI asking you to send Wifi-credentials etc... -6.) Create a user and login with the Spark-CLI +6.) On Command-line, cd to particle-server and place your core in DFU mode [flashing yellow] + +7.) Create and provision access on your local cloud with the keys doctor: ``` - spark setup + particle keys doctor your_core_id ``` -7.) Create and provision access on your local cloud with the keys doctor: +8.) Change server keys to local cloud key + IP Address ``` - spark keys doctor your_core_id +particle keys server default_key.pub.pem IP_ADDRESS ``` - -7.) See your connected cores! +9.) Go to cores_key directory to place core public key inside ``` - spark list +cd core_keys +place core in DFU-mode +particle keys save INPUT_DEVICE_ID_HERE ``` - What kind of project is this? ====================================== From e71de5ff914ba5e4669a395bd382737eb30898bf Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sat, 6 Aug 2016 15:52:36 +0200 Subject: [PATCH 006/504] updated to express 4 --- .gitignore | 2 ++ js/lib/AccessTokenViews.js | 10 +++--- js/main.js | 10 ++++-- js/package.json | 66 ++++++++++++++++++++------------------ js/views/api_v1.js | 32 +++++++++--------- 5 files changed, 64 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index 4d998bfa..55c6ac81 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ node_modules js/build/config.gypi js/package.json.ori +spark-server.esproj +.DS_Store diff --git a/js/lib/AccessTokenViews.js b/js/lib/AccessTokenViews.js index 9f280332..ce2ccd5a 100644 --- a/js/lib/AccessTokenViews.js +++ b/js/lib/AccessTokenViews.js @@ -38,7 +38,7 @@ AccessTokenViews.prototype = { //var credentials = this.basicAuth(req); var credentials= AccessTokenViews.prototype.basicAuth(req); if (!credentials) { - return res.json(401, { + return res.status(401).json({ ok: false, errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] }); @@ -53,7 +53,7 @@ AccessTokenViews.prototype = { res.json(userObj.access_tokens); }, function () { - res.json(401, { ok: false, errors: ['Bad password']}); + res.status(401).json({ ok: false, errors: ['Bad password']}); }); }, @@ -61,7 +61,7 @@ AccessTokenViews.prototype = { //var credentials = AccessTokenViews.basicAuth(req); var credentials= AccessTokenViews.prototype.basicAuth(req); if (!credentials) { - return res.json(401, { + return res.status(401).json({ ok: false, errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] }); @@ -76,11 +76,11 @@ AccessTokenViews.prototype = { } catch (ex) { logger.error("error saving user " + ex); - res.json(401, { ok: false, errors: ['Error updating token']}); + res.status(401).json({ ok: false, errors: ['Error updating token']}); } }, function () { - res.json(401, { ok: false, errors: ['Bad password']}); + res.status(401).json({ ok: false, errors: ['Bad password']}); }); }, diff --git a/js/main.js b/js/main.js index 27bf9651..0be5d392 100644 --- a/js/main.js +++ b/js/main.js @@ -19,7 +19,8 @@ var fs = require('fs'); var http = require('http'); var express = require('express'); - +var bodyParser = require('body-parser'); +var morgan = require('morgan'); var settings = require('./settings.js'); var utilities = require("./lib/utilities.js"); @@ -68,9 +69,12 @@ process.on('uncaughtException', function (ex) { var app = express(); -app.use(express.logger()); -app.use(express.bodyParser()); +app.set('json spaces', 4); +app.use(morgan('combined')); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); app.use(set_cors_headers); + app.use(oauth.handler()); app.use(oauth.errorHandler()); diff --git a/js/package.json b/js/package.json index 45683544..96767b30 100644 --- a/js/package.json +++ b/js/package.json @@ -1,34 +1,36 @@ { - "name": "spark-server", - "version": "0.1.1", - "license": "AGPL-3.0", - "repository": { - "type": "git", - "url": "https://github.com/spark/spark-server" - }, - "homepage": "https://github.com/spark/spark-server", - "bugs": "https://github.com/spark/spark-server/issues", - "author": { - "name": "David Middlecamp", - "email": "david@spark.io", - "url": "https://www.spark.io/" - }, - "dependencies": { - "spark-protocol": "*", - "express": "~3.4.4", - "hogan-express": "~0.5.1", - "request": "*", - "node-oauth2-server": "~1.5.3", - "ursa": "*", - "when": "*", - "moment": "*", - "xtend": "*" - }, - "scripts": { - "start": "node main.js" - }, - "main": "main.js", - "contributors": [ - "Kenneth Lim " - ] + "name": "spark-server", + "version": "0.1.1", + "license": "AGPL-3.0", + "repository": { + "type": "git", + "url": "https://github.com/spark/spark-server" + }, + "homepage": "https://github.com/spark/spark-server", + "bugs": "https://github.com/spark/spark-server/issues", + "author": { + "name": "David Middlecamp", + "email": "david@spark.io", + "url": "https://www.spark.io/" + }, + "dependencies": { + "body-parser": "^1.15.2", + "express": "^4.14.0", + "hogan-express": "^0.5.2", + "moment": "*", + "morgan": "^1.7.0", + "node-oauth2-server": "^1.5.3", + "request": "*", + "spark-protocol": "*", + "ursa": "*", + "when": "*", + "xtend": "*" + }, + "scripts": { + "start": "node main.js" + }, + "main": "main.js", + "contributors": [ + "Kenneth Lim " + ] } diff --git a/js/views/api_v1.js b/js/views/api_v1.js index aefc1d84..5a09dddc 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -122,7 +122,7 @@ var Api = { devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; } - res.json(200, devices); + res.status(200).json(devices); }); }, @@ -150,7 +150,7 @@ var Api = { if (!results || (results.length != 2)) { logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); - res.json(404, "Oops, I couldn't find that core"); + res.status(404).json("Oops, I couldn't find that core"); return; } @@ -162,7 +162,7 @@ var Api = { if (!doc || !doc.coreID) { logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); - res.json(404, "Oops, I couldn't find that core"); + res.status(404).json("Oops, I couldn't find that core"); return; } @@ -191,7 +191,7 @@ var Api = { } catch (ex) { logger.error("get_core_attributes merge error: " + ex); - res.json(500, { Error: "get_core_attributes error: " + ex }); + res.status(500).json({ Error: "get_core_attributes error: " + ex }); } }, null); @@ -327,7 +327,7 @@ var Api = { loadCore: function (req, res, next) { - req.coreID = req.param('coreid') || req.body.id; + req.coreID = req.params.coreid || req.body.id; //load core info! req.coreInfo = { @@ -381,8 +381,8 @@ var Api = { var userid = Api.getUserID(req); var socketID = Api.getSocketID(userid), coreID = req.coreID, - varName = req.param('var'), - format = req.param('format'); + varName = req.params.var, + format = req.params.format; logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); @@ -403,7 +403,7 @@ var Api = { if (msg.error) { //at this point, either we didn't get a describe return, or that variable //didn't exist, either way, 404 - return res.json(404, { + return res.status(404).json({ ok: false, error: msg.error }); @@ -414,14 +414,14 @@ var Api = { msg.coreInfo.connected = true; if (format && (format == "raw")) { - return res.send("" + msg.result); + return res.sendStatus("" + msg.result); } else { return res.json(msg); } }, function () { - res.json(408, {error: "Timed out."}); + res.status(408).json({error: "Timed out."}); } ).ensure(function () { socket.close(); @@ -459,20 +459,20 @@ var Api = { try { //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { - res.json(404, { + res.status(404).json({ ok: false, error: "Function not found" }); } else if (msg.error != null) { - res.json(400, { + res.status(400).json({ ok: false, error: msg.error }); } else { if (format && (format == "raw")) { - res.send("" + msg.result); + res.sendStatus("" + msg.result); } else { res.json({ @@ -487,14 +487,14 @@ var Api = { } catch (ex) { logger.error("FunCall handling resp error " + ex); - res.json(500, { + res.status(500).json({ ok: false, error: "Error while api was rendering response" }); } }, function () { - res.json(408, {error: "Timed out."}); + res.status(408).json({error: "Timed out."}); } ).ensure(function () { socket.close(); @@ -647,7 +647,7 @@ var Api = { }, function (err) { //different status code here? - res.json(400, err); + res.status(400).json(err); }); }, From 94626522eb717d346068e1872ae0faa038fca612 Mon Sep 17 00:00:00 2001 From: Flavio Ferrandi Date: Sat, 6 Aug 2016 17:46:15 +0200 Subject: [PATCH 007/504] added temporary token response on claim --- js/views/api_v1.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index aefc1d84..2adf4bf1 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -62,7 +62,8 @@ var Api = { app.post('/v1/provisioning/:coreid', Api.provision_core); //app.delete('/v1/devices/:coreid', Api.release_device); - app.post('/v1/devices', Api.claim_device); + //app.post('/v1/devices', Api.claim_device); + app.post('/v1/device_claims', Api.claim_device); }, @@ -322,7 +323,9 @@ var Api = { claim_device: function (req, res) { - res.json({ ok: true }); + logger.log("claim device"); + //res.json({ ok: true }); + res.json({ claim_code: 'rCFr2KAJNgzJ2rR3Jm1ZoUd7G4Sr3ak7MRHdWrM274eYzInP1+psZ0fP2qNehlj' }); }, From dfe7984a5f707c30d9de1e0a2ad7810fefc0e930 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 10:25:39 +0200 Subject: [PATCH 008/504] fix express 4 EventViews --- js/views/EventViews001.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index 03511b4f..e553f0e3 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -182,7 +182,7 @@ var EventsApi = { get_events: function (req, res) { - var name = req.param('event_name'); + var name = req.params.event_name; name = name || ""; var socket = new CoreController(); @@ -202,7 +202,7 @@ var EventsApi = { EventsApi.pipeEvents(socket, req, res); }, get_my_events: function (req, res) { - var name = req.param('event_name'); + var name = req.params.event_name; name = name || ""; var socket = new CoreController(); @@ -221,10 +221,10 @@ var EventsApi = { EventsApi.pipeEvents(socket, req, res); }, get_core_events: function (req, res) { - var name = req.param('event_name'); + var name = req.params.event_name; var socket = new CoreController(); name = name || ""; - var coreid = req.coreID || req.param('coreid'); + var coreid = req.coreID || req.params.coreid; var userid = Api.getUserID(req); // if (userid) { From fbea7cdadf4484242e48bc688cd6e9725fdbc805 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 10:29:04 +0200 Subject: [PATCH 009/504] first attempt to claim a device --- js/lib/RolesController.js | 88 +++++++++++++++++++++-- js/main.js | 2 +- js/views/api_v1.js | 145 ++++++++++++++++++++++++++++++++++---- 3 files changed, 214 insertions(+), 21 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 1e852be6..f46534ea 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -34,9 +34,12 @@ function RolesController() { RolesController.prototype = { users: null, usersByToken: null, + usersByDevice: null, + usersByClaimCode: null, usersByUsername: null, tokens: null, - + claimCodes: null, + devices: null, init: function () { this._loadAndCacheUsers(); @@ -59,6 +62,20 @@ RolesController.prototype = { this.usersByToken[ token ] = userObj; this.tokens[token.token] = token; } + + //claim codes + this.claimCodes[userObj._id] = userObj.claim_codes; + for (var i = 0; i < userObj.claim_codes.length; i++) { + var claimCode = userObj.claim_codes[i]; + this.usersByClaimCode[claimCode] = userObj; + } + + //devices claimed + this.devices[userObj._id] = userObj.devices; + for (var i = 0; i < userObj.devices.length; i++) { + var deviceId = userObj.devices[i]; + this.usersByDevice[ deviceId ] = userObj; + } }, destroyAccessToken: function (access_token) { var userObj = this.usersByToken[access_token]; @@ -102,7 +119,55 @@ RolesController.prototype = { } return tmp.promise; }, - + addClaimCode: function (claimCode, userId) { + var tmp = when.defer(); + try { + var userObj = this.getUserByUserid(userId); + + userObj.claim_codes.push(claimCode); + this.saveUser(userObj); + + this.claimCodes[userObj._id].push(claimCode); + this.usersByClaimCode[ claimCode ] = userObj; + + tmp.resolve(); + } + catch (ex) { + logger.error("Error adding claim code ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addDevice: function (deviceId, userId) { + var tmp = when.defer(); + try { + var userObj = this.getUserByUserid(userId); + + /*var deviceObj = { + id: deviceId, + name: null + };*/ + + if(this.usersByDevice[deviceId] == undefined) { + userObj.devices.push(deviceId); + this.saveUser(userObj); + + this.devices[userObj._id].push(deviceId); + this.usersByDevice[ deviceId ] = userObj; + + tmp.resolve(); + } else if(this.usersByDevice[deviceId] == userObj) { + tmp.resolve(); + } else { + tmp.reject("cannot claim"); + } + } + catch (ex) { + logger.error("Error adding device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, saveUser: function (userObj) { var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; @@ -113,12 +178,15 @@ RolesController.prototype = { _loadAndCacheUsers: function () { this.users = []; this.usersByToken = {}; + this.usersByDevice = {}; this.usersByUsername = {}; + this.usersByClaimCode = []; this.tokens = {}; - + this.claimCodes = []; + this.devices = []; // list files, load all user objects, index by access_tokens and usernames - + // and devices if (!fs.existsSync(settings.userDataDir)) { fs.mkdirSync(settings.userDataDir); } @@ -148,7 +216,13 @@ RolesController.prototype = { getUserByToken: function (access_token) { return this.usersByToken[access_token]; }, - + getUserByDevice: function (deviceId) { + return this.usersByDevice[deviceId]; + }, + getUserByClaimCode: function (claimCode) { + return this.usersByClaimCode[claimCode]; + }, + getUserByName: function (username) { return this.usersByUsername[username]; }, @@ -211,7 +285,9 @@ RolesController.prototype = { username: username, password_hash: hash, salt: salt, - access_tokens: [] + access_tokens: [], + claim_codes: [], + devices: [] }; var userFile = path.join(settings.userDataDir, username + ".json"); diff --git a/js/main.js b/js/main.js index 0be5d392..9881b093 100644 --- a/js/main.js +++ b/js/main.js @@ -94,7 +94,7 @@ tokenViews.loadViews(app); app.use(function (req, res, next) { - return res.send(404); + return res.sendStatus(404); }); diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 9dec23cd..1116ad3b 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -20,6 +20,7 @@ var settings = require('../settings.js'); var CoreController = require('../lib/CoreController.js'); var roles = require('../lib/RolesController.js'); +var PasswordHasher = require('../lib/PasswordHasher.js'); var sequence = require('when/sequence'); var parallel = require('when/parallel'); @@ -62,9 +63,9 @@ var Api = { app.post('/v1/provisioning/:coreid', Api.provision_core); //app.delete('/v1/devices/:coreid', Api.release_device); - //app.post('/v1/devices', Api.claim_device); - app.post('/v1/device_claims', Api.claim_device); - + + app.post('/v1/devices', Api.claim_device); + app.post('/v1/device_claims', Api.get_claim_code); }, getSocketID: function (userID) { @@ -85,12 +86,15 @@ var Api = { logger.log("ListDevices", { userID: userid }); //give me all the cores - - var allCoreIDs = global.server.getAllCoreIDs(), + + //var allCoreIDs = global.server.getAllCoreIDs(), + var userDevicesIDs = global.roles.devices[userid], devices = [], connected_promises = []; - - for (var coreid in allCoreIDs) { + + for (var index in userDevicesIDs) { + var coreid = userDevicesIDs[index]; + if (!coreid) { continue; } @@ -99,7 +103,7 @@ var Api = { var device = { id: coreid, - name: (core) ? core.name : null, + name: core ? core.name : null, last_app: core ? core.last_flashed_app_name : null, last_heard: null }; @@ -118,7 +122,6 @@ var Api = { when.settle(connected_promises).then(function (descriptors) { for (var i = 0; i < descriptors.length; i++) { var desc = descriptors[i]; - devices[i].connected = ('rejected' !== desc.state); devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; } @@ -315,18 +318,132 @@ var Api = { //send it along to the device service if (!socket.send(coreID, { cmd: "Ping" })) { - tmp.reject("send failed"); + tmp.reject("Device is not connected"); } return tmp.promise; }, - + get_claim_code: function (req, res) { + var userid = Api.getUserID(req); + logger.log("GenerateClaimCode", { userID: userid }); + + var userDevicesIDs = global.roles.devices[userid]; + PasswordHasher.generateSalt(function (err, code) { + code = code.toString('base64'); + code = code.substring(0, 63); + + when(roles.addClaimCode(code, userid)).then( + function () { + res.json({ + claim_code: code, + device_ids: userDevicesIDs + }); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); + }); + }, + claim_device: function (req, res) { - logger.log("claim device"); - //res.json({ ok: true }); - res.json({ claim_code: 'rCFr2KAJNgzJ2rR3Jm1ZoUd7G4Sr3ak7MRHdWrM274eYzInP1+psZ0fP2qNehlj' }); + var userid = Api.getUserID(req); + var coreid = req.body.id; + var core = global.server.getCoreAttributes(req.body.id); + + if(req.body.id) { + if(core.claimCode) { + var user = global.roles.getUserByClaimCode(core.claimCode); + + if(user && user._id == userid) { + when(roles.addDevice(coreid, userid)).then( + function () { + var claimInfo = { + user_id : userid, + id: coreid, + connected: false, + ok: true + } + + when(Api.isDeviceOnline(userid, coreid)) + .then( + function (desc) { + claimInfo.connected = ('rejected' !== desc.state); + + res.json(claimInfo); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); + } else { + res.json({ + ok: false, + errors: [ + {} + ] + //message: "user not found" + }); + } + } else { + res.json({ + ok: false, + errors: [ + {} + ] + //message: "device not found" + }); + } + } else { + res.json({ + ok: false, + errors: [ + "data.deviceID is empty" + ] + }); + } }, + + /*linkDevice: function (coreid, claimCode) { + var user = roles.getUserByClaimCode(claimCode); + + if(user != undefined) { + + logger.log("Linking Device...", { coreID: coreid }); + + when(roles.addDevice(coreid, user.id)).then( + function () { + logger.log("Device linked", { coreID: coreid }); + }, + function (err) { + logger.error("Error in linking Device", { coreID: coreid }); + } + ); + } else { + logger.error("Claim code not valid", { claimCode: claimCode }); + } + },*/ loadCore: function (req, res, next) { From 9d9315f87f1c1c70f671afc9362ee9629d825b33 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 10:48:25 +0200 Subject: [PATCH 010/504] fix AddClaimCode, AddDevice --- js/lib/RolesController.js | 4 +--- js/views/api_v1.js | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index f46534ea..50c612cf 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -127,7 +127,6 @@ RolesController.prototype = { userObj.claim_codes.push(claimCode); this.saveUser(userObj); - this.claimCodes[userObj._id].push(claimCode); this.usersByClaimCode[ claimCode ] = userObj; tmp.resolve(); @@ -148,11 +147,10 @@ RolesController.prototype = { name: null };*/ - if(this.usersByDevice[deviceId] == undefined) { + if(!this.usersByDevice[deviceId]) { userObj.devices.push(deviceId); this.saveUser(userObj); - this.devices[userObj._id].push(deviceId); this.usersByDevice[ deviceId ] = userObj; tmp.resolve(); diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 1116ad3b..bab6a6ef 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -333,7 +333,7 @@ var Api = { code = code.toString('base64'); code = code.substring(0, 63); - when(roles.addClaimCode(code, userid)).then( + when(global.roles.addClaimCode(code, userid)).then( function () { res.json({ claim_code: code, @@ -362,7 +362,7 @@ var Api = { var user = global.roles.getUserByClaimCode(core.claimCode); if(user && user._id == userid) { - when(roles.addDevice(coreid, userid)).then( + when(global.roles.addDevice(coreid, userid)).then( function () { var claimInfo = { user_id : userid, From 3f4d1b7e6bfcfdee4f589fca2d816f36478f1f92 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 14:00:21 +0200 Subject: [PATCH 011/504] fix OTA express 4 --- README.md | 13 ++++++++++--- js/package.json | 1 + js/views/api_v1.js | 8 +++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e8ff1bc..cf06c7c6 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,16 @@ Set Core attributes (and flash a core) `PUT /v1/devices/:coreid` +Get a device claim code + +`POST /v1/device_claims` + +Claim a device + +`POST /v1/devices` + + + Get all Events ``` @@ -156,9 +166,6 @@ What features will be added soon? - Release a Core DELETE /v1/devices/:coreid -- Claim a core - POST /v1/devices - - per-user / per-core ownership and access restrictions. Right now ANY user on your local cloud can access ANY device. - remote compiling, however flashing a binary will still work diff --git a/js/package.json b/js/package.json index 96767b30..d9558af8 100644 --- a/js/package.json +++ b/js/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "body-parser": "^1.15.2", + "connect-multiparty": "^2.0.0", "express": "^4.14.0", "hogan-express": "^0.5.2", "moment": "*", diff --git a/js/views/api_v1.js b/js/views/api_v1.js index bab6a6ef..705746bc 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -36,6 +36,9 @@ var path = require('path'); var ursa = require('ursa'); var moment = require('moment'); +var multipart = require('connect-multiparty'); +var multipartMiddleware = multipart(); + /* * TODO: modularize duplicate code * TODO: implement proper session handling / user authentication @@ -54,7 +57,7 @@ var Api = { app.post('/v1/devices/:coreid/:func', Api.fn_call); app.get('/v1/devices/:coreid/:var', Api.get_var); - app.put('/v1/devices/:coreid', Api.set_core_attributes); + app.put('/v1/devices/:coreid', multipartMiddleware, Api.set_core_attributes); app.get('/v1/devices/:coreid', Api.get_core_attributes); //doesn't need per-core permissions, only shows owned cores. @@ -221,6 +224,7 @@ var Api = { var hasFiles = req.files && req.files.file; if (hasFiles) { + console.log("file"); //oh hey, you want to flash firmware? promises.push(Api.compile_and__or_flash_dfd(req)); } @@ -725,7 +729,9 @@ var Api = { delete args.coreid; if (req.files) { + console.log(req.files); args.data = fs.readFileSync(req.files.file.path); + //args.data = fs.readFileSync(req.files.file.file); } var socket = new CoreController(socketID); From a92e48e570e1195d209542daf6ba11a2fb0aa515 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 21:08:21 +0200 Subject: [PATCH 012/504] fix destroy access token --- js/lib/AccessTokenViews.js | 1 + js/lib/RolesController.js | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/js/lib/AccessTokenViews.js b/js/lib/AccessTokenViews.js index ce2ccd5a..9382e00c 100644 --- a/js/lib/AccessTokenViews.js +++ b/js/lib/AccessTokenViews.js @@ -23,6 +23,7 @@ var sequence = require('when/sequence'); var pipeline = require('when/pipeline'); var PasswordHasher = require('./PasswordHasher.js'); var roles = require('./RolesController.js'); +var logger = require('./logger.js'); var AccessTokenViews = function (options) { this.options = options; diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 50c612cf..78a860e9 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -25,7 +25,7 @@ var PasswordHasher = require('./PasswordHasher.js'); var roles = require('./RolesController.js'); var settings = require('../settings.js'); var logger = require('./logger.js'); - +var utilities = require("./utilities.js"); function RolesController() { this.init(); @@ -82,17 +82,20 @@ RolesController.prototype = { if (!userObj) { return true; } - + delete this.usersByToken[access_token]; - if (userObj.access_token == access_token) { + /*if (userObj.access_token == access_token) { userObj.access_token = null; - } - var idx = utilities.indexOf(userObj.access_tokens, req.params.token); - if (idx >= 0) { - userObj.access_tokens.splice(idx, 1); + }*/ + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var tokenObj = userObj.access_tokens[i]; + if (tokenObj.token == access_token) { + userObj.access_tokens.splice(i, 1); + } } - this.saveUser(); + this.saveUser(userObj); }, addAccessToken: function (accessToken, clientId, userId, expires) { var tmp = when.defer(); @@ -165,8 +168,7 @@ RolesController.prototype = { tmp.reject(ex); } return tmp.promise; - }, - + }, saveUser: function (userObj) { var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; var userJson = JSON.stringify(userObj, null, 2); From ac679a9934ab61d7380193dd10b5c93f14fd6a2c Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 21:09:56 +0200 Subject: [PATCH 013/504] add release device API --- js/lib/RolesController.js | 20 ++++++++++++++++++++ js/views/api_v1.js | 30 +++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 78a860e9..676a9cba 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -168,6 +168,26 @@ RolesController.prototype = { tmp.reject(ex); } return tmp.promise; + }, + removeDevice: function (deviceId, userId) { + var tmp = when.defer(); + try { + var userObj = this.getUserByUserid(userId); + + delete this.usersByDevice[deviceId]; + var index = utilities.indexOf(userObj.devices, deviceId); + if (index > -1) { + userObj.devices.splice(index, 1); + } + + this.saveUser(userObj); + tmp.resolve(); + } + catch (ex) { + logger.error("Error releasing device ", ex); + tmp.reject(ex); + } + return tmp.promise; }, saveUser: function (userObj) { var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 705746bc..e20e2394 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -65,7 +65,7 @@ var Api = { app.post('/v1/provisioning/:coreid', Api.provision_core); - //app.delete('/v1/devices/:coreid', Api.release_device); + app.delete('/v1/devices/:coreid', Api.release_device); app.post('/v1/devices', Api.claim_device); app.post('/v1/device_claims', Api.get_claim_code); @@ -448,8 +448,32 @@ var Api = { logger.error("Claim code not valid", { claimCode: claimCode }); } },*/ - - + + release_device: function (req, res) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + + var user = global.roles.getUserByDevice(coreID); + + if(user && user._id == userid) { + when(global.roles.removeDevice(coreID, userid)).then( + function () { + res.json({'ok' : true }); + }, function (err) { + res.json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + } + ); + } else { + res.json({ + "error": "user Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + } + }, + loadCore: function (req, res, next) { req.coreID = req.params.coreid || req.body.id; From b782f28e437690ac4c4725afe51e3a158beee0b8 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 22:30:26 +0200 Subject: [PATCH 014/504] complete claiming process export global api in order to access from sparkcore.js. need refactoring --- README.md | 5 ++--- js/views/api_v1.js | 40 +++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index cf06c7c6..d3940075 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,9 @@ Claim a device `POST /v1/devices` +Release a device +`DELETE /v1/devices/:coreid` Get all Events @@ -163,9 +165,6 @@ Publish an event What features will be added soon? ==================================== -- Release a Core - DELETE /v1/devices/:coreid - - per-user / per-core ownership and access restrictions. Right now ANY user on your local cloud can access ANY device. - remote compiling, however flashing a binary will still work diff --git a/js/views/api_v1.js b/js/views/api_v1.js index e20e2394..cbf93cc7 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -428,26 +428,6 @@ var Api = { }); } }, - - /*linkDevice: function (coreid, claimCode) { - var user = roles.getUserByClaimCode(claimCode); - - if(user != undefined) { - - logger.log("Linking Device...", { coreID: coreid }); - - when(roles.addDevice(coreid, user.id)).then( - function () { - logger.log("Device linked", { coreID: coreid }); - }, - function (err) { - logger.error("Error in linking Device", { coreID: coreid }); - } - ); - } else { - logger.error("Claim code not valid", { claimCode: claimCode }); - } - },*/ release_device: function (req, res) { var coreID = req.coreID; @@ -474,6 +454,24 @@ var Api = { } }, + linkDevice: function (coreid, claimCode) { + var user = global.roles.getUserByClaimCode(claimCode); + if(user && user._id) { + logger.log("Linking Device...", { coreID: coreid }); + + when(global.roles.addDevice(coreid, user._id)).then( + function () { + logger.log("Device linked", { coreID: coreid }); + }, + function (err) { + logger.error("Error in linking Device", { coreID: coreid }); + } + ); + } else { + logger.error("Claim code not valid", { claimCode: claimCode }); + } + }, + loadCore: function (req, res, next) { req.coreID = req.params.coreid || req.body.id; @@ -835,4 +833,4 @@ var Api = { _: null }; -exports = module.exports = Api; +exports = module.exports = global.api = Api; From fab3432ef91d959e9eef7adb282c4a1700fbe808 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 7 Aug 2016 22:41:38 +0200 Subject: [PATCH 015/504] add claimed core attribute --- js/views/api_v1.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index cbf93cc7..8f519324 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -288,7 +288,6 @@ var Api = { } }, - isDeviceOnline: function (userID, coreID) { var tmp = when.defer(); @@ -380,6 +379,7 @@ var Api = { function (desc) { claimInfo.connected = ('rejected' !== desc.state); + global.server.setCoreAttribute(coreid, "claimed", true); res.json(claimInfo); }, function (err) { @@ -438,6 +438,8 @@ var Api = { if(user && user._id == userid) { when(global.roles.removeDevice(coreID, userid)).then( function () { + + global.server.setCoreAttribute(coreID, "claimed", false); res.json({'ok' : true }); }, function (err) { res.json({ @@ -461,6 +463,7 @@ var Api = { when(global.roles.addDevice(coreid, user._id)).then( function () { + global.server.setCoreAttribute(coreid, "claimed", true); logger.log("Device linked", { coreID: coreid }); }, function (err) { From fa32a57e522594d1dbffe22a633585566c6a6d62 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Mon, 8 Aug 2016 17:04:33 +0200 Subject: [PATCH 016/504] fix device permission on Api --- js/lib/RolesController.js | 2 +- js/views/api_v1.js | 123 +++++++++++++++++++++++++------------- 2 files changed, 81 insertions(+), 44 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 676a9cba..57175263 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -160,7 +160,7 @@ RolesController.prototype = { } else if(this.usersByDevice[deviceId] == userObj) { tmp.resolve(); } else { - tmp.reject("cannot claim"); + tmp.reject("already claimed"); } } catch (ex) { diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 8f519324..fc3a4a2a 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -19,7 +19,6 @@ var settings = require('../settings.js'); var CoreController = require('../lib/CoreController.js'); -var roles = require('../lib/RolesController.js'); var PasswordHasher = require('../lib/PasswordHasher.js'); var sequence = require('when/sequence'); @@ -83,6 +82,16 @@ var Api = { //req.user.id is set in authorise.validateAccessToken in the OAUTH code return req.user.id; }, + + hasDevice: function (req) { + //check core permission + if(global.roles.getUserByDevice(req.coreID) && global.roles.getUserByDevice(req.coreID)._id == req.user.id) { + return true; + } else { + logger.log("device Permission Denied"); + return false; + } + }, list_devices: function (req, res) { var userid = Api.getUserID(req); @@ -138,7 +147,14 @@ var Api = { var socketID = Api.getSocketID(userid), coreID = req.coreID, socket = new CoreController(socketID); - + + if(!Api.hasDevice(req)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); @@ -209,7 +225,15 @@ var Api = { set_core_attributes: function (req, res) { var coreID = req.coreID; var userid = Api.getUserID(req); - + + if(!Api.hasDevice(req)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + var promises = []; logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); @@ -358,13 +382,14 @@ var Api = { claim_device: function (req, res) { var userid = Api.getUserID(req); var coreid = req.body.id; - var core = global.server.getCoreAttributes(req.body.id); + var core = global.server.getCoreAttributes(coreid); - if(req.body.id) { - if(core.claimCode) { + if(coreid) { + + if(core.claimCode) { var user = global.roles.getUserByClaimCode(core.claimCode); - if(user && user._id == userid) { + if(user) { when(global.roles.addDevice(coreid, userid)).then( function () { var claimInfo = { @@ -383,44 +408,42 @@ var Api = { res.json(claimInfo); }, function (err) { - res.json({ + res.status(404).json({ ok: false, errors: [ - err + "Device is not connected" ] }); } ); }, function (err) { - res.json({ + res.status(403).json({ ok: false, errors: [ - err + "That belongs to someone else. To request a transfer add ?request_transfer=true to the URL." ] }); } ); } else { - res.json({ + res.status(404).json({ ok: false, errors: [ {} ] - //message: "user not found" }); } - } else { - res.json({ - ok: false, - errors: [ - {} - ] - //message: "device not found" - }); - } + } else { + res.status(404).json({ + ok: false, + errors: [ + {} + ] + }); + } } else { - res.json({ + res.status(404).json({ ok: false, errors: [ "data.deviceID is empty" @@ -433,27 +456,25 @@ var Api = { var coreID = req.coreID; var userid = Api.getUserID(req); - var user = global.roles.getUserByDevice(coreID); - - if(user && user._id == userid) { - when(global.roles.removeDevice(coreID, userid)).then( - function () { - - global.server.setCoreAttribute(coreID, "claimed", false); - res.json({'ok' : true }); - }, function (err) { - res.json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - } - ); - } else { - res.json({ - "error": "user Permission Denied", + if(!Api.hasDevice(req)) { + res.status(403).json({ + "error": "device Permission Denied", "info": "I didn't recognize that device name or ID" }); + return; } + + when(global.roles.removeDevice(coreID, userid)).then( + function () { + global.server.setCoreAttribute(coreID, "claimed", false); + res.json({'ok' : true }); + }, function (err) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + } + ); }, linkDevice: function (coreid, claimCode) { @@ -532,7 +553,15 @@ var Api = { coreID = req.coreID, varName = req.params.var, format = req.params.format; - + + if(!Api.hasDevice(req)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); @@ -582,7 +611,15 @@ var Api = { coreID = req.coreID, funcName = req.params.func, format = req.params.format; - + + if(!Api.hasDevice(req)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + logger.log("FunCall", { coreID: coreID, user_id: user_id.toString() }); var socketID = Api.getSocketID(user_id); From d6401092a1ca5348f1e72c5325f3cd0d7dcb7d8c Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Mon, 8 Aug 2016 18:31:30 +0200 Subject: [PATCH 017/504] fix events permission --- js/views/EventViews001.js | 4 ++++ js/views/api_v1.js | 25 +++++++++++++------------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index e553f0e3..0321817a 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -143,6 +143,10 @@ var EventsApi = { if (!checkSocket()) { return; } + + if(!Api.hasDevice(coreid, userid)) { + return; + } try { _lastMessage = new Date(); diff --git a/js/views/api_v1.js b/js/views/api_v1.js index fc3a4a2a..f366c269 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -83,12 +83,13 @@ var Api = { return req.user.id; }, - hasDevice: function (req) { + hasDevice: function (coreID, userID) { + var userObj = global.roles.getUserByDevice(coreID); //check core permission - if(global.roles.getUserByDevice(req.coreID) && global.roles.getUserByDevice(req.coreID)._id == req.user.id) { + if(userObj && userObj._id == userID) { return true; } else { - logger.log("device Permission Denied"); + //logger.log("device Permission Denied"); return false; } }, @@ -148,7 +149,7 @@ var Api = { coreID = req.coreID, socket = new CoreController(socketID); - if(!Api.hasDevice(req)) { + if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ "error": "device Permission Denied", "info": "I didn't recognize that device name or ID" @@ -226,7 +227,7 @@ var Api = { var coreID = req.coreID; var userid = Api.getUserID(req); - if(!Api.hasDevice(req)) { + if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ "error": "device Permission Denied", "info": "I didn't recognize that device name or ID" @@ -456,7 +457,7 @@ var Api = { var coreID = req.coreID; var userid = Api.getUserID(req); - if(!Api.hasDevice(req)) { + if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ "error": "device Permission Denied", "info": "I didn't recognize that device name or ID" @@ -554,7 +555,7 @@ var Api = { varName = req.params.var, format = req.params.format; - if(!Api.hasDevice(req)) { + if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ "error": "device Permission Denied", "info": "I didn't recognize that device name or ID" @@ -607,12 +608,12 @@ var Api = { }, fn_call: function (req, res) { - var user_id = Api.getUserID(req), + var userid = Api.getUserID(req), coreID = req.coreID, funcName = req.params.func, format = req.params.format; - if(!Api.hasDevice(req)) { + if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ "error": "device Permission Denied", "info": "I didn't recognize that device name or ID" @@ -620,16 +621,16 @@ var Api = { return; } - logger.log("FunCall", { coreID: coreID, user_id: user_id.toString() }); + logger.log("FunCall", { coreID: coreID, userid: userid.toString() }); - var socketID = Api.getSocketID(user_id); + var socketID = Api.getSocketID(userid); var socket = new CoreController(socketID); var core = socket.getCore(coreID); var args = req.body; delete args.access_token; - logger.log("FunCall - calling core ", { coreID: coreID, user_id: user_id.toString() }); + logger.log("FunCall - calling core ", { coreID: coreID, userid: userid.toString() }); var coreResult = socket.sendAndListenForDFD(coreID, { cmd: "CallFn", name: funcName, args: args }, { cmd: "FnReturn", name: funcName }, From 29973ff2276a471982a1b2661ac83d33aca335c3 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Wed, 10 Aug 2016 21:24:25 +0200 Subject: [PATCH 018/504] add oauth client authorization check --- js/lib/OAuth2ServerModel.js | 4 +++- js/lib/RolesController.js | 11 +++++++++++ js/oauth_clients.json | 7 +++++++ js/settings.js | 2 ++ 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 js/oauth_clients.json diff --git a/js/lib/OAuth2ServerModel.js b/js/lib/OAuth2ServerModel.js index b0f6f2b1..ccd2bb00 100644 --- a/js/lib/OAuth2ServerModel.js +++ b/js/lib/OAuth2ServerModel.js @@ -32,7 +32,9 @@ OAuth2ServerModel.prototype = { }, getClient: function (clientId, clientSecret, callback) { - return callback(null, { client_id: clientId }); + var client = roles.getClient(clientId, clientSecret); + // This object will be exposed in req.oauth.client + return callback(null, ( client ? { client_id : client } : false)); }, grantTypeAllowed: function (clientId, grantType, callback) { diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 57175263..ccb33e4d 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -232,6 +232,17 @@ RolesController.prototype = { } }, + getClient: function ( clientId, clientSecret) { + var filename = settings.oauthClientsFile; + var clients = JSON.parse(fs.readFileSync(filename)); + + for (var i = 0; i < clients.length; i++) { + if (clients[i].client_id == clientId) { + return (clients[i].secret == clientSecret ? clientId : false); + } + } + return false; + }, getUserByToken: function (access_token) { return this.usersByToken[access_token]; diff --git a/js/oauth_clients.json b/js/oauth_clients.json new file mode 100644 index 00000000..6ad9a54e --- /dev/null +++ b/js/oauth_clients.json @@ -0,0 +1,7 @@ +[ + { + "client_id" : "particle", + "secret" : "particle", + "grant_type" : "password" + } +] \ No newline at end of file diff --git a/js/settings.js b/js/settings.js index 1ddab9c2..05d6c32b 100644 --- a/js/settings.js +++ b/js/settings.js @@ -31,4 +31,6 @@ module.exports = { maxHooksPerUser: 20, maxHooksPerDevice: 10, + + oauthClientsFile: path.join(__dirname, "oauth_clients.json") }; \ No newline at end of file From 7bae07742a488bae57e5d7ae23ea035002883f8b Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Tue, 16 Aug 2016 00:42:16 +0200 Subject: [PATCH 019/504] fix events --- README.md | 2 -- js/views/EventViews001.js | 28 ++++++++++++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d3940075..e9d88bd1 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,6 @@ Publish an event What features will be added soon? ==================================== -- per-user / per-core ownership and access restrictions. Right now ANY user on your local cloud can access ANY device. - - remote compiling, however flashing a binary will still work diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index 0321817a..05ff8ff6 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -50,8 +50,11 @@ var EventsApi = { //----------------------------------------------------------------- - pipeEvents: function (socket, req, res, filterCoreId) { + pipeEvents: function (socket, req, res, next, filterCoreId) { var userid = Api.getUserID(req); + if(!userid) { + return next(); + } /* Start SSE @@ -185,12 +188,15 @@ var EventsApi = { }, - get_events: function (req, res) { + get_events: function (req, res, next) { var name = req.params.event_name; name = name || ""; var socket = new CoreController(); var userid = Api.getUserID(req); + if(!userid) { + return next(); + } // if (userid) { // socket.authorize(userid); // } @@ -205,12 +211,15 @@ var EventsApi = { //send it all through EventsApi.pipeEvents(socket, req, res); }, - get_my_events: function (req, res) { + get_my_events: function (req, res, next) { var name = req.params.event_name; name = name || ""; var socket = new CoreController(); var userid = Api.getUserID(req); + if(!userid) { + return next(); + } // if (userid) { // socket.authorize(userid); // } @@ -224,13 +233,16 @@ var EventsApi = { //don't filter by core id EventsApi.pipeEvents(socket, req, res); }, - get_core_events: function (req, res) { + get_core_events: function (req, res, next) { var name = req.params.event_name; var socket = new CoreController(); name = name || ""; var coreid = req.coreID || req.params.coreid; var userid = Api.getUserID(req); + if(!userid) { + return next(); + } // if (userid) { // socket.authorize(userid); // } @@ -248,14 +260,18 @@ var EventsApi = { }, - send_an_event: function (req, res) { + send_an_event: function (req, res, next) { var userid = Api.getUserID(req), socketID = Api.getSocketID(userid), eventName = req.body.name, data = req.body.data, ttl = req.body.ttl || 60, private_str = req.body.private; - + + if(!userid) { + return next(); + } + var is_public = (!private_str || (private_str == "") || (private_str == "false")); var socket = new CoreController(socketID); From c4fdd917a020afb090b413957625b630e9950f75 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Tue, 16 Aug 2016 00:54:52 +0200 Subject: [PATCH 020/504] update oauth package - new oauth2 server - add refresh_tokens grants --- js/lib/OAuth2ServerModel.js | 37 ++++------ js/lib/RolesController.js | 141 ++++++++++++++++++++++++++---------- js/package.json | 2 +- js/views/api_v1.js | 81 +++++++++++++++++---- 4 files changed, 182 insertions(+), 79 deletions(-) diff --git a/js/lib/OAuth2ServerModel.js b/js/lib/OAuth2ServerModel.js index ccd2bb00..e36c9b25 100644 --- a/js/lib/OAuth2ServerModel.js +++ b/js/lib/OAuth2ServerModel.js @@ -26,39 +26,32 @@ var OAuth2ServerModel = function (options) { }; OAuth2ServerModel.prototype = { - getAccessToken: function (bearerToken, callback) { - var token = roles.getTokenInfoByToken(bearerToken); - callback(null, token); + getAccessToken: function (bearerToken) { + return when(roles.getTokenInfoByAccessToken(bearerToken)); }, - getClient: function (clientId, clientSecret, callback) { - var client = roles.getClient(clientId, clientSecret); - // This object will be exposed in req.oauth.client - return callback(null, ( client ? { client_id : client } : false)); + getClient: function(clientId, clientSecret) { + return when(roles.getClient(clientId, clientSecret)); }, - - grantTypeAllowed: function (clientId, grantType, callback) { - return callback(null, 'password' === grantType); + + getRefreshToken: function (bearerToken) { + return when(roles.getTokenInfoByRefreshToken(bearerToken)); + }, + + revokeToken: function (bearerToken) { + return when(roles.revokeToken(bearerToken)); }, - saveAccessToken: function (accessToken, clientId, userId, expires, callback) { - when(roles.addAccessToken(accessToken, clientId, userId, expires)) - .ensure(callback); + saveToken: function (token, client, user) { + return when(roles.addAccessToken(token, client, user)); }, - getUser: function (username, password, callback) { + getUser: function (username, password) { if (username && username.toLowerCase) { username = username.toLowerCase(); } - when(roles.validateLogin(username, password)) - .then( - function (user) { - callback(null, { id: user._id }); - }, - function (err) { - callback(err, null); - }); + return when(roles.validateLogin(username, password)); } }; diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index ccb33e4d..67a98b32 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -37,8 +37,12 @@ RolesController.prototype = { usersByDevice: null, usersByClaimCode: null, usersByUsername: null, - tokens: null, + + access_tokens: null, + refresh_tokens: null, + claimCodes: null, + devices: null, init: function () { @@ -49,18 +53,11 @@ RolesController.prototype = { this.users.push(userObj); this.usersByUsername[ userObj.username ] = userObj; - if (userObj.access_token) { - this.usersByToken[userObj.access_token] = userObj; - this.tokens.push({ - user_id: userObj._id, - expires: userObj.access_token_expires_at - }); - } - for (var i = 0; i < userObj.access_tokens.length; i++) { var token = userObj.access_tokens[i]; - this.usersByToken[ token ] = userObj; - this.tokens[token.token] = token; + this.usersByToken[token.token] = userObj; + this.access_tokens[token.token] = token; + this.refresh_tokens[token.refresh_token] = token; } //claim codes @@ -84,10 +81,8 @@ RolesController.prototype = { } delete this.usersByToken[access_token]; - /*if (userObj.access_token == access_token) { - userObj.access_token = null; - }*/ - + delete this.access_tokens[access_token]; + for (var i = 0; i < userObj.access_tokens.length; i++) { var tokenObj = userObj.access_tokens[i]; if (tokenObj.token == access_token) { @@ -97,24 +92,62 @@ RolesController.prototype = { this.saveUser(userObj); }, - addAccessToken: function (accessToken, clientId, userId, expires) { - var tmp = when.defer(); - try { - var userObj = this.getUserByUserid(userId); - this.usersByToken[accessToken] = userObj; + revokeToken: function (token) { + var userObj = this.usersByToken[token.accessToken]; + if (!userObj) { + return false; + } + var tokenObj = this.access_tokens[token.accessToken]; + if(!tokenObj) { + return false; + } + + delete this.usersByToken[token.accessToken]; + delete this.access_tokens[token.accessToken]; + delete this.refresh_tokens[token.refreshToken]; + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var tokenObj = userObj.access_tokens[i]; + if (tokenObj.token == token.accessToken) { + userObj.access_tokens.splice(i, 1); + } + } + this.saveUser(userObj); - var tokenObj = { - user_id: userId, - client_id: clientId, - token: accessToken, - expires: expires, - _id: accessToken - }; + return token; + }, + addAccessToken: function (token, client, user) { + var tmp = when.defer(); + try { + var tokenObj = { + //user_id: user._id, + token: token.accessToken, + expires_at: token.accessTokenExpiresAt, + client: client.client_id, + refresh_token: token.refreshToken, + scope: token.scope + }; + + var userObj = this.getUserByUserid(user._id); + if(!userObj) { + //refresh_token + userObj = this.getUserByUserid(user); + } + this.usersByToken[token.accessToken] = userObj; - this.tokens[accessToken] = tokenObj; + this.access_tokens[token.accessToken] = tokenObj; + this.refresh_tokens[token.refreshToken] = tokenObj; userObj.access_tokens.push(tokenObj); this.saveUser(userObj); - tmp.resolve(); + + tmp.resolve({ + accessToken: token.accessToken, + client: client, + user: userObj._id, + refreshToken: token.refreshToken, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt + }); } catch (ex) { logger.error("Error adding access token ", ex); @@ -194,24 +227,24 @@ RolesController.prototype = { var userJson = JSON.stringify(userObj, null, 2); fs.writeFileSync(userFile, userJson); }, - + _loadAndCacheUsers: function () { this.users = []; this.usersByToken = {}; this.usersByDevice = {}; this.usersByUsername = {}; - this.usersByClaimCode = []; - this.tokens = {}; - this.claimCodes = []; + this.usersByClaimCode = {}; + this.access_tokens = {}; + this.refresh_tokens = {}; + this.claimCodes = {}; this.devices = []; - + // list files, load all user objects, index by access_tokens and usernames // and devices if (!fs.existsSync(settings.userDataDir)) { fs.mkdirSync(settings.userDataDir); } - - + var files = fs.readdirSync(settings.userDataDir); if (!files || (files.length == 0)) { logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); @@ -235,10 +268,11 @@ RolesController.prototype = { getClient: function ( clientId, clientSecret) { var filename = settings.oauthClientsFile; var clients = JSON.parse(fs.readFileSync(filename)); - for (var i = 0; i < clients.length; i++) { if (clients[i].client_id == clientId) { - return (clients[i].secret == clientSecret ? clientId : false); + if(clients[i].client_secret == clientSecret) { + return clients[i]; + } } } return false; @@ -257,8 +291,35 @@ RolesController.prototype = { getUserByName: function (username) { return this.usersByUsername[username]; }, - getTokenInfoByToken: function (token) { - return this.tokens[token]; + getTokenInfoByAccessToken: function (token) { + var tokenObj = this.access_tokens[token]; + if(!tokenObj) { + return false; + } + console.log(tokenObj); + return { + accessToken: tokenObj.token, + client: tokenObj.client, + user: this.getUserByToken(tokenObj.token)._id, + refreshToken: tokenObj.refresh_token, + accessTokenExpiresAt: new Date(tokenObj.expires_at), + scope: tokenObj.scope + }; + }, + getTokenInfoByRefreshToken: function (token) { + var tokenObj = this.refresh_tokens[token]; + if(!tokenObj) { + return false; + } + return { + accessToken: tokenObj.token, + client: tokenObj.client, + user: this.getUserByToken(tokenObj.token)._id, + refreshToken: tokenObj.refresh_token, + refreshTokenExpiresAt: new Date(tokenObj.expires_at), + //refreshTokenExpiresAt not managed return accessTokenExpires + scope: tokenObj.scope + }; }, getUserByUserid: function (userid) { for (var i = 0; i < this.users.length; i++) { diff --git a/js/package.json b/js/package.json index d9558af8..3f8c01ab 100644 --- a/js/package.json +++ b/js/package.json @@ -17,10 +17,10 @@ "body-parser": "^1.15.2", "connect-multiparty": "^2.0.0", "express": "^4.14.0", + "express-oauth-server": "^1.0.0", "hogan-express": "^0.5.2", "moment": "*", "morgan": "^1.7.0", - "node-oauth2-server": "^1.5.3", "request": "*", "spark-protocol": "*", "ursa": "*", diff --git a/js/views/api_v1.js b/js/views/api_v1.js index f366c269..19f79444 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -75,12 +75,12 @@ var Api = { }, getUserID: function (req) { - if (!req.user) { + if (!req.app.locals.oauth) { logger.log("User obj was empty"); - return null; + return false; } //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.user.id; + return req.app.locals.oauth.token.user; }, hasDevice: function (coreID, userID) { @@ -94,8 +94,12 @@ var Api = { } }, - list_devices: function (req, res) { + list_devices: function (req, res, next) { var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + logger.log("ListDevices", { userID: userid }); //give me all the cores @@ -143,8 +147,12 @@ var Api = { }); }, - get_core_attributes: function (req, res) { + get_core_attributes: function (req, res, next) { var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + var socketID = Api.getSocketID(userid), coreID = req.coreID, socket = new CoreController(socketID); @@ -223,9 +231,12 @@ var Api = { }, - set_core_attributes: function (req, res) { + set_core_attributes: function (req, res, next) { var coreID = req.coreID; var userid = Api.getUserID(req); + if(!userid) { + return next(); + } if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ @@ -352,8 +363,12 @@ var Api = { return tmp.promise; }, - get_claim_code: function (req, res) { + get_claim_code: function (req, res, next) { var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + logger.log("GenerateClaimCode", { userID: userid }); var userDevicesIDs = global.roles.devices[userid]; @@ -380,8 +395,12 @@ var Api = { }); }, - claim_device: function (req, res) { + claim_device: function (req, res, next) { var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + var coreid = req.body.id; var core = global.server.getCoreAttributes(coreid); @@ -453,9 +472,12 @@ var Api = { } }, - release_device: function (req, res) { + release_device: function (req, res, next) { var coreID = req.coreID; var userid = Api.getUserID(req); + if(!userid) { + return next(); + } if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ @@ -510,6 +532,10 @@ var Api = { //if that user doesn't own that coreID, maybe they sent us a core name var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + var gotCore = utilities.deferredAny([ function () { var core = global.server.getCoreAttributes(req.coreID); @@ -548,8 +574,12 @@ var Api = { }) }, - get_var: function (req, res) { + get_var: function (req, res, next) { var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + var socketID = Api.getSocketID(userid), coreID = req.coreID, varName = req.params.var, @@ -607,12 +637,16 @@ var Api = { }); }, - fn_call: function (req, res) { + fn_call: function (req, res, next) { var userid = Api.getUserID(req), coreID = req.coreID, funcName = req.params.func, format = req.params.format; + if(!userid) { + return next(); + } + if(!Api.hasDevice(coreID, userid)) { res.status(403).json({ "error": "device Permission Denied", @@ -703,7 +737,11 @@ var Api = { socketID = Api.getSocketID(userid), coreID = req.coreID, showSignal = parseInt(req.body.signal); - + + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); var socket = new CoreController(socketID); @@ -737,7 +775,10 @@ var Api = { var userid = Api.getUserID(req), coreID = req.coreID; - + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + // // Did they pass us a source file or a binary file? // @@ -785,7 +826,11 @@ var Api = { var userid = Api.getUserID(req), socketID = Api.getSocketID(userid), coreID = req.coreID; - + + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); var args = req.query; @@ -826,7 +871,7 @@ var Api = { return tmp.promise; }, - provision_core: function (req, res) { + provision_core: function (req, res, next) { //if we're here, the user should be allowed to provision cores. var done = Api.provision_core_dfd(req); @@ -845,7 +890,11 @@ var Api = { userid = Api.getUserID(req), deviceID = req.body.deviceID, publicKey = req.body.publicKey; - + + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + if (!deviceID) { return when.reject({ error: "No deviceID provided" }); } From b0c361aa5515edba1ff09baf4054538e48cc3233 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Wed, 21 Sep 2016 14:36:41 +0200 Subject: [PATCH 021/504] add customers --- js/lib/CustomerViews.js | 80 +++++++ js/lib/OAuth2ServerModel.js | 4 + js/lib/RolesController.js | 408 +++++++++++++++++++++++++++++++----- js/lib/utilities.js | 9 + js/main.js | 46 ++-- js/oauth_clients.json | 4 +- js/orgs/company.json | 8 + js/settings.js | 3 +- js/views/api_v1.js | 232 ++++++++++++++++++-- 9 files changed, 698 insertions(+), 96 deletions(-) create mode 100644 js/lib/CustomerViews.js create mode 100644 js/orgs/company.json diff --git a/js/lib/CustomerViews.js b/js/lib/CustomerViews.js new file mode 100644 index 00000000..7da0e286 --- /dev/null +++ b/js/lib/CustomerViews.js @@ -0,0 +1,80 @@ +/** +* Created by durielz +*/ + +var fs = require('fs'); +var path = require('path'); +var when = require('when'); +var sequence = require('when/sequence'); +var pipeline = require('when/pipeline'); +var PasswordHasher = require('./PasswordHasher.js'); +var roles = require('./RolesController.js'); +var logger = require('./logger.js'); + +var CustomerViews = function (options) { + this.options = options; +}; + +CustomerViews.prototype = { + loadViews: function (app) { + app.post('/v1/products/:productIdOrSlug/customers', this.createCustomer.bind(this)); + }, + + createCustomer: function (req, res, next) { + var credentials = CustomerViews.prototype.basicAuth(req); + if (!credentials) { + return res.status(401).json({ + ok: false, + errors: ["Unauthorized. You must send client_id and client_secret in HTTP Basic Auth to view your access tokens."] + }); + } + + var product = req.params.productIdOrSlug; + + var email = req.body.email; + if(!email){ + return res.status(400).json({ + ok: false, + errors: ["Email is required"] + }); + } + + when(roles.validateClient(credentials.clientId, credentials.clientSecret)) + .then( + function (clientObj) { + when(roles.createCustomer(clientObj, product, email)) + .then( + function () { + res.json({ok: true}); + }, + function (err) { + res.status(400).json({ ok: false, errors: [ err ] }); + } + ); + }, + function (err) { + res.status(401).json({ ok: false, errors: [ err ] }); + }); + }, + + basicAuth: function (req) { + var auth = req.get('Authorization'); + if (!auth) return null; + + var matches = auth.match(/Basic\s+(\S+)/); + if (!matches) return null; + + var creds = new Buffer(matches[1], 'base64').toString(); + var separatorIndex = creds.indexOf(':'); + if (-1 === separatorIndex) + return null; + + return { + clientId: creds.slice(0, separatorIndex), + clientSecret: creds.slice(separatorIndex + 1) + }; + } + +}; + +module.exports = CustomerViews; diff --git a/js/lib/OAuth2ServerModel.js b/js/lib/OAuth2ServerModel.js index e36c9b25..d75a0100 100644 --- a/js/lib/OAuth2ServerModel.js +++ b/js/lib/OAuth2ServerModel.js @@ -34,6 +34,10 @@ OAuth2ServerModel.prototype = { return when(roles.getClient(clientId, clientSecret)); }, + getUserFromClient: function(client) { + return when(roles.getUserByClient(client.client_id)); + }, + getRefreshToken: function (bearerToken) { return when(roles.getTokenInfoByRefreshToken(bearerToken)); }, diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 67a98b32..98a75d4c 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -40,10 +40,20 @@ RolesController.prototype = { access_tokens: null, refresh_tokens: null, - - claimCodes: null, + claim_codes: null, devices: null, + clients: null, + + orgsBySlug: null, + orgsByUserId: null, + + customers: null, + customersByEmail: null, + //customersByToken: null, + + orgsByProduct: null, + products : null, init: function () { this._loadAndCacheUsers(); @@ -61,7 +71,7 @@ RolesController.prototype = { } //claim codes - this.claimCodes[userObj._id] = userObj.claim_codes; + this.claim_codes[userObj._id] = userObj.claim_codes; for (var i = 0; i < userObj.claim_codes.length; i++) { var claimCode = userObj.claim_codes[i]; this.usersByClaimCode[claimCode] = userObj; @@ -74,6 +84,52 @@ RolesController.prototype = { this.usersByDevice[ deviceId ] = userObj; } }, + addCustomer: function (customerObj) { + + console.log("Loading customer " + customerObj.email); + + this.customers.push(customerObj); + this.customersByEmail[ customerObj.email ] = customerObj; + + for (var k = 0; k < customerObj.access_tokens.length; k++) { + var token = customerObj.access_tokens[k]; + this.usersByToken[token.token] = customerObj; + this.access_tokens[token.token] = token; + this.refresh_tokens[token.refresh_token] = token; + } + + //claim codes + this.claim_codes[customerObj._id] = customerObj.claim_codes; + for (var i = 0; i < customerObj.claim_codes.length; i++) { + var claimCode = customerObj.claim_codes[i]; + this.usersByClaimCode[claimCode.code] = customerObj; + } + + //devices claimed + this.devices[customerObj._id] = customerObj.devices; + for (var i = 0; i < customerObj.devices.length; i++) { + var deviceId = customerObj.devices[i]; + this.usersByDevice[ deviceId ] = customerObj; + } + }, + addClient: function (clientObj) { + this.clients.push(clientObj); + }, + addOrg: function (orgObj) { + this.orgsBySlug[orgObj.slug] = orgObj; + this.orgsByUserId[orgObj.user_id] = orgObj; + + for (var i = 0; i < orgObj.customers.length; i++) { + this.addCustomer(orgObj.customers[i]); //add customer + } + + this.products[orgObj.slug] = []; //list product + for (var j = 0; j < orgObj.products.length; j++) { + this.products[orgObj.slug].push(orgObj.products[j]); + this.orgsByProduct[ orgObj.products[j].slug ] = orgObj; + this.orgsByProduct[ orgObj.products[j].product_id ] = orgObj; + } + }, destroyAccessToken: function (access_token) { var userObj = this.usersByToken[access_token]; if (!userObj) { @@ -112,8 +168,11 @@ RolesController.prototype = { userObj.access_tokens.splice(i, 1); } } - this.saveUser(userObj); - + if(tokenObj.scope && tokenObj.scope.indexOf("customer=") > -1) { + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } return token; }, addAccessToken: function (token, client, user) { @@ -127,27 +186,49 @@ RolesController.prototype = { refresh_token: token.refreshToken, scope: token.scope }; - - var userObj = this.getUserByUserid(user._id); - if(!userObj) { - //refresh_token - userObj = this.getUserByUserid(user); + + if(token.scope && token.scope.indexOf("customer=") > -1) { + //is a customer token + var email = token.scope.split("=")[1]; + var customerObj = this.customersByEmail[email]; + + this.usersByToken[token.accessToken] = customerObj; + this.access_tokens[token.accessToken] = tokenObj; + this.refresh_tokens[token.refreshToken] = tokenObj; + customerObj.access_tokens.push(tokenObj); + this.saveCustomer(customerObj); + + tmp.resolve({ + accessToken: token.accessToken, + client: client, + refreshToken: token.refreshToken, + user: customerObj._id, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt + }); + + } else { + var userObj = this.getUserByUserid(user._id); + if(!userObj) { + //refresh_token + userObj = this.getUserByUserid(user); + } + this.usersByToken[token.accessToken] = userObj; + + this.access_tokens[token.accessToken] = tokenObj; + this.refresh_tokens[token.refreshToken] = tokenObj; + userObj.access_tokens.push(tokenObj); + this.saveUser(userObj); + + tmp.resolve({ + accessToken: token.accessToken, + client: client, + user: userObj._id, + refreshToken: token.refreshToken, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt + }); } - this.usersByToken[token.accessToken] = userObj; - - this.access_tokens[token.accessToken] = tokenObj; - this.refresh_tokens[token.refreshToken] = tokenObj; - userObj.access_tokens.push(tokenObj); - this.saveUser(userObj); - - tmp.resolve({ - accessToken: token.accessToken, - client: client, - user: userObj._id, - refreshToken: token.refreshToken, - scope: token.scope, - accessTokenExpiresAt: token.accessTokenExpiresAt - }); } catch (ex) { logger.error("Error adding access token ", ex); @@ -173,20 +254,38 @@ RolesController.prototype = { } return tmp.promise; }, - addDevice: function (deviceId, userId) { + addProductClaimCode: function (claimCode, customerId, productId) { + var tmp = when.defer(); + try { + var claimCodeObj = { + code : claimCode, + product_id : productId + } + + var customerObj = this.getCustomerByCustomerid(customerId); + customerObj.claim_codes.push(claimCodeObj); + this.saveCustomer(customerObj); + + this.usersByClaimCode[ claimCode ] = customerObj; + + tmp.resolve(); + } + catch (ex) { + logger.error("Error adding claim code ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addDevice: function (deviceId, userObj) { var tmp = when.defer(); try { - var userObj = this.getUserByUserid(userId); - - /*var deviceObj = { - id: deviceId, - name: null - };*/ - if(!this.usersByDevice[deviceId]) { userObj.devices.push(deviceId); - this.saveUser(userObj); - + if(userObj.org) { //customer + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } this.usersByDevice[ deviceId ] = userObj; tmp.resolve(); @@ -206,27 +305,89 @@ RolesController.prototype = { var tmp = when.defer(); try { var userObj = this.getUserByUserid(userId); - - delete this.usersByDevice[deviceId]; - var index = utilities.indexOf(userObj.devices, deviceId); - if (index > -1) { - userObj.devices.splice(index, 1); - } - - this.saveUser(userObj); - tmp.resolve(); + + if(this.usersByDevice[deviceId]) { + var index = utilities.indexOf(userObj.devices, deviceId); + if (index > -1) { + userObj.devices.splice(index, 1); + } + + delete this.usersByDevice[deviceId]; + + this.saveUser(userObj); + tmp.resolve(); + } else { + tmp.reject('Device not found'); + } } catch (ex) { logger.error("Error releasing device ", ex); tmp.reject(ex); } return tmp.promise; - }, + }, + removeProductDevice: function (deviceId, productid) { + var tmp = when.defer(); + try { + var productObj = this.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, deviceId); + if (index > -1) { + productObj.devices.splice(index, 1); + this.saveProduct(productObj); + + delete this.usersByDevice[deviceId]; + + var orgObj = this.getOrgByProduct(productObj.product_id); + for (var i = 0; i < orgObj.customers.length; i++) { + var index = utilities.indexOf(orgObj.customers[i].devices, deviceId); + if (index > -1) { + orgObj.customers[i].devices.splice(index, 1); + this.saveCustomer(orgObj.customers[i]); + } + } + + tmp.resolve(); + } else { + tmp.reject('Device not found for product'); + } + } + catch (ex) { + logger.error("Error releasing device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, saveUser: function (userObj) { var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; var userJson = JSON.stringify(userObj, null, 2); fs.writeFileSync(userFile, userJson); }, + saveCustomer: function (customerObj) { + var orgObj = this.orgsBySlug[customerObj.org]; + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + var index = 0; + for (var i = 0; i < orgObj.customers.length; i++) { + var customer = orgObj.customers[i]; + if (customer._id == customerObj._id) { + orgObj.customers[i] = customerObj; + } + } + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); + }, + saveProduct: function (productObj) { + var orgObj = this.orgsByProduct[productObj.slug]; + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + var index = 0; + for (var i = 0; i < orgObj.products.length; i++) { + var product = orgObj.products[i]; + if (product.id == productObj.id) { + orgObj.products[i] = productObj; + } + } + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); + }, _loadAndCacheUsers: function () { this.users = []; @@ -234,10 +395,22 @@ RolesController.prototype = { this.usersByDevice = {}; this.usersByUsername = {}; this.usersByClaimCode = {}; + this.access_tokens = {}; this.refresh_tokens = {}; - this.claimCodes = {}; + this.claim_codes = {}; + this.devices = []; + this.clients = []; + this.orgsBySlug = {}; + this.orgsByUserId = {}; + + this.customers = []; + this.customersByEmail = {}; + //this.customersByToken = {}; + + this.orgsByProduct = {}; + this.products = {}; // list files, load all user objects, index by access_tokens and usernames // and devices @@ -263,20 +436,47 @@ RolesController.prototype = { logger.error("RolesController - error loading user at " + filename); } } - }, + + var filenameClient = settings.oauthClientsFile; + var clients = JSON.parse(fs.readFileSync(filenameClient)); + for (var j = 0; j < clients.length; j++) { + try { + console.log("Loading client " + clients[j].client_id); + this.addClient(clients[j]); + } + catch (ex) { + logger.error("RolesController - error loading client at " + filenameOrg); + } + } + + var filesOrg = fs.readdirSync(settings.orgDataDir); + + for (var k = 0; k < filesOrg.length; k++) { + try { + var filenameOrg = path.join(settings.orgDataDir, filesOrg[k]); + var orgObj = JSON.parse(fs.readFileSync(filenameOrg)); + + console.log("Loading org " + orgObj.slug); + this.addOrg(orgObj); + } + catch (ex) { + logger.error("RolesController - error loading org at " + filenameOrg); + } + } + }, + getClient: function ( clientId, clientSecret) { - var filename = settings.oauthClientsFile; - var clients = JSON.parse(fs.readFileSync(filename)); - for (var i = 0; i < clients.length; i++) { - if (clients[i].client_id == clientId) { - if(clients[i].client_secret == clientSecret) { - return clients[i]; - } - } + var clientObj = this.getClientByClientid(clientId); + if (clientObj.client_secret == clientSecret) { + return clientObj; } return false; }, + + getUserByClient: function ( clientId ) { + return this.getUserByUserid(this.orgsBySlug[clientId].user_id); + }, getUserByToken: function (access_token) { return this.usersByToken[access_token]; @@ -287,6 +487,15 @@ RolesController.prototype = { getUserByClaimCode: function (claimCode) { return this.usersByClaimCode[claimCode]; }, + getOrgByProduct: function (product) { //ok + return this.orgsByProduct[product]; + }, + getOrgByUserid: function (user_id) { //ok + return this.orgsByUserId[user_id]; + }, + getCustomerByEmail: function (email) { + return this.customersByEmail[email]; + }, getUserByName: function (username) { return this.usersByUsername[username]; @@ -296,7 +505,6 @@ RolesController.prototype = { if(!tokenObj) { return false; } - console.log(tokenObj); return { accessToken: tokenObj.token, client: tokenObj.client, @@ -330,7 +538,37 @@ RolesController.prototype = { } return null; }, - + + getProductByProductid: function (productid) { + var orgObj = this.orgsByProduct[productid]; + for (var i = 0; i < this.products[orgObj.slug].length; i++) { + var product = this.products[orgObj.slug][i]; + if (product.id == productid || product.slug == productid) { + return product; + } + } + return null; + }, + + getCustomerByCustomerid: function (customerid) { + for (var i = 0; i < this.customers.length; i++) { + var customer = this.customers[i]; + if (customer._id == customerid) { + return customer; + } + } + return null; + }, + + getClientByClientid: function (clientId) { + for (var i = 0; i < this.clients.length; i++) { + var client = this.clients[i]; + if (client.client_id == clientId) { + return client; + } + } + return null; + }, validateHashPromise: function (user, password) { var tmp = when.defer(); @@ -351,7 +589,6 @@ RolesController.prototype = { return tmp.promise; }, - validateLogin: function (username, password) { var userObj = this.getUserByName(username); if (!userObj) { @@ -360,6 +597,19 @@ RolesController.prototype = { return this.validateHashPromise(userObj, password); }, + + validateClient: function (clientId, clientSecret) { + var tmp = when.defer(); + + var clientObj = this.getClient(clientId, clientSecret); + if (!clientObj) { + return tmp.reject("Bad client"); + } + + tmp.resolve(clientObj); + + return tmp.promise; + }, createUser: function (username, password) { var tmp = when.defer(); @@ -392,6 +642,48 @@ RolesController.prototype = { }); }); + return tmp.promise; + }, + + createCustomer: function (clientObj, product, email) { + var tmp = when.defer(); + var that = this; + + var orgObj = that.getOrgByProduct(product); + if(orgObj && orgObj.slug == clientObj.client_id) { + var customer = that.getCustomerByEmail(email); + if(!customer) { + + PasswordHasher.generateSalt(function (err, customerid) { + customerid = customerid.toString('base64'); + customerid = customerid.substring(0, 32); + + var customer = { + _id: customerid, + email: email, + org: orgObj.slug, + access_tokens: [], + claim_codes: [], + devices: [] + }; + + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + orgObj.customers.push(customer); + + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); + + that.addCustomer(customer); + + tmp.resolve(); + }); + } else { + tmp.reject('Customer '+email+' already exists'); + } + } else { + tmp.reject('Bad product'); + } + return tmp.promise; } }; diff --git a/js/lib/utilities.js b/js/lib/utilities.js index de822d22..683539cf 100644 --- a/js/lib/utilities.js +++ b/js/lib/utilities.js @@ -377,6 +377,15 @@ module.exports = that = { return results; }, + + slugify: function (text) { + return text.toString().toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text + }, foo: null }; \ No newline at end of file diff --git a/js/main.js b/js/main.js index 9881b093..ac14334d 100644 --- a/js/main.js +++ b/js/main.js @@ -26,23 +26,15 @@ var settings = require('./settings.js'); var utilities = require("./lib/utilities.js"); var logger = require('./lib/logger.js'); -var OAuthServer = require('node-oauth2-server'); +//var OAuthServer = require('node-oauth2-server'); +var oauthserver = require('express-oauth-server'); + var OAuth2ServerModel = require('./lib/OAuth2ServerModel'); var AccessTokenViews = require('./lib/AccessTokenViews.js'); +var CustomerViews = require('./lib/CustomerViews.js'); global._socket_counter = 1; -var oauth = OAuthServer({ - model: new OAuth2ServerModel({ }), - allow: { - "post": ['/v1/users'], - "get": ['/server/health', '/v1/access_tokens'], - "delete": ['/v1/access_tokens/([0-9a-f]{40})'] - }, - grants: ['password'], - accessTokenLifetime: 7776000 //90 days -}); - var set_cors_headers = function (req, res, next) { if ('OPTIONS' === req.method) { res.set({ @@ -69,14 +61,27 @@ process.on('uncaughtException', function (ex) { var app = express(); -app.set('json spaces', 4); + +app.oauth = new oauthserver({ + model: new OAuth2ServerModel({}), + accessTokenLifetime: 7776000 //90 days +}); + +app.set('json spaces', 2); app.use(morgan('combined')); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(set_cors_headers); -app.use(oauth.handler()); -app.use(oauth.errorHandler()); +app.post('/oauth/token', app.oauth.token()); + +//app.use(app.oauth.token()); +app.all('/v1/device*', app.oauth.authenticate()); +app.all('/v1/provisioning*', app.oauth.authenticate()); +app.all('/v1/events*', app.oauth.authenticate()); +//app.all('/v1/products', app.oauth.authenticate()); //TODO remove customer creation +//app.use(oauth.handler()); +//app.use(oauth.errorHandler()); var UserCreator = require('./lib/UserCreator.js'); app.post('/v1/users', UserCreator.getMiddleware()); @@ -84,18 +89,19 @@ app.post('/v1/users', UserCreator.getMiddleware()); var api = require('./views/api_v1.js'); var eventsV1 = require('./views/EventViews001.js'); var tokenViews = new AccessTokenViews({ }); +var customerViews = new CustomerViews({ }); eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); +customerViews.loadViews(app); - - -app.use(function (req, res, next) { - return res.sendStatus(404); -}); +/*app.use(function (req, res, next) { + res.status(404); + next(); +});*/ var node_port = process.env.NODE_PORT || '8080'; diff --git a/js/oauth_clients.json b/js/oauth_clients.json index 6ad9a54e..82af918d 100644 --- a/js/oauth_clients.json +++ b/js/oauth_clients.json @@ -1,7 +1,7 @@ [ { "client_id" : "particle", - "secret" : "particle", - "grant_type" : "password" + "client_secret" : "particle", + "grants" : [ "password", "refresh_token" ] } ] \ No newline at end of file diff --git a/js/orgs/company.json b/js/orgs/company.json new file mode 100644 index 00000000..47c3c38c --- /dev/null +++ b/js/orgs/company.json @@ -0,0 +1,8 @@ +{ + "_id": "wepqelo12ke12em13e", + "user_id": "5IMm6JpaQFkT2gh3UZBF0onKSphWYRXH", + "name": "Company", + "slug": "company", + "customers": [], + "products": [] +} \ No newline at end of file diff --git a/js/settings.js b/js/settings.js index 05d6c32b..5da94ad1 100644 --- a/js/settings.js +++ b/js/settings.js @@ -32,5 +32,6 @@ module.exports = { maxHooksPerUser: 20, maxHooksPerDevice: 10, - oauthClientsFile: path.join(__dirname, "oauth_clients.json") + oauthClientsFile: path.join(__dirname, "oauth_clients.json"), + orgDataDir: path.join(__dirname, "orgs"), }; \ No newline at end of file diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 19f79444..5ff46c56 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -57,10 +57,10 @@ var Api = { app.get('/v1/devices/:coreid/:var', Api.get_var); app.put('/v1/devices/:coreid', multipartMiddleware, Api.set_core_attributes); - app.get('/v1/devices/:coreid', Api.get_core_attributes); + app.get('/v1/devices/:coreid', Api.get_core_attributes);//ok customer //doesn't need per-core permissions, only shows owned cores. - app.get('/v1/devices', Api.list_devices); + app.get('/v1/devices', Api.list_devices);//ok customer app.post('/v1/provisioning/:coreid', Api.provision_core); @@ -68,6 +68,13 @@ var Api = { app.post('/v1/devices', Api.claim_device); app.post('/v1/device_claims', Api.get_claim_code); + + /*products*/ + app.get('/v1/products', app.oauth.authenticate(), Api.list_products); + app.get('/v1/products/:productIdOrSlug', app.oauth.authenticate(), Api.get_product); + app.post('/v1/products/:productIdOrSlug/device_claims', app.oauth.authenticate(), Api.get_product_claim_code); + app.delete('/v1/products/:productIdOrSlug/devices/:coreid', app.oauth.authenticate(), Api.release_product_device); + app.get('/v1/products/:productIdOrSlug/customers', app.oauth.authenticate(), Api.get_product_customers); }, getSocketID: function (userID) { @@ -76,7 +83,33 @@ var Api = { getUserID: function (req) { if (!req.app.locals.oauth) { - logger.log("User obj was empty"); + logger.log("Token obj was empty"); + return false; + } + if(req.app.locals.oauth.token.scope && req.app.locals.oauth.token.scope.indexOf("customer=") > -1) { + logger.log("Customer token"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + getCustomerID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); + return false; + } + if(!req.app.locals.oauth.token.scope || req.app.locals.oauth.token.scope.indexOf("customer=") == -1) { + logger.log("User token"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + getUserOrCustomerID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); return false; } //req.user.id is set in authorise.validateAccessToken in the OAUTH code @@ -95,7 +128,7 @@ var Api = { }, list_devices: function (req, res, next) { - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -117,7 +150,7 @@ var Api = { } var core = global.server.getCoreAttributes(coreid); - + var device = { id: coreid, name: core ? core.name : null, @@ -148,7 +181,7 @@ var Api = { }, get_core_attributes: function (req, res, next) { - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -407,13 +440,13 @@ var Api = { if(coreid) { if(core.claimCode) { - var user = global.roles.getUserByClaimCode(core.claimCode); + var userObj = global.roles.getUserByClaimCode(core.claimCode); if(user) { - when(global.roles.addDevice(coreid, userid)).then( + when(global.roles.addDevice(coreid, userObj)).then( function () { var claimInfo = { - user_id : userid, + user_id : userObj._id, id: coreid, connected: false, ok: true @@ -500,18 +533,38 @@ var Api = { ); }, - linkDevice: function (coreid, claimCode) { - var user = global.roles.getUserByClaimCode(claimCode); - if(user && user._id) { + //called when the core send its claim code + linkDevice: function (coreid, claimCode, productid) { + var userObj = global.roles.getUserByClaimCode(claimCode); + if(userObj) { logger.log("Linking Device...", { coreID: coreid }); - when(global.roles.addDevice(coreid, user._id)).then( + if(userObj.org) { //if customer + //check if coreid is present in product devices + var productObj = global.roles.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, deviceId); + if (index > -1) { + for (var i = 0; i < global.roles.claim_codes[userObj._id].length; i++) { + var claimCodeObj = global.roles.claim_codes[userObj._id][i]; + //check if the claim code is valid for the product + if (claimCodeObj.code == claimCode && claimCodeObj.product_id != productid) { + logger.error("Claim code not valid for product", { claimCode: claimCode }); + return false; + } + }; + } else { + logger.error("Device not found for product"); + return false; + } + } + + when(global.roles.addDevice(coreid, userObj)).then( function () { global.server.setCoreAttribute(coreid, "claimed", true); logger.log("Device linked", { coreID: coreid }); }, function (err) { - logger.error("Error in linking Device", { coreID: coreid }); + logger.error("Error in linking Device: "+err, { coreID: coreid }); } ); } else { @@ -918,7 +971,156 @@ var Api = { return result.promise; }, - + + //List products the currently authenticated user has access to. + list_products: function (req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("ListProducts", { userID: userid }); + + var orgObj = global.roles.getOrgByUserid(userid); + if(orgObj) { + var productObjs = global.roles.products[orgObj.slug]; + + //remove devices ?? + res.json({ products : productObjs }); + } else { + res.json({ products : [] }); + } + }, + + //Retrieve details for a product. + get_product: function( req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("GetProduct", { userID: userid }); + + var productid = req.params.productIdOrSlug; + var orgObj = global.roles.getOrgByProduct(productid); + if(orgObj && orgObj.user_id == userid) { + var productObj = global.roles.getProductByProductid(productid); + + res.json({ product : productObj }); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); + } + }, + + //Generate a device claim code for a customer, scoped for a specific product. + get_product_claim_code: function (req, res, next) { + var customerid = Api.getCustomerID(req); + if(!customerid) { + return next(); + } + + var productid = req.params.productIdOrSlug; + + logger.log("GenerateProductClaimCode", { customerID: customerid }); + + var productObj = global.roles.getProductByProductid(productid); + var productDevicesIDs = productObj.devices; + PasswordHasher.generateSalt(function (err, code) { + code = code.toString('base64'); + code = code.substring(0, 63); + + when(global.roles.addProductClaimCode(code, customerid, productObj.product_id)).then( + function () { + res.json({ + claim_code: code, + device_ids: productDevicesIDs + }); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); + }); + }, + + //Remove a device from a product and re-assign to a generic Particle product. This endpoint will unclaim the device if it is owned by a customer. + release_product_device: function (req, res, next) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + var productid = req.params.productIdOrSlug; + var orgObj = global.roles.getOrgByProduct(productid); + if(orgObj && orgObj.user_id == userid) { + when(global.roles.removeProductDevice(coreID, productid)).then( + function () { + global.server.setCoreAttribute(coreID, "claimed", false); + res.status(204).json(); + }, function (err) { + res.status(400).json({ + "code": 400, + "ok": false, + "info": "Device not found for this product" + }); + } + ); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); + } + }, + + //List Customers for a product. + get_product_customers: function( req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("GetProductCustomer", { userID: userid }); + + var productid = req.params.productIdOrSlug; + var productObj = global.roles.getProductByProductid(productid); + var productDevices = productObj.devices; + var orgObj = global.roles.getOrgByProduct(productid); + if(orgObj && orgObj.user_id == userid) { + var customerObjs = []; + var devices = []; + for (var k = 0; k < productDevices.length; k++) { + var deviceid = productDevices[k]; + var customerObj = global.roles.getUserByDevice(deviceid); + if(customerObj && customerObj.org) { //if customer + customerObjs.push({ + id: customerObj._id, + email: customerObj.email, + devices: customerObj.devices + }); + + var core = global.server.getCoreAttributes(deviceid); + + var device = { + id: deviceid, + name: core ? core.name : null, + last_ip_address: core.last_ip_address, + product_id: core.product_id + }; + + //miss device status + + devices.push(device); + } + } + res.json({ customers : customerObjs, devices : devices }); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); + } + }, _: null }; From adacef4b8f2c786845aa28f6d85132efaf30cc95 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Wed, 21 Sep 2016 14:38:41 +0200 Subject: [PATCH 022/504] ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 55c6ac81..f87b0742 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ js/build/config.gypi js/package.json.ori spark-server.esproj .DS_Store +/js/oauth_clients.json +js/orgs/* From 7ef3a9233b120dae3314b42e685358669ef73062 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Thu, 22 Sep 2016 17:03:03 +0200 Subject: [PATCH 023/504] fix oauth routes --- js/main.js | 10 +++++----- js/package.json | 2 +- js/views/api_v1.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/js/main.js b/js/main.js index ac14334d..69414723 100644 --- a/js/main.js +++ b/js/main.js @@ -64,6 +64,7 @@ var app = express(); app.oauth = new oauthserver({ model: new OAuth2ServerModel({}), + allowBearerTokensInQueryString:true, accessTokenLifetime: 7776000 //90 days }); @@ -76,7 +77,7 @@ app.use(set_cors_headers); app.post('/oauth/token', app.oauth.token()); //app.use(app.oauth.token()); -app.all('/v1/device*', app.oauth.authenticate()); +app.all('/v1/devices*', app.oauth.authenticate()); app.all('/v1/provisioning*', app.oauth.authenticate()); app.all('/v1/events*', app.oauth.authenticate()); //app.all('/v1/products', app.oauth.authenticate()); //TODO remove customer creation @@ -98,10 +99,9 @@ tokenViews.loadViews(app); customerViews.loadViews(app); -/*app.use(function (req, res, next) { - res.status(404); - next(); -});*/ +app.use(function (req, res, next) { + res.status(404).send({ ok: false, error: "Not Found" }); +}); var node_port = process.env.NODE_PORT || '8080'; diff --git a/js/package.json b/js/package.json index 3f8c01ab..a3647cf7 100644 --- a/js/package.json +++ b/js/package.json @@ -17,7 +17,7 @@ "body-parser": "^1.15.2", "connect-multiparty": "^2.0.0", "express": "^4.14.0", - "express-oauth-server": "^1.0.0", + "express-oauth-server": "git://github.com/durielz/express-oauth-server.git", "hogan-express": "^0.5.2", "moment": "*", "morgan": "^1.7.0", diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 5ff46c56..c2ac4889 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -67,7 +67,7 @@ var Api = { app.delete('/v1/devices/:coreid', Api.release_device); app.post('/v1/devices', Api.claim_device); - app.post('/v1/device_claims', Api.get_claim_code); + app.post('/v1/device_claims', app.oauth.authenticate(), Api.get_claim_code); /*products*/ app.get('/v1/products', app.oauth.authenticate(), Api.list_products); From d537429d263980399aac64534b62af6cfe35cbfc Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Thu, 22 Sep 2016 18:15:41 +0200 Subject: [PATCH 024/504] add validateScope method for oauth --- js/lib/OAuth2ServerModel.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/lib/OAuth2ServerModel.js b/js/lib/OAuth2ServerModel.js index d75a0100..47cf3e7b 100644 --- a/js/lib/OAuth2ServerModel.js +++ b/js/lib/OAuth2ServerModel.js @@ -49,6 +49,10 @@ OAuth2ServerModel.prototype = { saveToken: function (token, client, user) { return when(roles.addAccessToken(token, client, user)); }, + + validateScope: function(user, client, scope) { + return scope; + }, getUser: function (username, password) { if (username && username.toLowerCase) { From ae72ba5a4f7ed1bc17ddb39a0a19ffc3d135d12a Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Fri, 23 Sep 2016 17:23:50 +0200 Subject: [PATCH 025/504] update package --- js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/package.json b/js/package.json index a3647cf7..a1a660ba 100644 --- a/js/package.json +++ b/js/package.json @@ -22,7 +22,7 @@ "moment": "*", "morgan": "^1.7.0", "request": "*", - "spark-protocol": "*", + "spark-protocol": "git://github.com/durielz/spark-protocol.git", "ursa": "*", "when": "*", "xtend": "*" From f4be7612cc66a01d721c2fabd6621a097e0bba04 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 25 Sep 2016 18:52:03 +0200 Subject: [PATCH 026/504] let particle-cli login --- js/lib/RolesController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 98a75d4c..59405508 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -468,7 +468,7 @@ RolesController.prototype = { getClient: function ( clientId, clientSecret) { var clientObj = this.getClientByClientid(clientId); - if (clientObj.client_secret == clientSecret) { + if (clientObj.client_secret == clientSecret || clientId == 'particle') { return clientObj; } return false; From 32adbece723c557dddadc3caf34b176beb1c64ce Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 25 Sep 2016 19:38:02 +0200 Subject: [PATCH 027/504] fix buffering response in Server-Sent Events --- js/views/EventViews001.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index 05ff8ff6..d8fe4459 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -65,7 +65,8 @@ var EventsApi = { res.writeHead(200, { "Connection": "keep-alive", "Content-Type": "text/event-stream", - "Cache-Control": "no-cache" + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no" }); res.write(":ok\n\n"); From 9af7910f9231ab2bb6808a48f5c1a82bb3286476 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sun, 25 Sep 2016 20:14:56 +0200 Subject: [PATCH 028/504] fix devices events owned by user --- js/views/EventViews001.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index d8fe4459..a0df8e30 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -244,6 +244,10 @@ var EventsApi = { if(!userid) { return next(); } + //check if core is owned + if(!Api.hasDevice(coreid, userid)) { + return next(); + } // if (userid) { // socket.authorize(userid); // } From 5ae460590a202535ec75a80a9fb9ad150db4aed1 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Wed, 28 Sep 2016 21:59:36 +0200 Subject: [PATCH 029/504] fix customer events and api --- js/main.js | 2 +- js/views/EventViews001.js | 10 +++++----- js/views/api_v1.js | 21 +++++++++++++++------ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/js/main.js b/js/main.js index 69414723..0162fe07 100644 --- a/js/main.js +++ b/js/main.js @@ -43,7 +43,7 @@ var set_cors_headers = function (req, res, next) { 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', 'Access-Control-Max-Age': 300 }); - return res.send(204); + return res.sendStatus(204); } else { res.set({'Access-Control-Allow-Origin': '*'}); diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index a0df8e30..39c7df1f 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -51,7 +51,7 @@ var EventsApi = { //----------------------------------------------------------------- pipeEvents: function (socket, req, res, next, filterCoreId) { - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -194,7 +194,7 @@ var EventsApi = { name = name || ""; var socket = new CoreController(); - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -217,7 +217,7 @@ var EventsApi = { name = name || ""; var socket = new CoreController(); - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -240,7 +240,7 @@ var EventsApi = { name = name || ""; var coreid = req.coreID || req.params.coreid; - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -266,7 +266,7 @@ var EventsApi = { send_an_event: function (req, res, next) { - var userid = Api.getUserID(req), + var userid = Api.getUserOrCustomerID(req), socketID = Api.getSocketID(userid), eventName = req.body.name, data = req.body.data, diff --git a/js/views/api_v1.js b/js/views/api_v1.js index c2ac4889..c07d231c 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -126,6 +126,16 @@ var Api = { return false; } }, + + hasOrg: function (userID) { + var orgObj = global.roles.getOrgByUserid(userID); + //check user permission + if(orgObj) { + return true; + } else { + return false; + } + }, list_devices: function (req, res, next) { var userid = Api.getUserOrCustomerID(req); @@ -262,8 +272,7 @@ var Api = { //get_core_attribs - end }, - - + set_core_attributes: function (req, res, next) { var coreID = req.coreID; var userid = Api.getUserID(req); @@ -271,7 +280,7 @@ var Api = { return next(); } - if(!Api.hasDevice(coreID, userid)) { + if(!Api.hasDevice(coreID, userid) && !Api.hasOrg(userid)) { res.status(403).json({ "error": "device Permission Denied", "info": "I didn't recognize that device name or ID" @@ -584,7 +593,7 @@ var Api = { }; //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -628,7 +637,7 @@ var Api = { }, get_var: function (req, res, next) { - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } @@ -691,7 +700,7 @@ var Api = { }, fn_call: function (req, res, next) { - var userid = Api.getUserID(req), + var userid = Api.getUserOrCustomerID(req), coreID = req.coreID, funcName = req.params.func, format = req.params.format; From 2f89a86da3bef432fc417236fddcdc1786953ee0 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Thu, 29 Sep 2016 14:40:16 +0200 Subject: [PATCH 030/504] fix customer claim --- js/lib/RolesController.js | 2 +- js/views/api_v1.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 59405508..cbcb51cf 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -543,7 +543,7 @@ RolesController.prototype = { var orgObj = this.orgsByProduct[productid]; for (var i = 0; i < this.products[orgObj.slug].length; i++) { var product = this.products[orgObj.slug][i]; - if (product.id == productid || product.slug == productid) { + if (product.product_id == productid || product.slug == productid) { return product; } } diff --git a/js/views/api_v1.js b/js/views/api_v1.js index c07d231c..6dac9feb 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -551,7 +551,7 @@ var Api = { if(userObj.org) { //if customer //check if coreid is present in product devices var productObj = global.roles.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, deviceId); + var index = utilities.indexOf(productObj.devices, coreid); if (index > -1) { for (var i = 0; i < global.roles.claim_codes[userObj._id].length; i++) { var claimCodeObj = global.roles.claim_codes[userObj._id][i]; From 3280895dd5a9021f665401df3653225a6cc92e65 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Fri, 30 Sep 2016 19:02:54 +0200 Subject: [PATCH 031/504] add events per product --- js/lib/RolesController.js | 17 +++++++++--- js/views/EventViews001.js | 55 ++++++++++++++++++++++++++++++++++----- js/views/api_v1.js | 16 +++++++++--- 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index cbcb51cf..a3fe378f 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -337,7 +337,7 @@ RolesController.prototype = { delete this.usersByDevice[deviceId]; - var orgObj = this.getOrgByProduct(productObj.product_id); + var orgObj = this.getOrgByProductid(productObj.product_id); for (var i = 0; i < orgObj.customers.length; i++) { var index = utilities.indexOf(orgObj.customers[i].devices, deviceId); if (index > -1) { @@ -487,7 +487,7 @@ RolesController.prototype = { getUserByClaimCode: function (claimCode) { return this.usersByClaimCode[claimCode]; }, - getOrgByProduct: function (product) { //ok + getOrgByProductid: function (product) { //ok return this.orgsByProduct[product]; }, getOrgByUserid: function (user_id) { //ok @@ -550,6 +550,17 @@ RolesController.prototype = { return null; }, + getProductByUserid: function (userid) { + var orgObj = this.orgsByProduct[productid]; + for (var i = 0; i < this.products[orgObj.slug].length; i++) { + var product = this.products[orgObj.slug][i]; + if (product.product_id == productid || product.slug == productid) { + return product; + } + } + return null; + }, + getCustomerByCustomerid: function (customerid) { for (var i = 0; i < this.customers.length; i++) { var customer = this.customers[i]; @@ -649,7 +660,7 @@ RolesController.prototype = { var tmp = when.defer(); var that = this; - var orgObj = that.getOrgByProduct(product); + var orgObj = that.getOrgByProductid(product); if(orgObj && orgObj.slug == clientObj.client_id) { var customer = that.getCustomerByEmail(email); if(!customer) { diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index 39c7df1f..93ba752e 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -45,12 +45,14 @@ var EventsApi = { app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); + + app.get('/v1/products/:productIdOrSlug/events', app.oauth.authenticate(), EventsApi.get_product_events); }, //----------------------------------------------------------------- - pipeEvents: function (socket, req, res, next, filterCoreId) { + pipeEvents: function (socket, req, res, next, filterCoreId, filterProductId) { var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); @@ -139,16 +141,24 @@ var EventsApi = { // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events var writeEventGen = function (isPublic) { - return function (name, data, ttl, published_at, coreid) { + return function (name, data, ttl, published_at, coreid, productid) { if (filterCoreId && (filterCoreId != coreid)) { return; } + + if (filterProductId && (filterProductId != productid)) { + return; + } if (!checkSocket()) { return; } - if(!Api.hasDevice(coreid, userid)) { + if(!filterProductId && !Api.hasDevice(coreid, userid)) { + return; + } + + if(filterProductId && !Api.hasProduct(productid, userid)) { return; } @@ -162,7 +172,8 @@ var EventsApi = { data: data ? data.toString() : null, ttl: ttl ? ttl.toString() : null, published_at: (published_at) ? published_at.toString() : null, - coreid: (coreid) ? coreid.toString() : null + coreid: (coreid) ? coreid.toString() : null, + productid: (productid) ? productid.toString() : null }; res.write("event: " + name + "\n"); res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies @@ -210,7 +221,7 @@ var EventsApi = { //send it all through - EventsApi.pipeEvents(socket, req, res); + EventsApi.pipeEvents(socket, req, res, next); }, get_my_events: function (req, res, next) { var name = req.params.event_name; @@ -232,7 +243,7 @@ var EventsApi = { socket.subscribe(false, name, userid); //don't filter by core id - EventsApi.pipeEvents(socket, req, res); + EventsApi.pipeEvents(socket, req, res, next); }, get_core_events: function (req, res, next) { var name = req.params.event_name; @@ -261,7 +272,7 @@ var EventsApi = { //----------------------------------- //filter to core id - EventsApi.pipeEvents(socket, req, res, coreid); + EventsApi.pipeEvents(socket, req, res, next, coreid); }, @@ -294,6 +305,36 @@ var EventsApi = { res.json({ok: success}); }, 250); }, + + get_product_events: function (req, res, next) { + var name = req.params.event_name; + var socket = new CoreController(); + name = name || ""; + var productid = req.params.productIdOrSlug; + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + //check if product is owned by user + if(!Api.hasProduct(productid, userid)) { + return next(); + } +// if (userid) { +// socket.authorize(userid); +// } + + + //----------------------------------- + //get product events + //socket.subscribe(true, name); + socket.subscribe(true, name, userid); + socket.subscribe(false, name, userid); + + //----------------------------------- + //filter to core id + EventsApi.pipeEvents(socket, req, res, next, null, productid); + }, _: null }; diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 6dac9feb..0fc86d6a 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -136,6 +136,16 @@ var Api = { return false; } }, + + hasProduct: function (productIdOrSlug, userID) { + var orgObj = global.roles.getOrgByProductid(productIdOrSlug); + //check user permission + if(orgObj && orgObj.user_id == userID) { + return true; + } else { + return false; + } + }, list_devices: function (req, res, next) { var userid = Api.getUserOrCustomerID(req); @@ -1011,7 +1021,7 @@ var Api = { logger.log("GetProduct", { userID: userid }); var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProduct(productid); + var orgObj = global.roles.getOrgByProductid(productid); if(orgObj && orgObj.user_id == userid) { var productObj = global.roles.getProductByProductid(productid); @@ -1066,7 +1076,7 @@ var Api = { } var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProduct(productid); + var orgObj = global.roles.getOrgByProductid(productid); if(orgObj && orgObj.user_id == userid) { when(global.roles.removeProductDevice(coreID, productid)).then( function () { @@ -1097,7 +1107,7 @@ var Api = { var productid = req.params.productIdOrSlug; var productObj = global.roles.getProductByProductid(productid); var productDevices = productObj.devices; - var orgObj = global.roles.getOrgByProduct(productid); + var orgObj = global.roles.getOrgByProductid(productid); if(orgObj && orgObj.user_id == userid) { var customerObjs = []; var devices = []; From d9d424b20aacac790330a64feb2a7df4e45cce16 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Fri, 30 Sep 2016 20:08:09 +0200 Subject: [PATCH 032/504] fix product events --- js/views/EventViews001.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js index 93ba752e..b2db7679 100644 --- a/js/views/EventViews001.js +++ b/js/views/EventViews001.js @@ -141,24 +141,20 @@ var EventsApi = { // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events var writeEventGen = function (isPublic) { - return function (name, data, ttl, published_at, coreid, productid) { + return function (name, data, ttl, published_at, coreid) { if (filterCoreId && (filterCoreId != coreid)) { return; } - - if (filterProductId && (filterProductId != productid)) { - return; - } if (!checkSocket()) { return; } - if(!filterProductId && !Api.hasDevice(coreid, userid)) { + if(filterProductId == null && !Api.hasDevice(coreid, userid)) { return; } - if(filterProductId && !Api.hasProduct(productid, userid)) { + if(filterProductId != null && !Api.hasProduct(filterProductId, userid)) { return; } @@ -172,8 +168,7 @@ var EventsApi = { data: data ? data.toString() : null, ttl: ttl ? ttl.toString() : null, published_at: (published_at) ? published_at.toString() : null, - coreid: (coreid) ? coreid.toString() : null, - productid: (productid) ? productid.toString() : null + coreid: (coreid) ? coreid.toString() : null }; res.write("event: " + name + "\n"); res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies @@ -311,7 +306,8 @@ var EventsApi = { var socket = new CoreController(); name = name || ""; var productid = req.params.productIdOrSlug; - + productid = productid || null; + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); From cd60c2535e4333c5f8e729a981cfc0fe0d3c677f Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sat, 1 Oct 2016 11:45:03 +0200 Subject: [PATCH 033/504] add product device api --- js/lib/RolesController.js | 32 ++++++++++++++++++++++++++++++-- js/views/api_v1.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index a3fe378f..120f6c64 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -326,6 +326,34 @@ RolesController.prototype = { } return tmp.promise; }, + addProductDevice: function (deviceId, productid) { + var tmp = when.defer(); + try { + var productObj = this.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, deviceId); + if (index == -1) { //if not present + productObj.devices.push(deviceId); + this.saveProduct(productObj); + + //add deviceId to products array + var orgObj = this.getOrgByProductid(productObj.product_id); + for (var i = 0; i < this.products[orgObj.slug].length; i++) { + var product = this.products[orgObj.slug][i]; + if (product.product_id == productid || product.slug == productid) { + this.products[orgObj.slug][i].devices.push(deviceId); + } + } + tmp.resolve(); + } else { + tmp.reject('Device already present for that product'); + } + } + catch (ex) { + logger.error("Error adding device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, removeProductDevice: function (deviceId, productid) { var tmp = when.defer(); try { @@ -487,8 +515,8 @@ RolesController.prototype = { getUserByClaimCode: function (claimCode) { return this.usersByClaimCode[claimCode]; }, - getOrgByProductid: function (product) { //ok - return this.orgsByProduct[product]; + getOrgByProductid: function (productid) { //ok + return this.orgsByProduct[productid]; }, getOrgByUserid: function (user_id) { //ok return this.orgsByUserId[user_id]; diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 0fc86d6a..a3b2b6af 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -75,6 +75,8 @@ var Api = { app.post('/v1/products/:productIdOrSlug/device_claims', app.oauth.authenticate(), Api.get_product_claim_code); app.delete('/v1/products/:productIdOrSlug/devices/:coreid', app.oauth.authenticate(), Api.release_product_device); app.get('/v1/products/:productIdOrSlug/customers', app.oauth.authenticate(), Api.get_product_customers); + + app.post('/v1/products/:productIdOrSlug/devices', app.oauth.authenticate(), Api.add_product_device); }, getSocketID: function (userID) { @@ -1140,6 +1142,34 @@ var Api = { res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); } }, + + add_product_device: function (req, res, next) { + var coreID = req.body.id; + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + var productid = req.params.productIdOrSlug; + logger.log("AddingProductDevice", { productID: productid }); + + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + when(global.roles.addProductDevice(coreID, productid)).then( + function () { + res.sendStatus(204); + }, function (err) { + res.status(400).json({ + "code": 400, + "ok": false, + "info": "Device already present for that product" + }); + } + ); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); + } + }, _: null }; From 6256d6ba4ff7999ca44f5a134b4926455f47227d Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Sat, 1 Oct 2016 11:57:09 +0200 Subject: [PATCH 034/504] fix response in products api --- js/lib/RolesController.js | 8 -------- js/views/api_v1.js | 7 +++++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 120f6c64..76a75711 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -335,14 +335,6 @@ RolesController.prototype = { productObj.devices.push(deviceId); this.saveProduct(productObj); - //add deviceId to products array - var orgObj = this.getOrgByProductid(productObj.product_id); - for (var i = 0; i < this.products[orgObj.slug].length; i++) { - var product = this.products[orgObj.slug][i]; - if (product.product_id == productid || product.slug == productid) { - this.products[orgObj.slug][i].devices.push(deviceId); - } - } tmp.resolve(); } else { tmp.reject('Device already present for that product'); diff --git a/js/views/api_v1.js b/js/views/api_v1.js index a3b2b6af..1f5d200b 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -1083,7 +1083,7 @@ var Api = { when(global.roles.removeProductDevice(coreID, productid)).then( function () { global.server.setCoreAttribute(coreID, "claimed", false); - res.status(204).json(); + res.json({ ok:true }); }, function (err) { res.status(400).json({ "code": 400, @@ -1145,6 +1145,9 @@ var Api = { add_product_device: function (req, res, next) { var coreID = req.body.id; + if(!coreID) { + res.status(400).json({ ok: false, errors: [ 'id is required.' ] }); + } var userid = Api.getUserID(req); if(!userid) { return next(); @@ -1157,7 +1160,7 @@ var Api = { if(orgObj && orgObj.user_id == userid) { when(global.roles.addProductDevice(coreID, productid)).then( function () { - res.sendStatus(204); + res.json({ ok:true }); }, function (err) { res.status(400).json({ "code": 400, From af2a77d0e0279eaa5fe953e88dace9f1294173ef Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Wed, 5 Oct 2016 17:28:34 +0200 Subject: [PATCH 035/504] add info in devices list --- js/views/api_v1.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 1f5d200b..52041b54 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -177,6 +177,8 @@ var Api = { id: coreid, name: core ? core.name : null, last_app: core ? core.last_flashed_app_name : null, + product_id: core ? core.spark_product_id : null, + firmware_version: core ? core.product_firmware_version : null, last_heard: null }; From 2e5de06e1de02e059b7e1ce61cb26bd6a2a1c8ac Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Fri, 7 Oct 2016 12:35:19 +0200 Subject: [PATCH 036/504] private api safe mode --- js/main.js | 2 +- js/views/api_v1.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/js/main.js b/js/main.js index 0162fe07..01e79a01 100644 --- a/js/main.js +++ b/js/main.js @@ -104,7 +104,7 @@ app.use(function (req, res, next) { }); -var node_port = process.env.NODE_PORT || '8080'; +var node_port = process.env.NODE_PORT || '9000'; node_port = parseInt(node_port); console.log("Starting server, listening on " + node_port); diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 52041b54..e4c0b243 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -595,6 +595,15 @@ var Api = { } }, + safeMode: function (coreID, description) { + + logger.log("Device is in SAFE MODE", {coreID: coreID}); + + //TODO something + + global.server.publishSpecialEvents('spark/status/safe-mode', description, coreID); + }, + loadCore: function (req, res, next) { req.coreID = req.params.coreid || req.body.id; From 96aa20b32b2d9dbebd7a97c25d776e4fcd3302ec Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Thu, 13 Oct 2016 11:21:21 +0200 Subject: [PATCH 037/504] system_version - add system_version in core attributes - changes the way device is online --- js/views/api_v1.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index e4c0b243..7fbcc92b 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -179,6 +179,7 @@ var Api = { last_app: core ? core.last_flashed_app_name : null, product_id: core ? core.spark_product_id : null, firmware_version: core ? core.product_firmware_version : null, + system_version: core ? core.spark_system_version : null, last_heard: null }; @@ -230,6 +231,9 @@ var Api = { }, function () { return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); + }, + function () { + return when.resolve(Api.isDeviceOnline(userid, coreID)); } ]); @@ -237,7 +241,7 @@ var Api = { when(objReady).done(function (results) { try { - if (!results || (results.length != 2)) { + if (!results || (results.length != 3)) { logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); res.status(404).json("Oops, I couldn't find that core"); return; @@ -247,7 +251,8 @@ var Api = { //we're expecting descResult to be an array: [ sender, {} ] var doc = results[0], descResult = results[1], - coreState = null; + coreState = null, + connected = null; if (!doc || !doc.coreID) { logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); @@ -261,12 +266,16 @@ var Api = { if (!coreState) { logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); } + if (results[2].state) { + connected = ('rejected' !== results[2].state); + } var device = { id: doc.coreID, name: doc.name || null, last_app: doc.last_flashed, - connected: !!coreState, + //connected: !!coreState, + connected: !!connected, variables: (coreState) ? coreState.v : null, functions: (coreState) ? coreState.f : null, cc3000_patch_version: doc.cc3000_driver_version @@ -598,10 +607,11 @@ var Api = { safeMode: function (coreID, description) { logger.log("Device is in SAFE MODE", {coreID: coreID}); - - //TODO something - + global.server.publishSpecialEvents('spark/status/safe-mode', description, coreID); + + //# spark/safe-mode-updater/updating + //{"name":"spark/safe-mode-updater/updating","data":"2","ttl":"60","published_at":"2016-01-01T14:41:0.000Z","coreid":"particle-internal"} }, loadCore: function (req, res, next) { From 2ba8734643865356668e65dd651f027e18888576 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Thu, 13 Oct 2016 11:41:01 +0200 Subject: [PATCH 038/504] fix get core device online --- js/views/api_v1.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 7fbcc92b..ba08ee5b 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -252,7 +252,7 @@ var Api = { var doc = results[0], descResult = results[1], coreState = null, - connected = null; + connected = results[2].online || null; if (!doc || !doc.coreID) { logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); @@ -266,14 +266,14 @@ var Api = { if (!coreState) { logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); } - if (results[2].state) { - connected = ('rejected' !== results[2].state); - } var device = { id: doc.coreID, name: doc.name || null, last_app: doc.last_flashed, + product_id: doc.spark_product_id || null, + firmware_version: doc.product_firmware_version || null, + system_version: doc.spark_system_version || null, //connected: !!coreState, connected: !!connected, variables: (coreState) ? coreState.v : null, From 5c9d30acca5e884346a537c61add52f6eb19043b Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Thu, 13 Oct 2016 16:44:06 +0200 Subject: [PATCH 039/504] fix api --- js/views/api_v1.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index ba08ee5b..ff2b9bbf 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -231,9 +231,6 @@ var Api = { }, function () { return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); - }, - function () { - return when.resolve(Api.isDeviceOnline(userid, coreID)); } ]); @@ -241,7 +238,7 @@ var Api = { when(objReady).done(function (results) { try { - if (!results || (results.length != 3)) { + if (!results || (results.length != 2)) { logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); res.status(404).json("Oops, I couldn't find that core"); return; @@ -251,8 +248,7 @@ var Api = { //we're expecting descResult to be an array: [ sender, {} ] var doc = results[0], descResult = results[1], - coreState = null, - connected = results[2].online || null; + coreState = null; if (!doc || !doc.coreID) { logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); @@ -266,7 +262,7 @@ var Api = { if (!coreState) { logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); } - + var device = { id: doc.coreID, name: doc.name || null, @@ -274,8 +270,7 @@ var Api = { product_id: doc.spark_product_id || null, firmware_version: doc.product_firmware_version || null, system_version: doc.spark_system_version || null, - //connected: !!coreState, - connected: !!connected, + connected: !!coreState, variables: (coreState) ? coreState.v : null, functions: (coreState) ? coreState.f : null, cc3000_patch_version: doc.cc3000_driver_version From 074a835d3a6835a9fde1fbb3089ad5ae858d3b78 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Thu, 13 Oct 2016 18:16:58 +0200 Subject: [PATCH 040/504] change core request timeout settings --- js/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/settings.js b/js/settings.js index 5da94ad1..8b80a958 100644 --- a/js/settings.js +++ b/js/settings.js @@ -23,7 +23,7 @@ module.exports = { userDataDir: path.join(__dirname, "users"), coreKeysDir: path.join(__dirname, "core_keys"), - coreRequestTimeout: 30000, + coreRequestTimeout: 3000, isCoreOnlineTimeout: 2000, coreSignalTimeout: 30000, From 45bdba71f8d26e5ce690a72eceb5de956bf17cfe Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Fri, 14 Oct 2016 00:00:56 +0200 Subject: [PATCH 041/504] fix get core device online --- js/views/api_v1.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index ff2b9bbf..955e999b 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -231,6 +231,9 @@ var Api = { }, function () { return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); + }, + function () { + return when.resolve(Api.isDeviceOnline(userid, coreID)); } ]); @@ -238,7 +241,7 @@ var Api = { when(objReady).done(function (results) { try { - if (!results || (results.length != 2)) { + if (!results || (results.length != 3)) { logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); res.status(404).json("Oops, I couldn't find that core"); return; @@ -248,7 +251,8 @@ var Api = { //we're expecting descResult to be an array: [ sender, {} ] var doc = results[0], descResult = results[1], - coreState = null; + coreState = null, + connected = results[2].online; if (!doc || !doc.coreID) { logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); @@ -270,7 +274,8 @@ var Api = { product_id: doc.spark_product_id || null, firmware_version: doc.product_firmware_version || null, system_version: doc.spark_system_version || null, - connected: !!coreState, + //connected: !!coreState, + connected: connected, variables: (coreState) ? coreState.v : null, functions: (coreState) ? coreState.f : null, cc3000_patch_version: doc.cc3000_driver_version From 2dc440e49dea45907295a7319c20f8e489c59793 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Mon, 17 Oct 2016 14:47:06 +0200 Subject: [PATCH 042/504] another way to get core device online --- js/views/api_v1.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 955e999b..38632142 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -195,7 +195,7 @@ var Api = { //switched 'done' to 'then' - threw an exception with 'done' here. when.settle(connected_promises).then(function (descriptors) { - for (var i = 0; i < descriptors.length; i++) { + for (var i = 0; i < descriptors.length; i++) { var desc = descriptors[i]; devices[i].connected = ('rejected' !== desc.state); devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; @@ -233,7 +233,7 @@ var Api = { return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); }, function () { - return when.resolve(Api.isDeviceOnline(userid, coreID)); + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Ping" }, { cmd: "Pong" })); } ]); @@ -247,12 +247,11 @@ var Api = { return; } - //we're expecting descResult to be an array: [ sender, {} ] var doc = results[0], descResult = results[1], coreState = null, - connected = results[2].online; + descPingResult = results[2]; if (!doc || !doc.coreID) { logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); @@ -275,7 +274,7 @@ var Api = { firmware_version: doc.product_firmware_version || null, system_version: doc.spark_system_version || null, //connected: !!coreState, - connected: connected, + connected: (descPingResult) ? descPingResult[1].online : false, variables: (coreState) ? coreState.v : null, functions: (coreState) ? coreState.f : null, cc3000_patch_version: doc.cc3000_driver_version From 975a72f6a08f9a80dade2e7dbff6f9a31178f8cb Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Tue, 18 Oct 2016 00:21:21 +0200 Subject: [PATCH 043/504] change customer creation api --- js/lib/CustomerViews.js | 8 +++++--- js/lib/RolesController.js | 7 +++++-- js/views/api_v1.js | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/js/lib/CustomerViews.js b/js/lib/CustomerViews.js index 7da0e286..7ed797c8 100644 --- a/js/lib/CustomerViews.js +++ b/js/lib/CustomerViews.js @@ -17,7 +17,8 @@ var CustomerViews = function (options) { CustomerViews.prototype = { loadViews: function (app) { - app.post('/v1/products/:productIdOrSlug/customers', this.createCustomer.bind(this)); + //app.post('/v1/products/:productIdOrSlug/customers', this.createCustomer.bind(this)); + app.post('/v1/orgs/:orgSlug/customers', this.createCustomer.bind(this)); }, createCustomer: function (req, res, next) { @@ -29,7 +30,8 @@ CustomerViews.prototype = { }); } - var product = req.params.productIdOrSlug; + //var product = req.params.productIdOrSlug; + var orgSlug = req.params.orgSlug; var email = req.body.email; if(!email){ @@ -42,7 +44,7 @@ CustomerViews.prototype = { when(roles.validateClient(credentials.clientId, credentials.clientSecret)) .then( function (clientObj) { - when(roles.createCustomer(clientObj, product, email)) + when(roles.createCustomer(clientObj, /*product,*/ orgSlug, email)) .then( function () { res.json({ok: true}); diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 76a75711..b8fb7f34 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -510,6 +510,9 @@ RolesController.prototype = { getOrgByProductid: function (productid) { //ok return this.orgsByProduct[productid]; }, + getOrgBySlug: function (orgSlug) { //ok + return this.orgsBySlug[orgSlug]; + }, getOrgByUserid: function (user_id) { //ok return this.orgsByUserId[user_id]; }, @@ -676,11 +679,11 @@ RolesController.prototype = { return tmp.promise; }, - createCustomer: function (clientObj, product, email) { + createCustomer: function (clientObj, /*product,*/ orgSlug, email) { var tmp = when.defer(); var that = this; - var orgObj = that.getOrgByProductid(product); + var orgObj = that.getOrgBySlug(orgSlug); if(orgObj && orgObj.slug == clientObj.client_id) { var customer = that.getCustomerByEmail(email); if(!customer) { diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 38632142..aa3b0e28 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -274,7 +274,7 @@ var Api = { firmware_version: doc.product_firmware_version || null, system_version: doc.spark_system_version || null, //connected: !!coreState, - connected: (descPingResult) ? descPingResult[1].online : false, + connected: (descPingResult != "Request Timed Out") ? descPingResult[1].online : false, variables: (coreState) ? coreState.v : null, functions: (coreState) ? coreState.f : null, cc3000_patch_version: doc.cc3000_driver_version From 5df143e29cb31ef01050d1688500bb76d4deac10 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Tue, 18 Oct 2016 18:16:44 +0200 Subject: [PATCH 044/504] fix last_heard in get core attributes --- js/views/api_v1.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/views/api_v1.js b/js/views/api_v1.js index aa3b0e28..84ccb806 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -275,6 +275,7 @@ var Api = { system_version: doc.spark_system_version || null, //connected: !!coreState, connected: (descPingResult != "Request Timed Out") ? descPingResult[1].online : false, + last_heard: (descPingResult != "Request Timed Out") ? descPingResult[1].lastPing : null, variables: (coreState) ? coreState.v : null, functions: (coreState) ? coreState.f : null, cc3000_patch_version: doc.cc3000_driver_version From 092f3076051d29ba169e7061595069bce7387392 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Wed, 2 Nov 2016 18:54:00 +0100 Subject: [PATCH 045/504] enable unclaim for customer --- js/lib/RolesController.js | 8 ++++++-- js/views/api_v1.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index b8fb7f34..44920bdd 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -313,8 +313,12 @@ RolesController.prototype = { } delete this.usersByDevice[deviceId]; - - this.saveUser(userObj); + + if(userObj.org) { //customer + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } tmp.resolve(); } else { tmp.reject('Device not found'); diff --git a/js/views/api_v1.js b/js/views/api_v1.js index 84ccb806..ec3f3244 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -539,7 +539,7 @@ var Api = { release_device: function (req, res, next) { var coreID = req.coreID; - var userid = Api.getUserID(req); + var userid = Api.getUserOrCustomerID(req); if(!userid) { return next(); } From 7a574ea8cfe3e4d8b8e21a7c8c7180bb72ce3b82 Mon Sep 17 00:00:00 2001 From: Andrea Taffi Date: Wed, 2 Nov 2016 20:40:09 +0100 Subject: [PATCH 046/504] fix get customers and unclaim product device --- js/lib/RolesController.js | 4 ++-- js/views/api_v1.js | 35 ++++++++++++++--------------------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/js/lib/RolesController.js b/js/lib/RolesController.js index 44920bdd..a8a90740 100644 --- a/js/lib/RolesController.js +++ b/js/lib/RolesController.js @@ -356,8 +356,8 @@ RolesController.prototype = { var productObj = this.getProductByProductid(productid); var index = utilities.indexOf(productObj.devices, deviceId); if (index > -1) { - productObj.devices.splice(index, 1); - this.saveProduct(productObj); + /*productObj.devices.splice(index, 1); + this.saveProduct(productObj);*/ delete this.usersByDevice[deviceId]; diff --git a/js/views/api_v1.js b/js/views/api_v1.js index ec3f3244..d1b093b6 100644 --- a/js/views/api_v1.js +++ b/js/views/api_v1.js @@ -539,7 +539,7 @@ var Api = { release_device: function (req, res, next) { var coreID = req.coreID; - var userid = Api.getUserOrCustomerID(req); + var userid = Api.getUserID(req); if(!userid) { return next(); } @@ -1133,29 +1133,22 @@ var Api = { var orgObj = global.roles.getOrgByProductid(productid); if(orgObj && orgObj.user_id == userid) { var customerObjs = []; + var userObjIds = []; var devices = []; for (var k = 0; k < productDevices.length; k++) { var deviceid = productDevices[k]; - var customerObj = global.roles.getUserByDevice(deviceid); - if(customerObj && customerObj.org) { //if customer - customerObjs.push({ - id: customerObj._id, - email: customerObj.email, - devices: customerObj.devices - }); - - var core = global.server.getCoreAttributes(deviceid); - - var device = { - id: deviceid, - name: core ? core.name : null, - last_ip_address: core.last_ip_address, - product_id: core.product_id - }; - - //miss device status - - devices.push(device); + var userObj = global.roles.getUserByDevice(deviceid); + if(userObj && userObj.org) { //if customer + devices.push(deviceid); + var index = utilities.indexOf(userObjIds, userObj._id); + if (index == -1) { + userObjIds.push(userObj._id); + customerObjs.push({ + id: userObj._id, + email: userObj.email, + devices: userObj.devices + }); + } } } res.json({ customers : customerObjs, devices : devices }); From 5e0958b82956271b5d273ba87c136ef45a509f49 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 23 Nov 2016 08:10:51 -0800 Subject: [PATCH 047/504] Adding correct `spark-protocol` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 41457c07..ed9b2772 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "url": "https://www.spark.io/" }, "dependencies": { - "spark-protocol": "*", + "spark-protocol": "https://github.com/Brewskey/spark-protocol", "express": "~3.4.4", "hogan-express": "~0.5.1", "request": "*", From 559bd02803e58d86d5db924654c3f2e8a5aa1fd2 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 24 Nov 2016 08:41:53 -0800 Subject: [PATCH 048/504] Cleanup --- js/lib/AccessTokenViews.js | 108 -- js/lib/CoreController.js | 218 ---- js/lib/RolesController.js | 728 ------------- js/lib/utilities.js | 391 ------- js/oauth_clients.json | 7 +- js/package.json | 37 - js/views/EventViews001.js | 339 ------ js/views/api_v1.js | 1194 --------------------- lib/AccessTokenViews.js | 128 +-- lib/CoreController.js | 289 +++-- {js/lib => lib}/CustomerViews.js | 0 lib/RolesController.js | 850 ++++++++++++--- lib/utilities.js | 717 +++++++------ oauth_clients.json | 12 + {js/orgs => orgs}/company.json | 0 package.json | 85 +- views/EventViews001.js | 516 +++++---- views/api_v1.js | 1724 +++++++++++++++++++----------- 18 files changed, 2711 insertions(+), 4632 deletions(-) delete mode 100644 js/lib/AccessTokenViews.js delete mode 100644 js/lib/CoreController.js delete mode 100644 js/lib/RolesController.js delete mode 100644 js/lib/utilities.js delete mode 100644 js/package.json delete mode 100644 js/views/EventViews001.js delete mode 100644 js/views/api_v1.js rename {js/lib => lib}/CustomerViews.js (100%) create mode 100644 oauth_clients.json rename {js/orgs => orgs}/company.json (100%) diff --git a/js/lib/AccessTokenViews.js b/js/lib/AccessTokenViews.js deleted file mode 100644 index 9382e00c..00000000 --- a/js/lib/AccessTokenViews.js +++ /dev/null @@ -1,108 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var path = require('path'); -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); -var PasswordHasher = require('./PasswordHasher.js'); -var roles = require('./RolesController.js'); -var logger = require('./logger.js'); - -var AccessTokenViews = function (options) { - this.options = options; -}; - -AccessTokenViews.prototype = { - loadViews: function (app) { - app.get('/v1/access_tokens', this.index.bind(this)); - app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); - }, - index: function (req, res) { - //var credentials = AccessTokenViews.basicAuth(req); - //var credentials = this.basicAuth(req); - var credentials= AccessTokenViews.prototype.basicAuth(req); - if (!credentials) { - return res.status(401).json({ - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] - }); - } - - //if successful, should return something like: - // [ { token: d.token, expires: d.expires, client: d.client_id } ] - - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - res.json(userObj.access_tokens); - }, - function () { - res.status(401).json({ ok: false, errors: ['Bad password']}); - }); - }, - - destroy: function (req, res) { - //var credentials = AccessTokenViews.basicAuth(req); - var credentials= AccessTokenViews.prototype.basicAuth(req); - if (!credentials) { - return res.status(401).json({ - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] - }); - } - - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - try { - roles.destroyAccessToken(req.params.token); - res.json({ ok: true }); - } - catch (ex) { - logger.error("error saving user " + ex); - res.status(401).json({ ok: false, errors: ['Error updating token']}); - } - }, - function () { - res.status(401).json({ ok: false, errors: ['Bad password']}); - }); - }, - - basicAuth: function (req) { - var auth = req.get('Authorization'); - if (!auth) return null; - - var matches = auth.match(/Basic\s+(\S+)/); - if (!matches) return null; - - var creds = new Buffer(matches[1], 'base64').toString(); - var separatorIndex = creds.indexOf(':'); - if (-1 === separatorIndex) - return null; - - return { - username: creds.slice(0, separatorIndex), - password: creds.slice(separatorIndex + 1) - }; - } - -}; - -module.exports = AccessTokenViews; diff --git a/js/lib/CoreController.js b/js/lib/CoreController.js deleted file mode 100644 index c7b4d726..00000000 --- a/js/lib/CoreController.js +++ /dev/null @@ -1,218 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var when = require('when'); -var extend = require('xtend'); -var EventEmitter = require('events').EventEmitter; - -var logger = require('./logger.js'); -var settings = require("../settings"); -var utilities = require("./utilities.js"); - - -var CoreController = function (socketID) { - //this.coreID = coreID; - this.socketID = socketID; - EventEmitter.call(this); -}; - -CoreController.prototype = { - getCore: function (coreid) { - if (global.server) { - return global.server.getCore(coreid); - } - else { - logger.error("Spark-protocol server not running"); - } - }, - - - sendAndListenFor: function (recipient, msg, filter, callback, once) { - this.listenFor(recipient, filter, callback, once); - this.send(recipient, msg); - }, - - sendAndListenForDFD: function (recipient, msg, filter, failDelay, connectDelay) { - var result = when.defer(); - - failDelay = failDelay || settings.coreRequestTimeout; - var failTimer = setTimeout(function () { - result.reject("Request Timed Out"); - }, failDelay); - - var callback = function (sender, msg) { - clearTimeout(failTimer); - result.resolve([sender, msg]); - }; - - this.sendAndListenFor(recipient, msg, filter, callback, true); - return result.promise; - }, - - - /** - * send a message to a core - * @param recipient - * @param msg - */ - send: function (recipient, msg) { - var that = this; - var core = this.getCore(recipient); - if (!core || !core.onApiMessage) { - logger.error("Couldn't find that core ", recipient); - return false; - } - - process.nextTick(function () { - try { - //console.log("sending message with socketID" + that.socketID); - core.onApiMessage(that.socketID, msg); - } - catch (ex) { - logger.error("error during send: " + ex); - } - }); - return true; - }, - - /** - * starts listening for a message event with the given filter criteria - * @param filter - * @param callback - * @param once - removes the listener after we've heard back - */ - listenFor: function (recipient, filter, callback, once) { - var core = this.getCore(recipient); - if (!core || !core.on) { - logger.error("Couldn't find that core ", recipient); - return; - } - - var that = this, - handler = function (sender, msg) { - //logger.log('heard from ' + ((sender) ? sender.toString() : '(UNKNOWN)')); - - if (!utilities.leftHasRightFilter(msg, filter)) { - //logger.log('filters did not match'); - return; - } - - if (once) { - core.removeListener(that.socketID, handler); - } - - process.nextTick(function () { - try { - //logger.log('passing message to callback ', msg); - callback(sender, msg); - } - catch (ex) { - logger.error("listenFor error: " + ex, (ex) ? ex.stack : ''); - } - }); - }; - - core.on(that.socketID, handler); - }, - - subscribe: function (isPublic, name, userid,coreid) { -// if (!sock) { -// return false; -// } - - //start permitting these messages through on this socket. - global.publisher.subscribe(name,userid,coreid, this); - - return false; - }, - - unsubscribe: function (isPublic, name, userid) { - if (userid && (userid != "")) { - name = userid + "/" + name; - } - -// if (!sock) { -// return; -// } - - global.publisher.unsubscribe(name, this); - }, - - //isPublic, obj.name, obj.userid, obj.data, obj.ttl, obj.published_at - sendEvent: function (isPublic, name, userid, data, ttl, published_at, coreid) { - - if (!global.publisher) { - logger.error("Spark-protocol server not running"); - return; - } - - try { - global.publisher.publish( - isPublic, - name, - userid, - data, - ttl, - published_at, - coreid - ); - } - catch (ex) { - logger.error("sendEvent Error: " + ex); - } - - return true; - }, - - close: function () { - - } -}; - -///** -// * This should be made more efficient, this is too simplistic -// * @returns {{}} -// */ -//CoreController.listAllCores = function() { -// var files = fs.readdirSync(settings.coreKeysDir); -// var cores = []; -// -// -// -// -// -// var corelist = files.map(function(filename) { return utilities.filenameNoExt(filename); }); -// var cores = {}; -// for(var i=0;i. -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var path = require('path'); -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); -var PasswordHasher = require('./PasswordHasher.js'); -var roles = require('./RolesController.js'); -var settings = require('../settings.js'); -var logger = require('./logger.js'); -var utilities = require("./utilities.js"); - -function RolesController() { - this.init(); -}; - -RolesController.prototype = { - users: null, - usersByToken: null, - usersByDevice: null, - usersByClaimCode: null, - usersByUsername: null, - - access_tokens: null, - refresh_tokens: null, - claim_codes: null, - - devices: null, - clients: null, - - orgsBySlug: null, - orgsByUserId: null, - - customers: null, - customersByEmail: null, - //customersByToken: null, - - orgsByProduct: null, - products : null, - - init: function () { - this._loadAndCacheUsers(); - }, - - addUser: function (userObj) { - this.users.push(userObj); - this.usersByUsername[ userObj.username ] = userObj; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var token = userObj.access_tokens[i]; - this.usersByToken[token.token] = userObj; - this.access_tokens[token.token] = token; - this.refresh_tokens[token.refresh_token] = token; - } - - //claim codes - this.claim_codes[userObj._id] = userObj.claim_codes; - for (var i = 0; i < userObj.claim_codes.length; i++) { - var claimCode = userObj.claim_codes[i]; - this.usersByClaimCode[claimCode] = userObj; - } - - //devices claimed - this.devices[userObj._id] = userObj.devices; - for (var i = 0; i < userObj.devices.length; i++) { - var deviceId = userObj.devices[i]; - this.usersByDevice[ deviceId ] = userObj; - } - }, - addCustomer: function (customerObj) { - - console.log("Loading customer " + customerObj.email); - - this.customers.push(customerObj); - this.customersByEmail[ customerObj.email ] = customerObj; - - for (var k = 0; k < customerObj.access_tokens.length; k++) { - var token = customerObj.access_tokens[k]; - this.usersByToken[token.token] = customerObj; - this.access_tokens[token.token] = token; - this.refresh_tokens[token.refresh_token] = token; - } - - //claim codes - this.claim_codes[customerObj._id] = customerObj.claim_codes; - for (var i = 0; i < customerObj.claim_codes.length; i++) { - var claimCode = customerObj.claim_codes[i]; - this.usersByClaimCode[claimCode.code] = customerObj; - } - - //devices claimed - this.devices[customerObj._id] = customerObj.devices; - for (var i = 0; i < customerObj.devices.length; i++) { - var deviceId = customerObj.devices[i]; - this.usersByDevice[ deviceId ] = customerObj; - } - }, - addClient: function (clientObj) { - this.clients.push(clientObj); - }, - addOrg: function (orgObj) { - this.orgsBySlug[orgObj.slug] = orgObj; - this.orgsByUserId[orgObj.user_id] = orgObj; - - for (var i = 0; i < orgObj.customers.length; i++) { - this.addCustomer(orgObj.customers[i]); //add customer - } - - this.products[orgObj.slug] = []; //list product - for (var j = 0; j < orgObj.products.length; j++) { - this.products[orgObj.slug].push(orgObj.products[j]); - this.orgsByProduct[ orgObj.products[j].slug ] = orgObj; - this.orgsByProduct[ orgObj.products[j].product_id ] = orgObj; - } - }, - destroyAccessToken: function (access_token) { - var userObj = this.usersByToken[access_token]; - if (!userObj) { - return true; - } - - delete this.usersByToken[access_token]; - delete this.access_tokens[access_token]; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var tokenObj = userObj.access_tokens[i]; - if (tokenObj.token == access_token) { - userObj.access_tokens.splice(i, 1); - } - } - - this.saveUser(userObj); - }, - revokeToken: function (token) { - var userObj = this.usersByToken[token.accessToken]; - if (!userObj) { - return false; - } - var tokenObj = this.access_tokens[token.accessToken]; - if(!tokenObj) { - return false; - } - - delete this.usersByToken[token.accessToken]; - delete this.access_tokens[token.accessToken]; - delete this.refresh_tokens[token.refreshToken]; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var tokenObj = userObj.access_tokens[i]; - if (tokenObj.token == token.accessToken) { - userObj.access_tokens.splice(i, 1); - } - } - if(tokenObj.scope && tokenObj.scope.indexOf("customer=") > -1) { - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - return token; - }, - addAccessToken: function (token, client, user) { - var tmp = when.defer(); - try { - var tokenObj = { - //user_id: user._id, - token: token.accessToken, - expires_at: token.accessTokenExpiresAt, - client: client.client_id, - refresh_token: token.refreshToken, - scope: token.scope - }; - - if(token.scope && token.scope.indexOf("customer=") > -1) { - //is a customer token - var email = token.scope.split("=")[1]; - var customerObj = this.customersByEmail[email]; - - this.usersByToken[token.accessToken] = customerObj; - this.access_tokens[token.accessToken] = tokenObj; - this.refresh_tokens[token.refreshToken] = tokenObj; - customerObj.access_tokens.push(tokenObj); - this.saveCustomer(customerObj); - - tmp.resolve({ - accessToken: token.accessToken, - client: client, - refreshToken: token.refreshToken, - user: customerObj._id, - scope: token.scope, - accessTokenExpiresAt: token.accessTokenExpiresAt - }); - - } else { - var userObj = this.getUserByUserid(user._id); - if(!userObj) { - //refresh_token - userObj = this.getUserByUserid(user); - } - this.usersByToken[token.accessToken] = userObj; - - this.access_tokens[token.accessToken] = tokenObj; - this.refresh_tokens[token.refreshToken] = tokenObj; - userObj.access_tokens.push(tokenObj); - this.saveUser(userObj); - - tmp.resolve({ - accessToken: token.accessToken, - client: client, - user: userObj._id, - refreshToken: token.refreshToken, - scope: token.scope, - accessTokenExpiresAt: token.accessTokenExpiresAt - }); - } - } - catch (ex) { - logger.error("Error adding access token ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addClaimCode: function (claimCode, userId) { - var tmp = when.defer(); - try { - var userObj = this.getUserByUserid(userId); - - userObj.claim_codes.push(claimCode); - this.saveUser(userObj); - - this.usersByClaimCode[ claimCode ] = userObj; - - tmp.resolve(); - } - catch (ex) { - logger.error("Error adding claim code ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addProductClaimCode: function (claimCode, customerId, productId) { - var tmp = when.defer(); - try { - var claimCodeObj = { - code : claimCode, - product_id : productId - } - - var customerObj = this.getCustomerByCustomerid(customerId); - customerObj.claim_codes.push(claimCodeObj); - this.saveCustomer(customerObj); - - this.usersByClaimCode[ claimCode ] = customerObj; - - tmp.resolve(); - } - catch (ex) { - logger.error("Error adding claim code ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addDevice: function (deviceId, userObj) { - var tmp = when.defer(); - try { - if(!this.usersByDevice[deviceId]) { - userObj.devices.push(deviceId); - if(userObj.org) { //customer - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - this.usersByDevice[ deviceId ] = userObj; - - tmp.resolve(); - } else if(this.usersByDevice[deviceId] == userObj) { - tmp.resolve(); - } else { - tmp.reject("already claimed"); - } - } - catch (ex) { - logger.error("Error adding device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - removeDevice: function (deviceId, userId) { - var tmp = when.defer(); - try { - var userObj = this.getUserByUserid(userId); - - if(this.usersByDevice[deviceId]) { - var index = utilities.indexOf(userObj.devices, deviceId); - if (index > -1) { - userObj.devices.splice(index, 1); - } - - delete this.usersByDevice[deviceId]; - - if(userObj.org) { //customer - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - tmp.resolve(); - } else { - tmp.reject('Device not found'); - } - } - catch (ex) { - logger.error("Error releasing device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addProductDevice: function (deviceId, productid) { - var tmp = when.defer(); - try { - var productObj = this.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, deviceId); - if (index == -1) { //if not present - productObj.devices.push(deviceId); - this.saveProduct(productObj); - - tmp.resolve(); - } else { - tmp.reject('Device already present for that product'); - } - } - catch (ex) { - logger.error("Error adding device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - removeProductDevice: function (deviceId, productid) { - var tmp = when.defer(); - try { - var productObj = this.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, deviceId); - if (index > -1) { - /*productObj.devices.splice(index, 1); - this.saveProduct(productObj);*/ - - delete this.usersByDevice[deviceId]; - - var orgObj = this.getOrgByProductid(productObj.product_id); - for (var i = 0; i < orgObj.customers.length; i++) { - var index = utilities.indexOf(orgObj.customers[i].devices, deviceId); - if (index > -1) { - orgObj.customers[i].devices.splice(index, 1); - this.saveCustomer(orgObj.customers[i]); - } - } - - tmp.resolve(); - } else { - tmp.reject('Device not found for product'); - } - } - catch (ex) { - logger.error("Error releasing device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - saveUser: function (userObj) { - var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; - var userJson = JSON.stringify(userObj, null, 2); - fs.writeFileSync(userFile, userJson); - }, - saveCustomer: function (customerObj) { - var orgObj = this.orgsBySlug[customerObj.org]; - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - var index = 0; - for (var i = 0; i < orgObj.customers.length; i++) { - var customer = orgObj.customers[i]; - if (customer._id == customerObj._id) { - orgObj.customers[i] = customerObj; - } - } - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); - }, - saveProduct: function (productObj) { - var orgObj = this.orgsByProduct[productObj.slug]; - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - var index = 0; - for (var i = 0; i < orgObj.products.length; i++) { - var product = orgObj.products[i]; - if (product.id == productObj.id) { - orgObj.products[i] = productObj; - } - } - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); - }, - - _loadAndCacheUsers: function () { - this.users = []; - this.usersByToken = {}; - this.usersByDevice = {}; - this.usersByUsername = {}; - this.usersByClaimCode = {}; - - this.access_tokens = {}; - this.refresh_tokens = {}; - this.claim_codes = {}; - - this.devices = []; - this.clients = []; - this.orgsBySlug = {}; - this.orgsByUserId = {}; - - this.customers = []; - this.customersByEmail = {}; - //this.customersByToken = {}; - - this.orgsByProduct = {}; - this.products = {}; - - // list files, load all user objects, index by access_tokens and usernames - // and devices - if (!fs.existsSync(settings.userDataDir)) { - fs.mkdirSync(settings.userDataDir); - } - - var files = fs.readdirSync(settings.userDataDir); - if (!files || (files.length == 0)) { - logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); - } - - for (var i = 0; i < files.length; i++) { - try { - - var filename = path.join(settings.userDataDir, files[i]); - var userObj = JSON.parse(fs.readFileSync(filename)); - - console.log("Loading user " + userObj.username); - this.addUser(userObj); - } - catch (ex) { - logger.error("RolesController - error loading user at " + filename); - } - } - - var filenameClient = settings.oauthClientsFile; - var clients = JSON.parse(fs.readFileSync(filenameClient)); - for (var j = 0; j < clients.length; j++) { - try { - console.log("Loading client " + clients[j].client_id); - this.addClient(clients[j]); - } - catch (ex) { - logger.error("RolesController - error loading client at " + filenameOrg); - } - } - - var filesOrg = fs.readdirSync(settings.orgDataDir); - - for (var k = 0; k < filesOrg.length; k++) { - try { - - var filenameOrg = path.join(settings.orgDataDir, filesOrg[k]); - var orgObj = JSON.parse(fs.readFileSync(filenameOrg)); - - console.log("Loading org " + orgObj.slug); - this.addOrg(orgObj); - } - catch (ex) { - logger.error("RolesController - error loading org at " + filenameOrg); - } - } - }, - - getClient: function ( clientId, clientSecret) { - var clientObj = this.getClientByClientid(clientId); - if (clientObj.client_secret == clientSecret || clientId == 'particle') { - return clientObj; - } - return false; - }, - - getUserByClient: function ( clientId ) { - return this.getUserByUserid(this.orgsBySlug[clientId].user_id); - }, - - getUserByToken: function (access_token) { - return this.usersByToken[access_token]; - }, - getUserByDevice: function (deviceId) { - return this.usersByDevice[deviceId]; - }, - getUserByClaimCode: function (claimCode) { - return this.usersByClaimCode[claimCode]; - }, - getOrgByProductid: function (productid) { //ok - return this.orgsByProduct[productid]; - }, - getOrgBySlug: function (orgSlug) { //ok - return this.orgsBySlug[orgSlug]; - }, - getOrgByUserid: function (user_id) { //ok - return this.orgsByUserId[user_id]; - }, - getCustomerByEmail: function (email) { - return this.customersByEmail[email]; - }, - - getUserByName: function (username) { - return this.usersByUsername[username]; - }, - getTokenInfoByAccessToken: function (token) { - var tokenObj = this.access_tokens[token]; - if(!tokenObj) { - return false; - } - return { - accessToken: tokenObj.token, - client: tokenObj.client, - user: this.getUserByToken(tokenObj.token)._id, - refreshToken: tokenObj.refresh_token, - accessTokenExpiresAt: new Date(tokenObj.expires_at), - scope: tokenObj.scope - }; - }, - getTokenInfoByRefreshToken: function (token) { - var tokenObj = this.refresh_tokens[token]; - if(!tokenObj) { - return false; - } - return { - accessToken: tokenObj.token, - client: tokenObj.client, - user: this.getUserByToken(tokenObj.token)._id, - refreshToken: tokenObj.refresh_token, - refreshTokenExpiresAt: new Date(tokenObj.expires_at), - //refreshTokenExpiresAt not managed return accessTokenExpires - scope: tokenObj.scope - }; - }, - getUserByUserid: function (userid) { - for (var i = 0; i < this.users.length; i++) { - var user = this.users[i]; - if (user._id == userid) { - return user; - } - } - return null; - }, - - getProductByProductid: function (productid) { - var orgObj = this.orgsByProduct[productid]; - for (var i = 0; i < this.products[orgObj.slug].length; i++) { - var product = this.products[orgObj.slug][i]; - if (product.product_id == productid || product.slug == productid) { - return product; - } - } - return null; - }, - - getProductByUserid: function (userid) { - var orgObj = this.orgsByProduct[productid]; - for (var i = 0; i < this.products[orgObj.slug].length; i++) { - var product = this.products[orgObj.slug][i]; - if (product.product_id == productid || product.slug == productid) { - return product; - } - } - return null; - }, - - getCustomerByCustomerid: function (customerid) { - for (var i = 0; i < this.customers.length; i++) { - var customer = this.customers[i]; - if (customer._id == customerid) { - return customer; - } - } - return null; - }, - - getClientByClientid: function (clientId) { - for (var i = 0; i < this.clients.length; i++) { - var client = this.clients[i]; - if (client.client_id == clientId) { - return client; - } - } - return null; - }, - - validateHashPromise: function (user, password) { - var tmp = when.defer(); - - PasswordHasher.hash(password, user.salt, function (err, hash) { - if (err) { - logger.error("hash error " + err); - tmp.reject("Bad password"); - } - else if (hash === user.password_hash) { - tmp.resolve(user); - } - else { - tmp.reject("Bad password"); - } - }); - - return tmp.promise; - }, - - validateLogin: function (username, password) { - var userObj = this.getUserByName(username); - if (!userObj) { - return when.reject("Bad password"); - } - - return this.validateHashPromise(userObj, password); - }, - - validateClient: function (clientId, clientSecret) { - var tmp = when.defer(); - - var clientObj = this.getClient(clientId, clientSecret); - if (!clientObj) { - return tmp.reject("Bad client"); - } - - tmp.resolve(clientObj); - - return tmp.promise; - }, - - createUser: function (username, password) { - var tmp = when.defer(); - var that = this; - - PasswordHasher.generateSalt(function (err, userid) { - userid = userid.toString('base64'); - userid = userid.substring(0, 32); - - PasswordHasher.generateSalt(function (err, salt) { - salt = salt.toString('base64'); - PasswordHasher.hash(password, salt, function (err, hash) { - var user = { - _id: userid, - username: username, - password_hash: hash, - salt: salt, - access_tokens: [], - claim_codes: [], - devices: [] - }; - - var userFile = path.join(settings.userDataDir, username + ".json"); - fs.writeFileSync(userFile, JSON.stringify(user)); - - that.addUser(user); - - tmp.resolve(); - }); - }); - }); - - return tmp.promise; - }, - - createCustomer: function (clientObj, /*product,*/ orgSlug, email) { - var tmp = when.defer(); - var that = this; - - var orgObj = that.getOrgBySlug(orgSlug); - if(orgObj && orgObj.slug == clientObj.client_id) { - var customer = that.getCustomerByEmail(email); - if(!customer) { - - PasswordHasher.generateSalt(function (err, customerid) { - customerid = customerid.toString('base64'); - customerid = customerid.substring(0, 32); - - var customer = { - _id: customerid, - email: email, - org: orgObj.slug, - access_tokens: [], - claim_codes: [], - devices: [] - }; - - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - orgObj.customers.push(customer); - - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); - - that.addCustomer(customer); - - tmp.resolve(); - }); - } else { - tmp.reject('Customer '+email+' already exists'); - } - } else { - tmp.reject('Bad product'); - } - - return tmp.promise; - } -}; -module.exports = global.roles = new RolesController(); \ No newline at end of file diff --git a/js/lib/utilities.js b/js/lib/utilities.js deleted file mode 100644 index 683539cf..00000000 --- a/js/lib/utilities.js +++ /dev/null @@ -1,391 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -*/ - -var os = require('os'); -var when = require('when'); -var logger = require('./logger.js'); -var extend = require('xtend'); -var path = require('path'); -var fs = require('fs'); - -var that; -module.exports = that = { - - /** - * ensures the function in the provided scope - * @param fn - * @param scope - * @returns {Function} - */ - proxy: function (fn, scope) { - return function () { - try { - return fn.apply(scope, arguments); - } - catch (ex) { - logger.error(ex); - logger.error(ex.stack); - logger.log('error bubbled up ' + ex); - } - } - }, - - /** - * Surely there is a better way to do this. - * NOTE! This function does NOT short-circuit when an in-equality is detected. This is - * to avoid timing attacks. - * @param left - * @param right - */ - bufferCompare: function (left, right) { - if ((left == null) && (right == null)) { - return true; - } - else if ((left == null) || (right == null)) { - return false; - } - - if (!Buffer.isBuffer(left)) { - left = new Buffer(left); - } - if (!Buffer.isBuffer(right)) { - right = new Buffer(right); - } - - logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); - - var same = (left.length == right.length), - i = 0, - max = left.length; - - while (i < max) { - same &= (left[i] == right[i]); - i++; - } - - return same; - }, - - /** - * Iterates over the properties of the right object, checking to make - * sure the properties on the left object match. - * @param left - * @param right - */ - leftHasRightFilter: function (left, right) { - if (!left && !right) { - return true; - } - var matches = true; - - for (var prop in right) { - if (!right.hasOwnProperty(prop)) { - continue; - } - matches &= (left[prop] == right[prop]); - } - return matches; - }, - - promiseDoFile: function (filename, callback) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - if (callback(data)) { - deferred.resolve(); - } - } - catch(ex) { - deferred.reject(ex); - } - - }); - } - }); - return deferred; - }, - - promiseGetJsonFile: function (filename) { - var deferred = when.defer(); - - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - var obj = JSON.parse(data); - deferred.resolve(obj); - } - catch(ex) { - logger.error("Error parsing " + filename + " " + ex); - deferred.reject(ex); - } - }); - } - }); - return deferred; - }, - - promiseStreamFile: function (filename) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - var readStream = fs.createReadStream(filename); - - //TODO: catch can't read file stuff. - - deferred.resolve(readStream); - } - }); - return deferred; - }, - - bufferToHexString: function(buf) { - if (!buf || (buf.length <= 0)) { return null; } - - var r = []; - for(var i=0;i= 0) { - return filename.substr(idx); - } - else { - return filename; - } - }, - filenameNoExt: function (filename) { - if (!filename || (filename.length === 0)) { - return filename; - } - - var idx = filename.lastIndexOf('.'); - if (idx >= 0) { - return filename.substr(0, idx); - } - else { - return filename; - } - }, - - indexOf: function (arr, val) { - if (!arr || (arr.length == 0)) { - return -1; - } - for (var i = 0; i < arr.length; i++) { - if (arr[i] == val) { - return i; - } - } - return -1; - }, - contains: function (arr, val) { - return (that.indexOf(arr, val) !== -1); - }, - pipeDeferred: function(left, right) { - when(left).then(function() { - right.resolve.apply(right, arguments); - }, function() { - right.reject.apply(right, arguments); - }) - }, - - /** - * Non-competitive version of when.any - * @param arr - */ - deferredAny: function (arr) { - var tmp = when.defer(); - var index = -1; - var reasons = []; - - //step through a list of FUNCTIONS that return deferreds, - //process in order and resolve with the first one that resolves. - - var doNext = function () { - index++; - if (index > arr.length) { - tmp.reject(reasons); - return; - } - else if (!arr[index]) { - process.nextTick(doNext); - return; - } - - var promise = null; - try { - promise = arr[index](); - } - catch (ex) { - logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); - } - - if (promise) { - when(promise).then( - function () { - //chain this forward and resolve! we're done! - tmp.resolve.apply(tmp, arguments); - }, - function (err) { - reasons.push(err); - - //lets try the next one! - process.nextTick(doNext); - }); - } - else { - process.nextTick(doNext); - } - }; - - process.nextTick(doNext); - return tmp.promise; - }, - - check_requires_update: function(device, target) { - var version = (device && device["cc3000_patch_version"]); - return (version && (version < target)); - }, - - getIPAddresses: function () { - //adapter = adapter || "eth0"; - var results = []; - var nics = os.networkInterfaces(); - - for (var name in nics) { - var nic = nics[name]; - - for (var i = 0; i < nic.length; i++) { - var addy = nic[i]; - - if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { - continue; - } - - results.push(addy.address); - } - } - - return results; - }, - - slugify: function (text) { - return text.toString().toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/[^\w\-]+/g, '') // Remove all non-word chars - .replace(/\-\-+/g, '-') // Replace multiple - with single - - .replace(/^-+/, '') // Trim - from start of text - .replace(/-+$/, ''); // Trim - from end of text - }, - - foo: null -}; \ No newline at end of file diff --git a/js/oauth_clients.json b/js/oauth_clients.json index 82af918d..e7e46d54 100644 --- a/js/oauth_clients.json +++ b/js/oauth_clients.json @@ -3,5 +3,10 @@ "client_id" : "particle", "client_secret" : "particle", "grants" : [ "password", "refresh_token" ] + }, + { + "client_id" : "CLI2", + "client_secret" : "client_secret_here", + "grants" : [ "password", "refresh_token" ] } -] \ No newline at end of file +] diff --git a/js/package.json b/js/package.json deleted file mode 100644 index a1a660ba..00000000 --- a/js/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "spark-server", - "version": "0.1.1", - "license": "AGPL-3.0", - "repository": { - "type": "git", - "url": "https://github.com/spark/spark-server" - }, - "homepage": "https://github.com/spark/spark-server", - "bugs": "https://github.com/spark/spark-server/issues", - "author": { - "name": "David Middlecamp", - "email": "david@spark.io", - "url": "https://www.spark.io/" - }, - "dependencies": { - "body-parser": "^1.15.2", - "connect-multiparty": "^2.0.0", - "express": "^4.14.0", - "express-oauth-server": "git://github.com/durielz/express-oauth-server.git", - "hogan-express": "^0.5.2", - "moment": "*", - "morgan": "^1.7.0", - "request": "*", - "spark-protocol": "git://github.com/durielz/spark-protocol.git", - "ursa": "*", - "when": "*", - "xtend": "*" - }, - "scripts": { - "start": "node main.js" - }, - "main": "main.js", - "contributors": [ - "Kenneth Lim " - ] -} diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js deleted file mode 100644 index b2db7679..00000000 --- a/js/views/EventViews001.js +++ /dev/null @@ -1,339 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var settings = require('../settings.js'); -var CoreController = require('../lib/CoreController.js'); - -var Api = require('./api_v1.js'); -var utilities = require("../lib/utilities.js"); -var logger = require('../lib/logger.js'); - -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); - -var moment = require('moment'); - -var EventsApi = { - loadViews: function (app) { - - // GET /v1/events[/:event_name] - // GET /v1/devices/events[/:event_name] - // GET /v1/devices/:device_id/events[/:event_name] - - app.get('/v1/events', EventsApi.get_events); - app.get('/v1/events/:event_name', EventsApi.get_events); - - app.get('/v1/devices/events', EventsApi.get_my_events); - app.post('/v1/devices/events', EventsApi.send_an_event); - app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); - - app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); - app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); - - app.get('/v1/products/:productIdOrSlug/events', app.oauth.authenticate(), EventsApi.get_product_events); - }, - - - //----------------------------------------------------------------- - - pipeEvents: function (socket, req, res, next, filterCoreId, filterProductId) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - /* - Start SSE - */ - - req.socket.setNoDelay(); - - res.writeHead(200, { - "Connection": "keep-alive", - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - "X-Accel-Buffering": "no" - }); - res.write(":ok\n\n"); - - - var _idleTimer = null; - var _lastMessage = null; - var keepAlive = function() { - if (((new Date()) - _lastMessage) >= 9000) { - _lastMessage = new Date(); - res.write("\n"); - checkSocket(); - } - }; - - //if nothing gets sent for 9 seconds, send a newline. - var aliveInterval = setInterval(keepAlive, 3000); - - var checkSocket = function () { - try { - if (!socket) { - cleanup(); - return false; - } - - if (res.socket.destroyed) { - logger.log("Socket destroyed, cleaning up Event listener"); - cleanup(); - return false; - } - } - catch (ex) { - logger.error("pipeEvents - error checking socket ", ex); - } - return true; - }; - - var cleanup = function () { - try { - if (socket) { - socket.close(); - socket = null; - } - } - catch (ex) { - logger.error("pipeEvents - event socket close err: ", ex); - } - - try { - if (res.socket) { - res.socket.end(); - } - res.end(); - } - catch (ex) { - logger.error("pipeEvents - response close err: ", ex); - } - - try { - if (aliveInterval) { - clearInterval(aliveInterval); - aliveInterval = null; - } - } - catch (ex) { - logger.error("pipeEvents - clear interval err: ", ex); - } - - }; - - - // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events - var writeEventGen = function (isPublic) { - return function (name, data, ttl, published_at, coreid) { - if (filterCoreId && (filterCoreId != coreid)) { - return; - } - - if (!checkSocket()) { - return; - } - - if(filterProductId == null && !Api.hasDevice(coreid, userid)) { - return; - } - - if(filterProductId != null && !Api.hasProduct(filterProductId, userid)) { - return; - } - - try { - _lastMessage = new Date(); - - //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. - name = (name) ? name.toString().replace(userid + "/", "") : null; - - var obj = { - data: data ? data.toString() : null, - ttl: ttl ? ttl.toString() : null, - published_at: (published_at) ? published_at.toString() : null, - coreid: (coreid) ? coreid.toString() : null - }; - res.write("event: " + name + "\n"); - res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies - } - catch (ex) { - logger.error("pipeEvents - write error: " + ex); - } - - //OTHER HEADERS: - //retry: ? - //id: ? //if we want to support resuming - //TODO: escape newlines in message? - }; - }; - - socket.on('public', writeEventGen(true)); - socket.on('private', writeEventGen(false)); - - req.on("close", cleanup); - req.on("end", cleanup); - res.on("close", cleanup); - res.on("finish", cleanup); - //res.setTimeout(30 * 1000, cleanup); - }, - - - get_events: function (req, res, next) { - var name = req.params.event_name; - name = name || ""; - var socket = new CoreController(); - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - //----------------------------------- - //get firehose and my private events. - //socket.subscribe(true, name); - //socket.subscribe(true, name); - socket.subscribe(false, name, userid); - - - //send it all through - EventsApi.pipeEvents(socket, req, res, next); - }, - get_my_events: function (req, res, next) { - var name = req.params.event_name; - name = name || ""; - var socket = new CoreController(); - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - //----------------------------------- - //get my events: - //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); - - //don't filter by core id - EventsApi.pipeEvents(socket, req, res, next); - }, - get_core_events: function (req, res, next) { - var name = req.params.event_name; - var socket = new CoreController(); - name = name || ""; - var coreid = req.coreID || req.params.coreid; - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - //check if core is owned - if(!Api.hasDevice(coreid, userid)) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - - //----------------------------------- - //get core events - //socket.subscribe(true, name); - socket.subscribe(true, name, userid,coreid); - socket.subscribe(false, name, userid,coreid); - - //----------------------------------- - //filter to core id - EventsApi.pipeEvents(socket, req, res, next, coreid); - }, - - - send_an_event: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req), - socketID = Api.getSocketID(userid), - eventName = req.body.name, - data = req.body.data, - ttl = req.body.ttl || 60, - private_str = req.body.private; - - if(!userid) { - return next(); - } - - var is_public = (!private_str || (private_str == "") || (private_str == "false")); - - var socket = new CoreController(socketID); - var success = socket.sendEvent(is_public, - eventName, - userid, - data, - parseInt(ttl), - moment().toISOString(), - userid - ); - - var autoClose = setTimeout(function () { - socket.close(); - res.json({ok: success}); - }, 250); - }, - - get_product_events: function (req, res, next) { - var name = req.params.event_name; - var socket = new CoreController(); - name = name || ""; - var productid = req.params.productIdOrSlug; - productid = productid || null; - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - //check if product is owned by user - if(!Api.hasProduct(productid, userid)) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - - //----------------------------------- - //get product events - //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); - - //----------------------------------- - //filter to core id - EventsApi.pipeEvents(socket, req, res, next, null, productid); - }, - - _: null -}; - - -module.exports = EventsApi; \ No newline at end of file diff --git a/js/views/api_v1.js b/js/views/api_v1.js deleted file mode 100644 index d1b093b6..00000000 --- a/js/views/api_v1.js +++ /dev/null @@ -1,1194 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var settings = require('../settings.js'); - -var CoreController = require('../lib/CoreController.js'); -var PasswordHasher = require('../lib/PasswordHasher.js'); - -var sequence = require('when/sequence'); -var parallel = require('when/parallel'); -var pipeline = require('when/pipeline'); - -var logger = require('../lib/logger.js'); -var utilities = require("../lib/utilities.js"); - -var fs = require('fs'); -var when = require('when'); -var util = require('util'); -var path = require('path'); -var ursa = require('ursa'); -var moment = require('moment'); - -var multipart = require('connect-multiparty'); -var multipartMiddleware = multipart(); - -/* - * TODO: modularize duplicate code - * TODO: implement proper session handling / user authentication - * TODO: add cors handler without losing :params support - * - */ - -var Api = { - loadViews: function (app) { - - //our middleware - app.param("coreid", Api.loadCore); - - - //core functions / variables - app.post('/v1/devices/:coreid/:func', Api.fn_call); - app.get('/v1/devices/:coreid/:var', Api.get_var); - - app.put('/v1/devices/:coreid', multipartMiddleware, Api.set_core_attributes); - app.get('/v1/devices/:coreid', Api.get_core_attributes);//ok customer - - //doesn't need per-core permissions, only shows owned cores. - app.get('/v1/devices', Api.list_devices);//ok customer - - app.post('/v1/provisioning/:coreid', Api.provision_core); - - app.delete('/v1/devices/:coreid', Api.release_device); - - app.post('/v1/devices', Api.claim_device); - app.post('/v1/device_claims', app.oauth.authenticate(), Api.get_claim_code); - - /*products*/ - app.get('/v1/products', app.oauth.authenticate(), Api.list_products); - app.get('/v1/products/:productIdOrSlug', app.oauth.authenticate(), Api.get_product); - app.post('/v1/products/:productIdOrSlug/device_claims', app.oauth.authenticate(), Api.get_product_claim_code); - app.delete('/v1/products/:productIdOrSlug/devices/:coreid', app.oauth.authenticate(), Api.release_product_device); - app.get('/v1/products/:productIdOrSlug/customers', app.oauth.authenticate(), Api.get_product_customers); - - app.post('/v1/products/:productIdOrSlug/devices', app.oauth.authenticate(), Api.add_product_device); - }, - - getSocketID: function (userID) { - return userID + "_" + global._socket_counter++; - }, - - getUserID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - if(req.app.locals.oauth.token.scope && req.app.locals.oauth.token.scope.indexOf("customer=") > -1) { - logger.log("Customer token"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - getCustomerID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - if(!req.app.locals.oauth.token.scope || req.app.locals.oauth.token.scope.indexOf("customer=") == -1) { - logger.log("User token"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - getUserOrCustomerID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - hasDevice: function (coreID, userID) { - var userObj = global.roles.getUserByDevice(coreID); - //check core permission - if(userObj && userObj._id == userID) { - return true; - } else { - //logger.log("device Permission Denied"); - return false; - } - }, - - hasOrg: function (userID) { - var orgObj = global.roles.getOrgByUserid(userID); - //check user permission - if(orgObj) { - return true; - } else { - return false; - } - }, - - hasProduct: function (productIdOrSlug, userID) { - var orgObj = global.roles.getOrgByProductid(productIdOrSlug); - //check user permission - if(orgObj && orgObj.user_id == userID) { - return true; - } else { - return false; - } - }, - - list_devices: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - logger.log("ListDevices", { userID: userid }); - - //give me all the cores - - //var allCoreIDs = global.server.getAllCoreIDs(), - var userDevicesIDs = global.roles.devices[userid], - devices = [], - connected_promises = []; - - for (var index in userDevicesIDs) { - var coreid = userDevicesIDs[index]; - - if (!coreid) { - continue; - } - - var core = global.server.getCoreAttributes(coreid); - - var device = { - id: coreid, - name: core ? core.name : null, - last_app: core ? core.last_flashed_app_name : null, - product_id: core ? core.spark_product_id : null, - firmware_version: core ? core.product_firmware_version : null, - system_version: core ? core.spark_system_version : null, - last_heard: null - }; - - if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - devices.push(device); - connected_promises.push(Api.isDeviceOnline(userid, device.id)); - } - - logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); - - //switched 'done' to 'then' - threw an exception with 'done' here. - when.settle(connected_promises).then(function (descriptors) { - for (var i = 0; i < descriptors.length; i++) { - var desc = descriptors[i]; - devices[i].connected = ('rejected' !== desc.state); - devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; - } - - res.status(200).json(devices); - }); - }, - - get_core_attributes: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - socket = new CoreController(socketID); - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); - - var objReady = parallel([ - function () { - return when.resolve(global.server.getCoreAttributes(coreID)); - }, - function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); - }, - function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Ping" }, { cmd: "Pong" })); - } - ]); - - //whatever we get back... - when(objReady).done(function (results) { - try { - - if (!results || (results.length != 3)) { - logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); - res.status(404).json("Oops, I couldn't find that core"); - return; - } - - //we're expecting descResult to be an array: [ sender, {} ] - var doc = results[0], - descResult = results[1], - coreState = null, - descPingResult = results[2]; - - if (!doc || !doc.coreID) { - logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); - res.status(404).json("Oops, I couldn't find that core"); - return; - } - - if (util.isArray(descResult) && (descResult.length > 1)) { - coreState = descResult[1].state || {}; - } - if (!coreState) { - logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); - } - - var device = { - id: doc.coreID, - name: doc.name || null, - last_app: doc.last_flashed, - product_id: doc.spark_product_id || null, - firmware_version: doc.product_firmware_version || null, - system_version: doc.spark_system_version || null, - //connected: !!coreState, - connected: (descPingResult != "Request Timed Out") ? descPingResult[1].online : false, - last_heard: (descPingResult != "Request Timed Out") ? descPingResult[1].lastPing : null, - variables: (coreState) ? coreState.v : null, - functions: (coreState) ? coreState.f : null, - cc3000_patch_version: doc.cc3000_driver_version - }; - - if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - res.json(device); - } - catch (ex) { - logger.error("get_core_attributes merge error: " + ex); - res.status(500).json({ Error: "get_core_attributes error: " + ex }); - } - }, null); - - //get_core_attribs - end - }, - - set_core_attributes: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid) && !Api.hasOrg(userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - var promises = []; - - logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); - - var coreName = req.body ? req.body.name : null; - if (coreName != null) { - logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); - - global.server.setCoreAttribute(req.coreID, "name", coreName); - promises.push(when.resolve({ ok: true, name: coreName })); - } - - var hasFiles = req.files && req.files.file; - if (hasFiles) { - console.log("file"); - //oh hey, you want to flash firmware? - promises.push(Api.compile_and__or_flash_dfd(req)); - } - - var signal = req.body && req.body.signal; - if (signal) { - //get your hands up in the air! Or down. - promises.push(Api.core_signal_dfd(req)); - } - - var flashApp = req.body ? req.body.app : null; - if (flashApp) { - // It makes no sense to flash a known app and also - // either signal or flash a file sent with the request - if (!hasFiles && !signal) { - - // MUST sanitize app name here, before sending to Device Service - if (utilities.contains(settings.known_apps, flashApp)) { - promises.push(Api.flash_known_app_dfd(req)); - } - else { - promises.push(when.reject("Can't flash unknown app " + flashApp)); - } - } - } - - var app_id = req.body ? req.body.app_id : null; - if (app_id && !hasFiles && !signal && !flashApp) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_app_in_db_dfd(req)); - } - - var app_example_id = req.body ? req.body.app_example_id : null; - if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_example_app_in_db_dfd(req)); - } - - - if (promises.length >= 1) { - when.all(promises).done( - function (results) { - var aggregate = {}; - for (var i in results) { - for (var key in results[i]) { - aggregate[key] = results[i][key]; - } - } - res.json(aggregate); - }, - function (err) { - res.json({ ok: false, errors: [err] }); - } - ); - } - else { - logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); - res.json({error: "Nothing to do?"}); - } - }, - - isDeviceOnline: function (userID, coreID) { - var tmp = when.defer(); - - var socketID = Api.getSocketID(userID); - var socket = new CoreController(socketID); - - var failTimer = setTimeout(function () { - logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); - socket.close(); - tmp.reject("Device is not connected"); - }, settings.isCoreOnlineTimeout); - - - //setup listener for response back from the device service - socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); - - if (msg && msg.online) { - tmp.resolve(msg); - } - else { - tmp.reject(["Core isn't online", 404]); - } - - }, true); - - logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); - - //send it along to the device service - if (!socket.send(coreID, { cmd: "Ping" })) { - tmp.reject("Device is not connected"); - } - - return tmp.promise; - }, - - get_claim_code: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("GenerateClaimCode", { userID: userid }); - - var userDevicesIDs = global.roles.devices[userid]; - PasswordHasher.generateSalt(function (err, code) { - code = code.toString('base64'); - code = code.substring(0, 63); - - when(global.roles.addClaimCode(code, userid)).then( - function () { - res.json({ - claim_code: code, - device_ids: userDevicesIDs - }); - }, - function (err) { - res.json({ - ok: false, - errors: [ - err - ] - }); - } - ); - }); - }, - - claim_device: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - var coreid = req.body.id; - var core = global.server.getCoreAttributes(coreid); - - if(coreid) { - - if(core.claimCode) { - var userObj = global.roles.getUserByClaimCode(core.claimCode); - - if(user) { - when(global.roles.addDevice(coreid, userObj)).then( - function () { - var claimInfo = { - user_id : userObj._id, - id: coreid, - connected: false, - ok: true - } - - when(Api.isDeviceOnline(userid, coreid)) - .then( - function (desc) { - claimInfo.connected = ('rejected' !== desc.state); - - global.server.setCoreAttribute(coreid, "claimed", true); - res.json(claimInfo); - }, - function (err) { - res.status(404).json({ - ok: false, - errors: [ - "Device is not connected" - ] - }); - } - ); - }, - function (err) { - res.status(403).json({ - ok: false, - errors: [ - "That belongs to someone else. To request a transfer add ?request_transfer=true to the URL." - ] - }); - } - ); - } else { - res.status(404).json({ - ok: false, - errors: [ - {} - ] - }); - } - } else { - res.status(404).json({ - ok: false, - errors: [ - {} - ] - }); - } - } else { - res.status(404).json({ - ok: false, - errors: [ - "data.deviceID is empty" - ] - }); - } - }, - - release_device: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - when(global.roles.removeDevice(coreID, userid)).then( - function () { - global.server.setCoreAttribute(coreID, "claimed", false); - res.json({'ok' : true }); - }, function (err) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - } - ); - }, - - //called when the core send its claim code - linkDevice: function (coreid, claimCode, productid) { - var userObj = global.roles.getUserByClaimCode(claimCode); - if(userObj) { - logger.log("Linking Device...", { coreID: coreid }); - - if(userObj.org) { //if customer - //check if coreid is present in product devices - var productObj = global.roles.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, coreid); - if (index > -1) { - for (var i = 0; i < global.roles.claim_codes[userObj._id].length; i++) { - var claimCodeObj = global.roles.claim_codes[userObj._id][i]; - //check if the claim code is valid for the product - if (claimCodeObj.code == claimCode && claimCodeObj.product_id != productid) { - logger.error("Claim code not valid for product", { claimCode: claimCode }); - return false; - } - }; - } else { - logger.error("Device not found for product"); - return false; - } - } - - when(global.roles.addDevice(coreid, userObj)).then( - function () { - global.server.setCoreAttribute(coreid, "claimed", true); - logger.log("Device linked", { coreID: coreid }); - }, - function (err) { - logger.error("Error in linking Device: "+err, { coreID: coreid }); - } - ); - } else { - logger.error("Claim code not valid", { claimCode: claimCode }); - } - }, - - safeMode: function (coreID, description) { - - logger.log("Device is in SAFE MODE", {coreID: coreID}); - - global.server.publishSpecialEvents('spark/status/safe-mode', description, coreID); - - //# spark/safe-mode-updater/updating - //{"name":"spark/safe-mode-updater/updating","data":"2","ttl":"60","published_at":"2016-01-01T14:41:0.000Z","coreid":"particle-internal"} - }, - - loadCore: function (req, res, next) { - req.coreID = req.params.coreid || req.body.id; - - //load core info! - req.coreInfo = { - "last_app": "", - "last_heard": new Date(), - "connected": false, - "deviceID": req.coreID - }; - - //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var gotCore = utilities.deferredAny([ - function () { - var core = global.server.getCoreAttributes(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - }, - function () { - var core = global.server.getCoreByName(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - } - ]); - - when(gotCore).then( - function (core) { - if (core) { - req.coreID = core.coreID || req.coreID; - req.coreInfo = { - last_handshake_at: core.last_handshake_at - }; - } - - next(); - }, - function (err) { - //s`okay. - next(); - }) - }, - - get_var: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - varName = req.params.var, - format = req.params.format; - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); - - - //send it along to the device service - //and listen for a response back from the device service - var socket = new CoreController(socketID); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "GetVar", name: varName }, - { cmd: "VarReturn", name: varName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then(function (arr) { - var msg = arr[1]; - if (msg.error) { - //at this point, either we didn't get a describe return, or that variable - //didn't exist, either way, 404 - return res.status(404).json({ - ok: false, - error: msg.error - }); - } - - //TODO: make me look like the spec. - msg.coreInfo = req.coreInfo; - msg.coreInfo.connected = true; - - if (format && (format == "raw")) { - return res.sendStatus("" + msg.result); - } - else { - return res.json(msg); - } - }, - function () { - res.status(408).json({error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - }, - - fn_call: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req), - coreID = req.coreID, - funcName = req.params.func, - format = req.params.format; - - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("FunCall", { coreID: coreID, userid: userid.toString() }); - - var socketID = Api.getSocketID(userid); - var socket = new CoreController(socketID); - var core = socket.getCore(coreID); - - - var args = req.body; - delete args.access_token; - logger.log("FunCall - calling core ", { coreID: coreID, userid: userid.toString() }); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "CallFn", name: funcName, args: args }, - { cmd: "FnReturn", name: funcName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then( - function (arr) { - var sender = arr[0], msg = arr[1]; - - try { - //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); - if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { - res.status(404).json({ - ok: false, - error: "Function not found" - }); - } - else if (msg.error != null) { - res.status(400).json({ - ok: false, - error: msg.error - }); - } - else { - if (format && (format == "raw")) { - res.sendStatus("" + msg.result); - } - else { - res.json({ - id: core.coreID, - name: core.name || null, - last_app: core.last_flashed_app_name || null, - connected: true, - return_value: msg.result - }); - } - } - } - catch (ex) { - logger.error("FunCall handling resp error " + ex); - res.status(500).json({ - ok: false, - error: "Error while api was rendering response" - }); - } - }, - function () { - res.status(408).json({error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - - //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); - - // send the function call along to the device service - }, - - /** - * Ask the core to start / stop the "RaiseYourHand" signal - * @param req - */ - core_signal_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID, - showSignal = parseInt(req.body.signal); - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out, didn't hear back"}); - }, settings.coreSignalTimeout); - - //listen for a response back from the device service - socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, - function () { - clearTimeout(failTimer); - socket.close(); - - tmp.resolve({ - id: coreID, - connected: true, - signaling: showSignal === 1 - }); - }, true); - - - //send it along to the core via the device service - socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); - - return tmp.promise; - }, - - compile_and__or_flash_dfd: function (req) { - var allDone = when.defer(); - var userid = Api.getUserID(req), - coreID = req.coreID; - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - // - // Did they pass us a source file or a binary file? - // - var hasSourceFiles = false; - var sourceExts = [".cpp", ".c", ".h", ".ino" ]; - if (req.files) { - for (var name in req.files) { - if (!req.files.hasOwnProperty(name)) { - continue; - } - - var ext = utilities.getFilenameExt(req.files[name].path); - if (utilities.contains(sourceExts, ext)) { - hasSourceFiles = true; - break; - } - } - } - - - if (hasSourceFiles) { - //TODO: federate? - allDone.reject("Not yet implemented"); - } - else { - //they sent a binary, just flash it! - var flashDone = Api.flash_core_dfd(req); - - //pipe rejection / resolution of flash to response - utilities.pipeDeferred(flashDone, allDone); - } - - return allDone.promise; - }, - - - /** - * Flashing firmware to the core, binary file! - * @param req - * @returns {promise|*|Function|Promise|when.promise} - */ - flash_core_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID; - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); - - var args = req.query; - delete args.coreid; - - if (req.files) { - console.log(req.files); - args.data = fs.readFileSync(req.files.file.path); - //args.data = fs.readFileSync(req.files.file.file); - } - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out."}); - }, settings.coreFlashTimeout); - - //listen for the first response back from the device service - socket.listenFor(coreID, { cmd: "Event", name: "Update" }, - function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - var response = { id: coreID, status: msg.message }; - if ("Update started" === msg.message) { - tmp.resolve(response); - } - else { - logger.error("flash_core_dfd rejected ", response); - tmp.reject(response); - } - - }, true); - - //send it along to the device service - socket.send(coreID, { cmd: "UFlash", args: args }); - - return tmp.promise; - }, - - provision_core: function (req, res, next) { - //if we're here, the user should be allowed to provision cores. - - var done = Api.provision_core_dfd(req); - when(done).then( - function (result) { - res.json(result); - }, - function (err) { - //different status code here? - res.status(400).json(err); - }); - }, - - provision_core_dfd: function (req) { - var result = when.defer(), - userid = Api.getUserID(req), - deviceID = req.body.deviceID, - publicKey = req.body.publicKey; - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - if (!deviceID) { - return when.reject({ error: "No deviceID provided" }); - } - - try { - var keyObj = ursa.createPublicKey(publicKey); - if (!publicKey || (!ursa.isPublicKey(keyObj))) { - return when.reject({ error: "No key provided" }); - } - } - catch (ex) { - logger.error("error while parsing publicKey " + ex); - return when.reject({ error: "Key error " + ex }); - } - - - global.server.addCoreKey(deviceID, publicKey); - global.server.setCoreAttribute(deviceID, "registrar", userid); - global.server.setCoreAttribute(deviceID, "timestamp", new Date()); - result.resolve("Success!"); - - return result.promise; - }, - - //List products the currently authenticated user has access to. - list_products: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("ListProducts", { userID: userid }); - - var orgObj = global.roles.getOrgByUserid(userid); - if(orgObj) { - var productObjs = global.roles.products[orgObj.slug]; - - //remove devices ?? - res.json({ products : productObjs }); - } else { - res.json({ products : [] }); - } - }, - - //Retrieve details for a product. - get_product: function( req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("GetProduct", { userID: userid }); - - var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - var productObj = global.roles.getProductByProductid(productid); - - res.json({ product : productObj }); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); - } - }, - - //Generate a device claim code for a customer, scoped for a specific product. - get_product_claim_code: function (req, res, next) { - var customerid = Api.getCustomerID(req); - if(!customerid) { - return next(); - } - - var productid = req.params.productIdOrSlug; - - logger.log("GenerateProductClaimCode", { customerID: customerid }); - - var productObj = global.roles.getProductByProductid(productid); - var productDevicesIDs = productObj.devices; - PasswordHasher.generateSalt(function (err, code) { - code = code.toString('base64'); - code = code.substring(0, 63); - - when(global.roles.addProductClaimCode(code, customerid, productObj.product_id)).then( - function () { - res.json({ - claim_code: code, - device_ids: productDevicesIDs - }); - }, - function (err) { - res.json({ - ok: false, - errors: [ - err - ] - }); - } - ); - }); - }, - - //Remove a device from a product and re-assign to a generic Particle product. This endpoint will unclaim the device if it is owned by a customer. - release_product_device: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - when(global.roles.removeProductDevice(coreID, productid)).then( - function () { - global.server.setCoreAttribute(coreID, "claimed", false); - res.json({ ok:true }); - }, function (err) { - res.status(400).json({ - "code": 400, - "ok": false, - "info": "Device not found for this product" - }); - } - ); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); - } - }, - - //List Customers for a product. - get_product_customers: function( req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("GetProductCustomer", { userID: userid }); - - var productid = req.params.productIdOrSlug; - var productObj = global.roles.getProductByProductid(productid); - var productDevices = productObj.devices; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - var customerObjs = []; - var userObjIds = []; - var devices = []; - for (var k = 0; k < productDevices.length; k++) { - var deviceid = productDevices[k]; - var userObj = global.roles.getUserByDevice(deviceid); - if(userObj && userObj.org) { //if customer - devices.push(deviceid); - var index = utilities.indexOf(userObjIds, userObj._id); - if (index == -1) { - userObjIds.push(userObj._id); - customerObjs.push({ - id: userObj._id, - email: userObj.email, - devices: userObj.devices - }); - } - } - } - res.json({ customers : customerObjs, devices : devices }); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); - } - }, - - add_product_device: function (req, res, next) { - var coreID = req.body.id; - if(!coreID) { - res.status(400).json({ ok: false, errors: [ 'id is required.' ] }); - } - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - var productid = req.params.productIdOrSlug; - logger.log("AddingProductDevice", { productID: productid }); - - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - when(global.roles.addProductDevice(coreID, productid)).then( - function () { - res.json({ ok:true }); - }, function (err) { - res.status(400).json({ - "code": 400, - "ok": false, - "info": "Device already present for that product" - }); - } - ); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); - } - }, - - _: null -}; - -exports = module.exports = global.api = Api; diff --git a/lib/AccessTokenViews.js b/lib/AccessTokenViews.js index b5b02a0d..9382e00c 100644 --- a/lib/AccessTokenViews.js +++ b/lib/AccessTokenViews.js @@ -23,81 +23,85 @@ var sequence = require('when/sequence'); var pipeline = require('when/pipeline'); var PasswordHasher = require('./PasswordHasher.js'); var roles = require('./RolesController.js'); +var logger = require('./logger.js'); var AccessTokenViews = function (options) { - this.options = options; + this.options = options; }; AccessTokenViews.prototype = { - loadViews: function (app) { - app.get('/v1/access_tokens', this.index.bind(this)); - app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); - }, - index: function (req, res) { - var credentials = AccessTokenViews.basicAuth(req); - if (!credentials) { - return res.json(401, { - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] - }); - } + loadViews: function (app) { + app.get('/v1/access_tokens', this.index.bind(this)); + app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); + }, + index: function (req, res) { + //var credentials = AccessTokenViews.basicAuth(req); + //var credentials = this.basicAuth(req); + var credentials= AccessTokenViews.prototype.basicAuth(req); + if (!credentials) { + return res.status(401).json({ + ok: false, + errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] + }); + } - //if successful, should return something like: - // [ { token: d.token, expires: d.expires, client: d.client_id } ] + //if successful, should return something like: + // [ { token: d.token, expires: d.expires, client: d.client_id } ] - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - res.json(userObj.access_tokens); - }, - function () { - res.json(401, { ok: false, errors: ['Bad password']}); - }); - }, + when(roles.validateLogin(credentials.username, credentials.password)) + .then( + function (userObj) { + res.json(userObj.access_tokens); + }, + function () { + res.status(401).json({ ok: false, errors: ['Bad password']}); + }); + }, - destroy: function (req, res) { - var credentials = AccessTokenViews.basicAuth(req); - if (!credentials) { - return res.json(401, { - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] - }); - } + destroy: function (req, res) { + //var credentials = AccessTokenViews.basicAuth(req); + var credentials= AccessTokenViews.prototype.basicAuth(req); + if (!credentials) { + return res.status(401).json({ + ok: false, + errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] + }); + } - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - try { - roles.destroyAccessToken(req.params.token); - res.json({ ok: true }); - } - catch (ex) { - logger.error("error saving user " + ex); - res.json(401, { ok: false, errors: ['Error updating token']}); - } - }, - function () { - res.json(401, { ok: false, errors: ['Bad password']}); - }); - }, + when(roles.validateLogin(credentials.username, credentials.password)) + .then( + function (userObj) { + try { + roles.destroyAccessToken(req.params.token); + res.json({ ok: true }); + } + catch (ex) { + logger.error("error saving user " + ex); + res.status(401).json({ ok: false, errors: ['Error updating token']}); + } + }, + function () { + res.status(401).json({ ok: false, errors: ['Bad password']}); + }); + }, - basicAuth: function (req) { - var auth = req.get('Authorization'); - if (!auth) return null; + basicAuth: function (req) { + var auth = req.get('Authorization'); + if (!auth) return null; - var matches = auth.match(/Basic\s+(\S+)/); - if (!matches) return null; + var matches = auth.match(/Basic\s+(\S+)/); + if (!matches) return null; - var creds = new Buffer(matches[1], 'base64').toString(); - var separatorIndex = creds.indexOf(':'); - if (-1 === separatorIndex) - return null; + var creds = new Buffer(matches[1], 'base64').toString(); + var separatorIndex = creds.indexOf(':'); + if (-1 === separatorIndex) + return null; - return { - username: creds.slice(0, separatorIndex), - password: creds.slice(separatorIndex + 1) - }; - } + return { + username: creds.slice(0, separatorIndex), + password: creds.slice(separatorIndex + 1) + }; + } }; diff --git a/lib/CoreController.js b/lib/CoreController.js index 3bf0bb97..c7b4d726 100644 --- a/lib/CoreController.js +++ b/lib/CoreController.js @@ -27,167 +27,162 @@ var utilities = require("./utilities.js"); var CoreController = function (socketID) { - //this.coreID = coreID; - this.socketID = socketID; - EventEmitter.call(this); + //this.coreID = coreID; + this.socketID = socketID; + EventEmitter.call(this); }; CoreController.prototype = { - getCore: function (coreid) { - if (global.server) { - return global.server.getCore(coreid); - } - else { - logger.error("Spark-protocol server not running"); - } - }, - - - sendAndListenFor: function (recipient, msg, filter, callback, once) { - this.listenFor(recipient, filter, callback, once); - this.send(recipient, msg); - }, - - sendAndListenForDFD: function (recipient, msg, filter, failDelay, connectDelay) { - var result = when.defer(); - - failDelay = failDelay || settings.coreRequestTimeout; - var failTimer = setTimeout(function () { - result.reject("Request Timed Out"); - }, failDelay); - - var callback = function (sender, msg) { - clearTimeout(failTimer); - result.resolve([sender, msg]); - }; - - this.sendAndListenFor(recipient, msg, filter, callback, true); - return result.promise; - }, - - - /** - * send a message to a core - * @param recipient - * @param msg - */ - send: function (recipient, msg) { - var that = this; - var core = this.getCore(recipient); - if (!core || !core.onApiMessage) { - logger.error("Couldn't find that core ", recipient); - return false; - } - - process.nextTick(function () { - try { - //console.log("sending message with socketID" + that.socketID); - core.onApiMessage(that.socketID, msg); - } - catch (ex) { - logger.error("error during send: " + ex); - } - }); - return true; - }, - - /** - * starts listening for a message event with the given filter criteria - * @param filter - * @param callback - * @param once - removes the listener after we've heard back - */ - listenFor: function (recipient, filter, callback, once) { - var core = this.getCore(recipient); - if (!core || !core.on) { - logger.error("Couldn't find that core ", recipient); - return; - } - - var that = this, - handler = function (sender, msg) { - //logger.log('heard from ' + ((sender) ? sender.toString() : '(UNKNOWN)')); - - if (!utilities.leftHasRightFilter(msg, filter)) { - //logger.log('filters did not match'); - return; - } - - if (once) { - core.removeListener(that.socketID, handler); - } - - process.nextTick(function () { - try { - //logger.log('passing message to callback ', msg); - callback(sender, msg); - } - catch (ex) { - logger.error("listenFor error: " + ex, (ex) ? ex.stack : ''); - } - }); - }; - - core.on(that.socketID, handler); - }, - - subscribe: function (isPublic, name, userid) { - if (userid && (userid != "")) { - name = userid + "/" + name; - } - - + getCore: function (coreid) { + if (global.server) { + return global.server.getCore(coreid); + } + else { + logger.error("Spark-protocol server not running"); + } + }, + + + sendAndListenFor: function (recipient, msg, filter, callback, once) { + this.listenFor(recipient, filter, callback, once); + this.send(recipient, msg); + }, + + sendAndListenForDFD: function (recipient, msg, filter, failDelay, connectDelay) { + var result = when.defer(); + + failDelay = failDelay || settings.coreRequestTimeout; + var failTimer = setTimeout(function () { + result.reject("Request Timed Out"); + }, failDelay); + + var callback = function (sender, msg) { + clearTimeout(failTimer); + result.resolve([sender, msg]); + }; + + this.sendAndListenFor(recipient, msg, filter, callback, true); + return result.promise; + }, + + + /** + * send a message to a core + * @param recipient + * @param msg + */ + send: function (recipient, msg) { + var that = this; + var core = this.getCore(recipient); + if (!core || !core.onApiMessage) { + logger.error("Couldn't find that core ", recipient); + return false; + } + + process.nextTick(function () { + try { + //console.log("sending message with socketID" + that.socketID); + core.onApiMessage(that.socketID, msg); + } + catch (ex) { + logger.error("error during send: " + ex); + } + }); + return true; + }, + + /** + * starts listening for a message event with the given filter criteria + * @param filter + * @param callback + * @param once - removes the listener after we've heard back + */ + listenFor: function (recipient, filter, callback, once) { + var core = this.getCore(recipient); + if (!core || !core.on) { + logger.error("Couldn't find that core ", recipient); + return; + } + + var that = this, + handler = function (sender, msg) { + //logger.log('heard from ' + ((sender) ? sender.toString() : '(UNKNOWN)')); + + if (!utilities.leftHasRightFilter(msg, filter)) { + //logger.log('filters did not match'); + return; + } + + if (once) { + core.removeListener(that.socketID, handler); + } + + process.nextTick(function () { + try { + //logger.log('passing message to callback ', msg); + callback(sender, msg); + } + catch (ex) { + logger.error("listenFor error: " + ex, (ex) ? ex.stack : ''); + } + }); + }; + + core.on(that.socketID, handler); + }, + + subscribe: function (isPublic, name, userid,coreid) { // if (!sock) { // return false; // } - //start permitting these messages through on this socket. - global.publisher.subscribe(name, this); + //start permitting these messages through on this socket. + global.publisher.subscribe(name,userid,coreid, this); - return false; - }, + return false; + }, - unsubscribe: function (isPublic, name, userid) { - if (userid && (userid != "")) { - name = userid + "/" + name; - } + unsubscribe: function (isPublic, name, userid) { + if (userid && (userid != "")) { + name = userid + "/" + name; + } // if (!sock) { // return; // } - global.publisher.unsubscribe(name, this); - }, - - //isPublic, obj.name, obj.userid, obj.data, obj.ttl, obj.published_at - sendEvent: function (isPublic, name, userid, data, ttl, published_at, coreid) { - - if (!global.publisher) { - logger.error("Spark-protocol server not running"); - return; - } - - try { - global.publisher.publish( - isPublic, - name, - userid, - data, - ttl, - published_at, - coreid - ); - } - catch (ex) { - logger.error("sendEvent Error: " + ex); - } - - return true; - }, - - close: function () { - - } + global.publisher.unsubscribe(name, this); + }, + + //isPublic, obj.name, obj.userid, obj.data, obj.ttl, obj.published_at + sendEvent: function (isPublic, name, userid, data, ttl, published_at, coreid) { + + if (!global.publisher) { + logger.error("Spark-protocol server not running"); + return; + } + + try { + global.publisher.publish( + isPublic, + name, + userid, + data, + ttl, + published_at, + coreid + ); + } + catch (ex) { + logger.error("sendEvent Error: " + ex); + } + + return true; + }, + + close: function () { + + } }; ///** diff --git a/js/lib/CustomerViews.js b/lib/CustomerViews.js similarity index 100% rename from js/lib/CustomerViews.js rename to lib/CustomerViews.js diff --git a/lib/RolesController.js b/lib/RolesController.js index 0d6c8e54..88047157 100644 --- a/lib/RolesController.js +++ b/lib/RolesController.js @@ -25,206 +25,704 @@ var PasswordHasher = require('./PasswordHasher.js'); var roles = require('./RolesController.js'); var settings = require('../settings.js'); var logger = require('./logger.js'); - +var utilities = require("./utilities.js"); function RolesController() { - this.init(); + this.init(); }; RolesController.prototype = { - users: null, - usersByToken: null, - usersByUsername: null, - tokens: null, - - - init: function () { - this._loadAndCacheUsers(); - }, - - addUser: function (userObj) { - this.users.push(userObj); - this.usersByUsername[ userObj.username ] = userObj; - - if (userObj.access_token) { - this.usersByToken[userObj.access_token] = userObj; - this.tokens.push({ - user_id: userObj._id, - expires: userObj.access_token_expires_at - }); - } - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var token = userObj.access_tokens[i]; - this.usersByToken[ token ] = userObj; - this.tokens[token.token] = token; - } - }, - destroyAccessToken: function (access_token) { - var userObj = this.usersByToken[access_token]; - if (!userObj) { - return true; - } - - delete this.usersByToken[access_token]; - if (userObj.access_token == access_token) { - userObj.access_token = null; - } - var idx = utilities.indexOf(userObj.access_tokens, req.params.token); - if (idx >= 0) { - userObj.access_tokens.splice(idx, 1); - } - - this.saveUser(); - }, - addAccessToken: function (accessToken, clientId, userId, expires) { + users: null, + usersByToken: null, + usersByDevice: null, + usersByClaimCode: null, + usersByUsername: null, + + access_tokens: null, + refresh_tokens: null, + claim_codes: null, + + devices: null, + clients: null, + + orgsBySlug: null, + orgsByUserId: null, + + customers: null, + customersByEmail: null, + //customersByToken: null, + + orgsByProduct: null, + products : null, + + init: function () { + this._loadAndCacheUsers(); + }, + + addUser: function (userObj) { + this.users.push(userObj); + this.usersByUsername[ userObj.username ] = userObj; + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var token = userObj.access_tokens[i]; + this.usersByToken[token.token] = userObj; + this.access_tokens[token.token] = token; + this.refresh_tokens[token.refresh_token] = token; + } + + //claim codes + this.claim_codes[userObj._id] = userObj.claim_codes; + for (var i = 0; i < userObj.claim_codes.length; i++) { + var claimCode = userObj.claim_codes[i]; + this.usersByClaimCode[claimCode] = userObj; + } + + //devices claimed + this.devices[userObj._id] = userObj.devices; + for (var i = 0; i < userObj.devices.length; i++) { + var deviceId = userObj.devices[i]; + this.usersByDevice[ deviceId ] = userObj; + } + }, + addCustomer: function (customerObj) { + + console.log("Loading customer " + customerObj.email); + + this.customers.push(customerObj); + this.customersByEmail[ customerObj.email ] = customerObj; + + for (var k = 0; k < customerObj.access_tokens.length; k++) { + var token = customerObj.access_tokens[k]; + this.usersByToken[token.token] = customerObj; + this.access_tokens[token.token] = token; + this.refresh_tokens[token.refresh_token] = token; + } + + //claim codes + this.claim_codes[customerObj._id] = customerObj.claim_codes; + for (var i = 0; i < customerObj.claim_codes.length; i++) { + var claimCode = customerObj.claim_codes[i]; + this.usersByClaimCode[claimCode.code] = customerObj; + } + + //devices claimed + this.devices[customerObj._id] = customerObj.devices; + for (var i = 0; i < customerObj.devices.length; i++) { + var deviceId = customerObj.devices[i]; + this.usersByDevice[ deviceId ] = customerObj; + } + }, + addClient: function (clientObj) { + this.clients.push(clientObj); + }, + addOrg: function (orgObj) { + this.orgsBySlug[orgObj.slug] = orgObj; + this.orgsByUserId[orgObj.user_id] = orgObj; + + for (var i = 0; i < orgObj.customers.length; i++) { + this.addCustomer(orgObj.customers[i]); //add customer + } + + this.products[orgObj.slug] = []; //list product + for (var j = 0; j < orgObj.products.length; j++) { + this.products[orgObj.slug].push(orgObj.products[j]); + this.orgsByProduct[ orgObj.products[j].slug ] = orgObj; + this.orgsByProduct[ orgObj.products[j].product_id ] = orgObj; + } + }, + destroyAccessToken: function (access_token) { + var userObj = this.usersByToken[access_token]; + if (!userObj) { + return true; + } + + delete this.usersByToken[access_token]; + delete this.access_tokens[access_token]; + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var tokenObj = userObj.access_tokens[i]; + if (tokenObj.token == access_token) { + userObj.access_tokens.splice(i, 1); + } + } + + this.saveUser(userObj); + }, + revokeToken: function (token) { + var userObj = this.usersByToken[token.accessToken]; + if (!userObj) { + return false; + } + var tokenObj = this.access_tokens[token.accessToken]; + if(!tokenObj) { + return false; + } + + delete this.usersByToken[token.accessToken]; + delete this.access_tokens[token.accessToken]; + delete this.refresh_tokens[token.refreshToken]; + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var tokenObj = userObj.access_tokens[i]; + if (tokenObj.token == token.accessToken) { + userObj.access_tokens.splice(i, 1); + } + } + if(tokenObj.scope && tokenObj.scope.indexOf("customer=") > -1) { + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } + return token; + }, + addAccessToken: function (token, client, user) { + var tmp = when.defer(); + try { + var tokenObj = { + //user_id: user._id, + token: token.accessToken, + expires_at: token.accessTokenExpiresAt, + client: client.client_id, + refresh_token: token.refreshToken, + scope: token.scope + }; + + if(token.scope && token.scope.indexOf("customer=") > -1) { + //is a customer token + var email = token.scope.split("=")[1]; + var customerObj = this.customersByEmail[email]; + + this.usersByToken[token.accessToken] = customerObj; + this.access_tokens[token.accessToken] = tokenObj; + this.refresh_tokens[token.refreshToken] = tokenObj; + customerObj.access_tokens.push(tokenObj); + this.saveCustomer(customerObj); + + tmp.resolve({ + accessToken: token.accessToken, + client: client, + refreshToken: token.refreshToken, + user: customerObj._id, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt + }); + + } else { + var userObj = this.getUserByUserid(user._id); + if(!userObj) { + //refresh_token + userObj = this.getUserByUserid(user); + } + this.usersByToken[token.accessToken] = userObj; + + this.access_tokens[token.accessToken] = tokenObj; + this.refresh_tokens[token.refreshToken] = tokenObj; + userObj.access_tokens.push(tokenObj); + this.saveUser(userObj); + + tmp.resolve({ + accessToken: token.accessToken, + client: client, + user: userObj._id, + refreshToken: token.refreshToken, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt + }); + } + } + catch (ex) { + logger.error("Error adding access token ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addClaimCode: function (claimCode, userId) { + var tmp = when.defer(); + try { + var userObj = this.getUserByUserid(userId); + + userObj.claim_codes.push(claimCode); + this.saveUser(userObj); + + this.usersByClaimCode[ claimCode ] = userObj; + + tmp.resolve(); + } + catch (ex) { + logger.error("Error adding claim code ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addProductClaimCode: function (claimCode, customerId, productId) { + var tmp = when.defer(); + try { + var claimCodeObj = { + code : claimCode, + product_id : productId + } + + var customerObj = this.getCustomerByCustomerid(customerId); + customerObj.claim_codes.push(claimCodeObj); + this.saveCustomer(customerObj); + + this.usersByClaimCode[ claimCode ] = customerObj; + + tmp.resolve(); + } + catch (ex) { + logger.error("Error adding claim code ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addDevice: function (deviceId, userObj) { + var tmp = when.defer(); + try { + if(!this.usersByDevice[deviceId]) { + userObj.devices.push(deviceId); + if(userObj.org) { //customer + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } + this.usersByDevice[ deviceId ] = userObj; + + tmp.resolve(); + } else if(this.usersByDevice[deviceId] == userObj) { + tmp.resolve(); + } else { + tmp.reject("already claimed"); + } + } + catch (ex) { + logger.error("Error adding device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + removeDevice: function (deviceId, userId) { var tmp = when.defer(); try { - var userObj = this.getUserByUserid(userId); - this.usersByToken[accessToken] = userObj; - - var tokenObj = { - user_id: userId, - client_id: clientId, - token: accessToken, - expires: expires, - _id: accessToken - }; - - this.tokens[accessToken] = tokenObj; - userObj.access_tokens.push(tokenObj); - this.saveUser(userObj); - tmp.resolve(); + var userObj = this.getUserByUserid(userId); + + if(this.usersByDevice[deviceId]) { + var index = utilities.indexOf(userObj.devices, deviceId); + if (index > -1) { + userObj.devices.splice(index, 1); + } + + delete this.usersByDevice[deviceId]; + + if(userObj.org) { //customer + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } + tmp.resolve(); + } else { + tmp.reject('Device not found'); + } } catch (ex) { - logger.error("Error adding access token ", ex); - tmp.reject(ex); + logger.error("Error releasing device ", ex); + tmp.reject(ex); } return tmp.promise; + }, + addProductDevice: function (deviceId, productid) { + var tmp = when.defer(); + try { + var productObj = this.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, deviceId); + if (index == -1) { //if not present + productObj.devices.push(deviceId); + this.saveProduct(productObj); + + tmp.resolve(); + } else { + tmp.reject('Device already present for that product'); + } + } + catch (ex) { + logger.error("Error adding device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + removeProductDevice: function (deviceId, productid) { + var tmp = when.defer(); + try { + var productObj = this.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, deviceId); + if (index > -1) { + /*productObj.devices.splice(index, 1); + this.saveProduct(productObj);*/ + + delete this.usersByDevice[deviceId]; + + var orgObj = this.getOrgByProductid(productObj.product_id); + for (var i = 0; i < orgObj.customers.length; i++) { + var index = utilities.indexOf(orgObj.customers[i].devices, deviceId); + if (index > -1) { + orgObj.customers[i].devices.splice(index, 1); + this.saveCustomer(orgObj.customers[i]); + } + } + + tmp.resolve(); + } else { + tmp.reject('Device not found for product'); + } + } + catch (ex) { + logger.error("Error releasing device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + saveUser: function (userObj) { + var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; + var userJson = JSON.stringify(userObj, null, 2); + fs.writeFileSync(userFile, userJson); + }, + saveCustomer: function (customerObj) { + var orgObj = this.orgsBySlug[customerObj.org]; + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + var index = 0; + for (var i = 0; i < orgObj.customers.length; i++) { + var customer = orgObj.customers[i]; + if (customer._id == customerObj._id) { + orgObj.customers[i] = customerObj; + } + } + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); }, - - - saveUser: function (userObj) { - var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; - var userJson = JSON.stringify(userObj, null, 2); - fs.writeFileSync(userFile, userJson); + saveProduct: function (productObj) { + var orgObj = this.orgsByProduct[productObj.slug]; + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + var index = 0; + for (var i = 0; i < orgObj.products.length; i++) { + var product = orgObj.products[i]; + if (product.id == productObj.id) { + orgObj.products[i] = productObj; + } + } + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); }, - _loadAndCacheUsers: function () { - this.users = []; - this.usersByToken = {}; - this.usersByUsername = {}; - this.tokens = {}; - - - // list files, load all user objects, index by access_tokens and usernames - - if (!fs.existsSync(settings.userDataDir)) { - fs.mkdirSync(settings.userDataDir); - } - - - var files = fs.readdirSync(settings.userDataDir); - if (!files || (files.length == 0)) { - logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); - } - - for (var i = 0; i < files.length; i++) { - try { - - var filename = path.join(settings.userDataDir, files[i]); - var userObj = JSON.parse(fs.readFileSync(filename)); - - console.log("Loading user " + userObj.username); - this.addUser(userObj); - } - catch (ex) { - logger.error("RolesController - error loading user at " + filename); - } + _loadAndCacheUsers: function () { + this.users = []; + this.usersByToken = {}; + this.usersByDevice = {}; + this.usersByUsername = {}; + this.usersByClaimCode = {}; + + this.access_tokens = {}; + this.refresh_tokens = {}; + this.claim_codes = {}; + + this.devices = []; + this.clients = []; + this.orgsBySlug = {}; + this.orgsByUserId = {}; + + this.customers = []; + this.customersByEmail = {}; + //this.customersByToken = {}; + + this.orgsByProduct = {}; + this.products = {}; + + // list files, load all user objects, index by access_tokens and usernames + // and devices + if (!fs.existsSync(settings.userDataDir)) { + fs.mkdirSync(settings.userDataDir); + } + + var files = fs.readdirSync(settings.userDataDir); + if (!files || (files.length == 0)) { + logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); + } + + for (var i = 0; i < files.length; i++) { + try { + + var filename = path.join(settings.userDataDir, files[i]); + var userObj = JSON.parse(fs.readFileSync(filename)); + + console.log("Loading user " + userObj.username); + this.addUser(userObj); + } + catch (ex) { + logger.error("RolesController - error loading user at " + filename); + } + } + + var filenameClient = settings.oauthClientsFile; + var clients = JSON.parse(fs.readFileSync(filenameClient)); + for (var j = 0; j < clients.length; j++) { + try { + console.log("Loading client " + clients[j].client_id); + this.addClient(clients[j]); + } + catch (ex) { + logger.error("RolesController - error loading client at " + filenameOrg); + } + } + + var filesOrg = fs.readdirSync(settings.orgDataDir); + + for (var k = 0; k < filesOrg.length; k++) { + try { + + var filenameOrg = path.join(settings.orgDataDir, filesOrg[k]); + var orgObj = JSON.parse(fs.readFileSync(filenameOrg)); + + console.log("Loading org " + orgObj.slug); + this.addOrg(orgObj); + } + catch (ex) { + logger.error("RolesController - error loading org at " + filenameOrg); + } + } + }, + + getClient: function ( clientId, clientSecret) { + var clientObj = this.getClientByClientid(clientId); + if (clientObj.client_secret == clientSecret || clientId == 'particle') { + return clientObj; } + return false; }, - - getUserByToken: function (access_token) { - return this.usersByToken[access_token]; + getUserByClient: function ( clientId ) { + return this.getUserByUserid(this.orgsBySlug[clientId].user_id); }, - getUserByName: function (username) { - return this.usersByUsername[username]; + getUserByToken: function (access_token) { + return this.usersByToken[access_token]; + }, + getUserByDevice: function (deviceId) { + return this.usersByDevice[deviceId]; }, - getTokenInfoByToken: function (token) { - return this.tokens[token]; + getUserByClaimCode: function (claimCode) { + return this.usersByClaimCode[claimCode]; }, - getUserByUserid: function (userid) { - for (var i = 0; i < this.users.length; i++) { - var user = this.users[i]; - if (user._id == userid) { - return user; - } - } - return null; + getOrgByProductid: function (productid) { //ok + return this.orgsByProduct[productid]; }, - - - validateHashPromise: function (user, password) { - var tmp = when.defer(); - - PasswordHasher.hash(password, user.salt, function (err, hash) { - if (err) { - logger.error("hash error " + err); - tmp.reject("Bad password"); - } - else if (hash === user.password_hash) { - tmp.resolve(user); - } - else { - tmp.reject("Bad password"); - } - }); - - return tmp.promise; + getOrgBySlug: function (orgSlug) { //ok + return this.orgsBySlug[orgSlug]; }, - - - validateLogin: function (username, password) { - var userObj = this.getUserByName(username); - if (!userObj) { - return when.reject("Bad password"); - } - - return this.validateHashPromise(userObj, password); + getOrgByUserid: function (user_id) { //ok + return this.orgsByUserId[user_id]; + }, + getCustomerByEmail: function (email) { + return this.customersByEmail[email]; }, - createUser: function (username, password) { - var tmp = when.defer(); - var that = this; - - PasswordHasher.generateSalt(function (err, userid) { - userid = userid.toString('base64'); - userid = userid.substring(0, 32); - - PasswordHasher.generateSalt(function (err, salt) { - salt = salt.toString('base64'); - PasswordHasher.hash(password, salt, function (err, hash) { - var user = { - _id: userid, - username: username, - password_hash: hash, - salt: salt, - access_tokens: [] - }; - - var userFile = path.join(settings.userDataDir, username + ".json"); - fs.writeFileSync(userFile, JSON.stringify(user)); - - that.addUser(user); - - tmp.resolve(); - }); - }); - }); + getUserByName: function (username) { + return this.usersByUsername[username]; + }, + getTokenInfoByAccessToken: function (token) { + var tokenObj = this.access_tokens[token]; + if(!tokenObj) { + return false; + } + return { + accessToken: tokenObj.token, + client: tokenObj.client, + user: this.getUserByToken(tokenObj.token)._id, + refreshToken: tokenObj.refresh_token, + accessTokenExpiresAt: new Date(tokenObj.expires_at), + scope: tokenObj.scope + }; + }, + getTokenInfoByRefreshToken: function (token) { + var tokenObj = this.refresh_tokens[token]; + if(!tokenObj) { + return false; + } + return { + accessToken: tokenObj.token, + client: tokenObj.client, + user: this.getUserByToken(tokenObj.token)._id, + refreshToken: tokenObj.refresh_token, + refreshTokenExpiresAt: new Date(tokenObj.expires_at), + //refreshTokenExpiresAt not managed return accessTokenExpires + scope: tokenObj.scope + }; + }, + getUserByUserid: function (userid) { + for (var i = 0; i < this.users.length; i++) { + var user = this.users[i]; + if (user._id == userid) { + return user; + } + } + return null; + }, + + getProductByProductid: function (productid) { + var orgObj = this.orgsByProduct[productid]; + for (var i = 0; i < this.products[orgObj.slug].length; i++) { + var product = this.products[orgObj.slug][i]; + if (product.product_id == productid || product.slug == productid) { + return product; + } + } + return null; + }, + + getProductByUserid: function (userid) { + var orgObj = this.orgsByProduct[productid]; + for (var i = 0; i < this.products[orgObj.slug].length; i++) { + var product = this.products[orgObj.slug][i]; + if (product.product_id == productid || product.slug == productid) { + return product; + } + } + return null; + }, + + getCustomerByCustomerid: function (customerid) { + for (var i = 0; i < this.customers.length; i++) { + var customer = this.customers[i]; + if (customer._id == customerid) { + return customer; + } + } + return null; + }, + + getClientByClientid: function (clientId) { + for (var i = 0; i < this.clients.length; i++) { + var client = this.clients[i]; + if (client.client_id == clientId) { + return client; + } + } + return null; + }, + + validateHashPromise: function (user, password) { + var tmp = when.defer(); + + PasswordHasher.hash(password, user.salt, function (err, hash) { + if (err) { + logger.error("hash error " + err); + tmp.reject("Bad password"); + } + else if (hash === user.password_hash) { + tmp.resolve(user); + } + else { + tmp.reject("Bad password"); + } + }); + + return tmp.promise; + }, + + validateLogin: function (username, password) { + var userObj = this.getUserByName(username); + if (!userObj) { + return when.reject("Bad password"); + } + + return this.validateHashPromise(userObj, password); + }, + + validateClient: function (clientId, clientSecret) { + var tmp = when.defer(); + + var clientObj = this.getClient(clientId, clientSecret); + if (!clientObj) { + return tmp.reject("Bad client"); + } + + tmp.resolve(clientObj); + + return tmp.promise; + }, + + createUser: function (username, password) { + var tmp = when.defer(); + var that = this; + + PasswordHasher.generateSalt(function (err, userid) { + userid = userid.toString('base64'); + userid = userid.substring(0, 32); + + PasswordHasher.generateSalt(function (err, salt) { + salt = salt.toString('base64'); + PasswordHasher.hash(password, salt, function (err, hash) { + var user = { + _id: userid, + username: username, + password_hash: hash, + salt: salt, + access_tokens: [], + claim_codes: [], + devices: [] + }; + + var userFile = path.join(settings.userDataDir, username + ".json"); + fs.writeFileSync(userFile, JSON.stringify(user)); + + that.addUser(user); + + tmp.resolve(); + }); + }); + }); + + return tmp.promise; + }, + + createCustomer: function (clientObj, /*product,*/ orgSlug, email) { + var tmp = when.defer(); + var that = this; + + var orgObj = that.getOrgBySlug(orgSlug); + if(orgObj && orgObj.slug == clientObj.client_id) { + var customer = that.getCustomerByEmail(email); + if(!customer) { + + PasswordHasher.generateSalt(function (err, customerid) { + customerid = customerid.toString('base64'); + customerid = customerid.substring(0, 32); + + var customer = { + _id: customerid, + email: email, + org: orgObj.slug, + access_tokens: [], + claim_codes: [], + devices: [] + }; + + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + orgObj.customers.push(customer); + + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); + + that.addCustomer(customer); + + tmp.resolve(); + }); + } else { + tmp.reject('Customer '+email+' already exists'); + } + } else { + tmp.reject('Bad product'); + } - return tmp.promise; - } + return tmp.promise; + } }; -module.exports = global.roles = new RolesController(); \ No newline at end of file +module.exports = global.roles = new RolesController(); diff --git a/lib/utilities.js b/lib/utilities.js index bfa5b69d..683539cf 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -25,358 +25,367 @@ var fs = require('fs'); var that; module.exports = that = { - /** - * ensures the function in the provided scope - * @param fn - * @param scope - * @returns {Function} - */ - proxy: function (fn, scope) { - return function () { - try { - return fn.apply(scope, arguments); - } - catch (ex) { - logger.error(ex); - logger.error(ex.stack); - logger.log('error bubbled up ' + ex); - } - } - }, - - /** - * Surely there is a better way to do this. - * NOTE! This function does NOT short-circuit when an in-equality is detected. This is - * to avoid timing attacks. - * @param left - * @param right - */ - bufferCompare: function (left, right) { - if ((left == null) && (right == null)) { - return true; - } - else if ((left == null) || (right == null)) { - return false; - } - - if (!Buffer.isBuffer(left)) { - left = new Buffer(left); - } - if (!Buffer.isBuffer(right)) { - right = new Buffer(right); - } - - logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); - - var same = (left.length == right.length), - i = 0, - max = left.length; - - while (i < max) { - same &= (left[i] == right[i]); - i++; - } - - return same; - }, - - /** - * Iterates over the properties of the right object, checking to make - * sure the properties on the left object match. - * @param left - * @param right - */ - leftHasRightFilter: function (left, right) { - if (!left && !right) { - return true; - } - var matches = true; - - for (var prop in right) { - if (!right.hasOwnProperty(prop)) { - continue; - } - matches &= (left[prop] == right[prop]); - } - return matches; - }, - - promiseDoFile: function (filename, callback) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - if (callback(data)) { - deferred.resolve(); - } - } - catch(ex) { - deferred.reject(ex); - } - - }); - } - }); - return deferred; - }, - - promiseGetJsonFile: function (filename) { - var deferred = when.defer(); - - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - var obj = JSON.parse(data); - deferred.resolve(obj); - } - catch(ex) { - logger.error("Error parsing " + filename + " " + ex); - deferred.reject(ex); - } - }); - } - }); - return deferred; - }, - - promiseStreamFile: function (filename) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - var readStream = fs.createReadStream(filename); - - //TODO: catch can't read file stuff. - - deferred.resolve(readStream); - } - }); - return deferred; - }, - - bufferToHexString: function(buf) { - if (!buf || (buf.length <= 0)) { return null; } - - var r = []; - for(var i=0;i= 0) { - return filename.substr(idx); - } - else { - return filename; - } - }, - filenameNoExt: function (filename) { - if (!filename || (filename.length === 0)) { - return filename; - } - - var idx = filename.lastIndexOf('.'); - if (idx >= 0) { - return filename.substr(0, idx); - } - else { - return filename; - } - }, - - indexOf: function (arr, val) { - if (!arr || (arr.length == 0)) { - return -1; - } - for (var i = 0; i < arr.length; i++) { - if (arr[i] == val) { - return i; - } - } - return -1; - }, - contains: function (arr, val) { - return (that.indexOf(arr, val) !== -1); - }, - pipeDeferred: function(left, right) { - when(left).then(function() { - right.resolve.apply(right, arguments); - }, function() { - right.reject.apply(right, arguments); - }) - }, - - /** - * Non-competitive version of when.any - * @param arr - */ - deferredAny: function (arr) { - var tmp = when.defer(); - var index = -1; - var reasons = []; - - //step through a list of FUNCTIONS that return deferreds, - //process in order and resolve with the first one that resolves. - - var doNext = function () { - index++; - if (index > arr.length) { - tmp.reject(reasons); - return; - } - else if (!arr[index]) { - process.nextTick(doNext); - return; - } - - var promise = null; - try { - promise = arr[index](); - } - catch (ex) { - logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); - } - - if (promise) { - when(promise).then( - function () { - //chain this forward and resolve! we're done! - tmp.resolve.apply(tmp, arguments); - }, - function (err) { - reasons.push(err); - - //lets try the next one! - process.nextTick(doNext); - }); - } - else { - process.nextTick(doNext); - } - }; - - process.nextTick(doNext); - return tmp.promise; - }, - - check_requires_update: function(device, target) { - var version = (device && device["cc3000_patch_version"]); - return (version && (version < target)); - }, - - getIPAddresses: function () { - //adapter = adapter || "eth0"; - var results = []; - var nics = os.networkInterfaces(); - - for (var name in nics) { - var nic = nics[name]; - - for (var i = 0; i < nic.length; i++) { - var addy = nic[i]; - - if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { - continue; - } - - results.push(addy.address); - } - } - - return results; - }, - - foo: null + /** + * ensures the function in the provided scope + * @param fn + * @param scope + * @returns {Function} + */ + proxy: function (fn, scope) { + return function () { + try { + return fn.apply(scope, arguments); + } + catch (ex) { + logger.error(ex); + logger.error(ex.stack); + logger.log('error bubbled up ' + ex); + } + } + }, + + /** + * Surely there is a better way to do this. + * NOTE! This function does NOT short-circuit when an in-equality is detected. This is + * to avoid timing attacks. + * @param left + * @param right + */ + bufferCompare: function (left, right) { + if ((left == null) && (right == null)) { + return true; + } + else if ((left == null) || (right == null)) { + return false; + } + + if (!Buffer.isBuffer(left)) { + left = new Buffer(left); + } + if (!Buffer.isBuffer(right)) { + right = new Buffer(right); + } + + logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); + + var same = (left.length == right.length), + i = 0, + max = left.length; + + while (i < max) { + same &= (left[i] == right[i]); + i++; + } + + return same; + }, + + /** + * Iterates over the properties of the right object, checking to make + * sure the properties on the left object match. + * @param left + * @param right + */ + leftHasRightFilter: function (left, right) { + if (!left && !right) { + return true; + } + var matches = true; + + for (var prop in right) { + if (!right.hasOwnProperty(prop)) { + continue; + } + matches &= (left[prop] == right[prop]); + } + return matches; + }, + + promiseDoFile: function (filename, callback) { + var deferred = when.defer(); + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + fs.readFile(filename, function (err, data) { + if (err) { + logger.error("error reading " + filename, err); + deferred.reject(); + } + + try { + if (callback(data)) { + deferred.resolve(); + } + } + catch(ex) { + deferred.reject(ex); + } + + }); + } + }); + return deferred; + }, + + promiseGetJsonFile: function (filename) { + var deferred = when.defer(); + + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + fs.readFile(filename, function (err, data) { + if (err) { + logger.error("error reading " + filename, err); + deferred.reject(); + } + + try { + var obj = JSON.parse(data); + deferred.resolve(obj); + } + catch(ex) { + logger.error("Error parsing " + filename + " " + ex); + deferred.reject(ex); + } + }); + } + }); + return deferred; + }, + + promiseStreamFile: function (filename) { + var deferred = when.defer(); + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + var readStream = fs.createReadStream(filename); + + //TODO: catch can't read file stuff. + + deferred.resolve(readStream); + } + }); + return deferred; + }, + + bufferToHexString: function(buf) { + if (!buf || (buf.length <= 0)) { return null; } + + var r = []; + for(var i=0;i= 0) { + return filename.substr(idx); + } + else { + return filename; + } + }, + filenameNoExt: function (filename) { + if (!filename || (filename.length === 0)) { + return filename; + } + + var idx = filename.lastIndexOf('.'); + if (idx >= 0) { + return filename.substr(0, idx); + } + else { + return filename; + } + }, + + indexOf: function (arr, val) { + if (!arr || (arr.length == 0)) { + return -1; + } + for (var i = 0; i < arr.length; i++) { + if (arr[i] == val) { + return i; + } + } + return -1; + }, + contains: function (arr, val) { + return (that.indexOf(arr, val) !== -1); + }, + pipeDeferred: function(left, right) { + when(left).then(function() { + right.resolve.apply(right, arguments); + }, function() { + right.reject.apply(right, arguments); + }) + }, + + /** + * Non-competitive version of when.any + * @param arr + */ + deferredAny: function (arr) { + var tmp = when.defer(); + var index = -1; + var reasons = []; + + //step through a list of FUNCTIONS that return deferreds, + //process in order and resolve with the first one that resolves. + + var doNext = function () { + index++; + if (index > arr.length) { + tmp.reject(reasons); + return; + } + else if (!arr[index]) { + process.nextTick(doNext); + return; + } + + var promise = null; + try { + promise = arr[index](); + } + catch (ex) { + logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); + } + + if (promise) { + when(promise).then( + function () { + //chain this forward and resolve! we're done! + tmp.resolve.apply(tmp, arguments); + }, + function (err) { + reasons.push(err); + + //lets try the next one! + process.nextTick(doNext); + }); + } + else { + process.nextTick(doNext); + } + }; + + process.nextTick(doNext); + return tmp.promise; + }, + + check_requires_update: function(device, target) { + var version = (device && device["cc3000_patch_version"]); + return (version && (version < target)); + }, + + getIPAddresses: function () { + //adapter = adapter || "eth0"; + var results = []; + var nics = os.networkInterfaces(); + + for (var name in nics) { + var nic = nics[name]; + + for (var i = 0; i < nic.length; i++) { + var addy = nic[i]; + + if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { + continue; + } + + results.push(addy.address); + } + } + + return results; + }, + + slugify: function (text) { + return text.toString().toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text + }, + + foo: null }; \ No newline at end of file diff --git a/oauth_clients.json b/oauth_clients.json new file mode 100644 index 00000000..e7e46d54 --- /dev/null +++ b/oauth_clients.json @@ -0,0 +1,12 @@ +[ + { + "client_id" : "particle", + "client_secret" : "particle", + "grants" : [ "password", "refresh_token" ] + }, + { + "client_id" : "CLI2", + "client_secret" : "client_secret_here", + "grants" : [ "password", "refresh_token" ] + } +] diff --git a/js/orgs/company.json b/orgs/company.json similarity index 100% rename from js/orgs/company.json rename to orgs/company.json diff --git a/package.json b/package.json index ed9b2772..24a7c73e 100644 --- a/package.json +++ b/package.json @@ -1,43 +1,46 @@ { - "name": "spark-server", - "version": "0.1.1", - "license": "AGPL-3.0", - "repository": { - "type": "git", - "url": "https://github.com/spark/spark-server" - }, - "homepage": "https://github.com/spark/spark-server", - "bugs": "https://github.com/spark/spark-server/issues", - "author": { - "name": "David Middlecamp", - "email": "david@spark.io", - "url": "https://www.spark.io/" - }, - "dependencies": { - "spark-protocol": "https://github.com/Brewskey/spark-protocol", - "express": "~3.4.4", - "hogan-express": "~0.5.1", - "request": "*", - "node-oauth2-server": "~1.5.3", - "ursa": "*", - "when": "*", - "moment": "*", - "xtend": "*" - }, - "scripts": { - "start": "node main.js" - }, - "main": "main.js", - "contributors": [ - { - "name": "Kenneth Lim", - "email": "kennethlimcp@gmail.com", - "url": "https://github.com/kennethlimcp" - }, - { - "name": "Emily Rose", - "email": "emily@spark.io", - "url": "https://github.com/emilyrose" - } - ] + "name": "spark-server", + "version": "0.1.1", + "license": "AGPL-3.0", + "repository": { + "type": "git", + "url": "https://github.com/spark/spark-server" + }, + "homepage": "https://github.com/spark/spark-server", + "bugs": "https://github.com/spark/spark-server/issues", + "author": { + "name": "David Middlecamp", + "email": "david@spark.io", + "url": "https://www.spark.io/" + }, + "dependencies": { + "body-parser": "^1.15.2", + "express-oauth-server": "^2.0.0-b1", + "morgan": "^1.7.0", + "spark-protocol": "https://github.com/Brewskey/spark-protocol", + "express": "~3.4.4", + "hogan-express": "~0.5.1", + "request": "*", + "node-oauth2-server": "~1.5.3", + "ursa": "*", + "when": "*", + "moment": "*", + "xtend": "*" + }, + "scripts": { + "start": "node main.js" + }, + "main": "main.js", + "contributors": [ + { + "name": "Kenneth Lim", + "email": "kennethlimcp@gmail.com", + "url": "https://github.com/kennethlimcp" + }, + { + "name": "Emily Rose", + "email": "emily@spark.io", + "url": "https://github.com/emilyrose" + } + ] } diff --git a/views/EventViews001.js b/views/EventViews001.js index aab9ece8..b2db7679 100644 --- a/views/EventViews001.js +++ b/views/EventViews001.js @@ -30,247 +30,309 @@ var pipeline = require('when/pipeline'); var moment = require('moment'); var EventsApi = { - loadViews: function (app) { - - // GET /v1/events[/:event_name] - // GET /v1/devices/events[/:event_name] - // GET /v1/devices/:device_id/events[/:event_name] - - app.get('/v1/events', EventsApi.get_events); - app.get('/v1/events/:event_name', EventsApi.get_events); - - app.get('/v1/devices/events', EventsApi.get_my_events); - app.post('/v1/devices/events', EventsApi.send_an_event); - app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); - - app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); - app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); - }, - - - //----------------------------------------------------------------- - - pipeEvents: function (socket, req, res, filterCoreId) { - var userid = Api.getUserID(req); - - /* - Start SSE - */ - - req.socket.setNoDelay(); - - res.writeHead(200, { - "Connection": "keep-alive", - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache" - }); - res.write(":ok\n\n"); - - - var _idleTimer = null; - var _lastMessage = null; - var keepAlive = function() { - if (((new Date()) - _lastMessage) >= 9000) { - _lastMessage = new Date(); - res.write("\n"); - checkSocket(); - } - }; - - //if nothing gets sent for 9 seconds, send a newline. - var aliveInterval = setInterval(keepAlive, 3000); - - var checkSocket = function () { - try { - if (!socket) { - cleanup(); - return false; - } - - if (res.socket.destroyed) { - logger.log("Socket destroyed, cleaning up Event listener"); - cleanup(); - return false; - } - } - catch (ex) { - logger.error("pipeEvents - error checking socket ", ex); - } - return true; - }; - - var cleanup = function () { - try { - if (socket) { - socket.close(); - socket = null; - } - } - catch (ex) { - logger.error("pipeEvents - event socket close err: ", ex); - } - - try { - if (res.socket) { - res.socket.end(); - } - res.end(); - } - catch (ex) { - logger.error("pipeEvents - response close err: ", ex); - } - - try { - if (aliveInterval) { - clearInterval(aliveInterval); - aliveInterval = null; - } - } - catch (ex) { - logger.error("pipeEvents - clear interval err: ", ex); - } - - }; - - - // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events - var writeEventGen = function (isPublic) { - return function (name, data, ttl, published_at, coreid) { - if (filterCoreId && (filterCoreId != coreid)) { - return; - } - - if (!checkSocket()) { - return; - } - - try { - _lastMessage = new Date(); - - //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. - name = (name) ? name.toString().replace(userid + "/", "") : null; - - var obj = { - data: data ? data.toString() : null, - ttl: ttl ? ttl.toString() : null, - published_at: (published_at) ? published_at.toString() : null, - coreid: (coreid) ? coreid.toString() : null - }; - res.write("event: " + name + "\n"); - res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies - } - catch (ex) { - logger.error("pipeEvents - write error: " + ex); - } - - //OTHER HEADERS: - //retry: ? - //id: ? //if we want to support resuming - //TODO: escape newlines in message? - }; - }; - - socket.on('public', writeEventGen(true)); - socket.on('private', writeEventGen(false)); - - req.on("close", cleanup); - req.on("end", cleanup); - res.on("close", cleanup); - res.on("finish", cleanup); - //res.setTimeout(30 * 1000, cleanup); - }, - - - get_events: function (req, res) { - var name = req.param('event_name'); - name = name || ""; - var socket = new CoreController(); - - var userid = Api.getUserID(req); + loadViews: function (app) { + + // GET /v1/events[/:event_name] + // GET /v1/devices/events[/:event_name] + // GET /v1/devices/:device_id/events[/:event_name] + + app.get('/v1/events', EventsApi.get_events); + app.get('/v1/events/:event_name', EventsApi.get_events); + + app.get('/v1/devices/events', EventsApi.get_my_events); + app.post('/v1/devices/events', EventsApi.send_an_event); + app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); + + app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); + app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); + + app.get('/v1/products/:productIdOrSlug/events', app.oauth.authenticate(), EventsApi.get_product_events); + }, + + + //----------------------------------------------------------------- + + pipeEvents: function (socket, req, res, next, filterCoreId, filterProductId) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + /* + Start SSE + */ + + req.socket.setNoDelay(); + + res.writeHead(200, { + "Connection": "keep-alive", + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no" + }); + res.write(":ok\n\n"); + + + var _idleTimer = null; + var _lastMessage = null; + var keepAlive = function() { + if (((new Date()) - _lastMessage) >= 9000) { + _lastMessage = new Date(); + res.write("\n"); + checkSocket(); + } + }; + + //if nothing gets sent for 9 seconds, send a newline. + var aliveInterval = setInterval(keepAlive, 3000); + + var checkSocket = function () { + try { + if (!socket) { + cleanup(); + return false; + } + + if (res.socket.destroyed) { + logger.log("Socket destroyed, cleaning up Event listener"); + cleanup(); + return false; + } + } + catch (ex) { + logger.error("pipeEvents - error checking socket ", ex); + } + return true; + }; + + var cleanup = function () { + try { + if (socket) { + socket.close(); + socket = null; + } + } + catch (ex) { + logger.error("pipeEvents - event socket close err: ", ex); + } + + try { + if (res.socket) { + res.socket.end(); + } + res.end(); + } + catch (ex) { + logger.error("pipeEvents - response close err: ", ex); + } + + try { + if (aliveInterval) { + clearInterval(aliveInterval); + aliveInterval = null; + } + } + catch (ex) { + logger.error("pipeEvents - clear interval err: ", ex); + } + + }; + + + // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events + var writeEventGen = function (isPublic) { + return function (name, data, ttl, published_at, coreid) { + if (filterCoreId && (filterCoreId != coreid)) { + return; + } + + if (!checkSocket()) { + return; + } + + if(filterProductId == null && !Api.hasDevice(coreid, userid)) { + return; + } + + if(filterProductId != null && !Api.hasProduct(filterProductId, userid)) { + return; + } + + try { + _lastMessage = new Date(); + + //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. + name = (name) ? name.toString().replace(userid + "/", "") : null; + + var obj = { + data: data ? data.toString() : null, + ttl: ttl ? ttl.toString() : null, + published_at: (published_at) ? published_at.toString() : null, + coreid: (coreid) ? coreid.toString() : null + }; + res.write("event: " + name + "\n"); + res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies + } + catch (ex) { + logger.error("pipeEvents - write error: " + ex); + } + + //OTHER HEADERS: + //retry: ? + //id: ? //if we want to support resuming + //TODO: escape newlines in message? + }; + }; + + socket.on('public', writeEventGen(true)); + socket.on('private', writeEventGen(false)); + + req.on("close", cleanup); + req.on("end", cleanup); + res.on("close", cleanup); + res.on("finish", cleanup); + //res.setTimeout(30 * 1000, cleanup); + }, + + + get_events: function (req, res, next) { + var name = req.params.event_name; + name = name || ""; + var socket = new CoreController(); + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } // if (userid) { // socket.authorize(userid); // } - //----------------------------------- - //get firehose and my private events. - //socket.subscribe(true, name); - socket.subscribe(true, name); - socket.subscribe(false, name, userid); - - - //send it all through - EventsApi.pipeEvents(socket, req, res); - }, - get_my_events: function (req, res) { - var name = req.param('event_name'); - name = name || ""; - var socket = new CoreController(); + //----------------------------------- + //get firehose and my private events. + //socket.subscribe(true, name); + //socket.subscribe(true, name); + socket.subscribe(false, name, userid); + + + //send it all through + EventsApi.pipeEvents(socket, req, res, next); + }, + get_my_events: function (req, res, next) { + var name = req.params.event_name; + name = name || ""; + var socket = new CoreController(); + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } +// if (userid) { +// socket.authorize(userid); +// } - var userid = Api.getUserID(req); + //----------------------------------- + //get my events: + //socket.subscribe(true, name); + socket.subscribe(true, name, userid); + socket.subscribe(false, name, userid); + + //don't filter by core id + EventsApi.pipeEvents(socket, req, res, next); + }, + get_core_events: function (req, res, next) { + var name = req.params.event_name; + var socket = new CoreController(); + name = name || ""; + var coreid = req.coreID || req.params.coreid; + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + //check if core is owned + if(!Api.hasDevice(coreid, userid)) { + return next(); + } // if (userid) { // socket.authorize(userid); // } - //----------------------------------- - //get my events: - //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); - - //don't filter by core id - EventsApi.pipeEvents(socket, req, res); - }, - get_core_events: function (req, res) { - var name = req.param('event_name'); - var socket = new CoreController(); - name = name || ""; - var coreid = req.coreID || req.param('coreid'); - - var userid = Api.getUserID(req); + + //----------------------------------- + //get core events + //socket.subscribe(true, name); + socket.subscribe(true, name, userid,coreid); + socket.subscribe(false, name, userid,coreid); + + //----------------------------------- + //filter to core id + EventsApi.pipeEvents(socket, req, res, next, coreid); + }, + + + send_an_event: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req), + socketID = Api.getSocketID(userid), + eventName = req.body.name, + data = req.body.data, + ttl = req.body.ttl || 60, + private_str = req.body.private; + + if(!userid) { + return next(); + } + + var is_public = (!private_str || (private_str == "") || (private_str == "false")); + + var socket = new CoreController(socketID); + var success = socket.sendEvent(is_public, + eventName, + userid, + data, + parseInt(ttl), + moment().toISOString(), + userid + ); + + var autoClose = setTimeout(function () { + socket.close(); + res.json({ok: success}); + }, 250); + }, + + get_product_events: function (req, res, next) { + var name = req.params.event_name; + var socket = new CoreController(); + name = name || ""; + var productid = req.params.productIdOrSlug; + productid = productid || null; + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + //check if product is owned by user + if(!Api.hasProduct(productid, userid)) { + return next(); + } // if (userid) { // socket.authorize(userid); // } - //----------------------------------- - //get core events - //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); - - //----------------------------------- - //filter to core id - EventsApi.pipeEvents(socket, req, res, coreid); - }, - - - send_an_event: function (req, res) { - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - eventName = req.body.name, - data = req.body.data, - ttl = req.body.ttl || 60, - private_str = req.body.private; - - var is_public = (!private_str || (private_str == "") || (private_str == "false")); - - var socket = new CoreController(socketID); - var success = socket.sendEvent(is_public, - eventName, - userid, - data, - parseInt(ttl), - moment().toISOString(), - userid - ); - - var autoClose = setTimeout(function () { - socket.close(); - res.json({ok: success}); - }, 250); - }, - - _: null + //----------------------------------- + //get product events + //socket.subscribe(true, name); + socket.subscribe(true, name, userid); + socket.subscribe(false, name, userid); + + //----------------------------------- + //filter to core id + EventsApi.pipeEvents(socket, req, res, next, null, productid); + }, + + _: null }; diff --git a/views/api_v1.js b/views/api_v1.js index 53e66de7..d1b093b6 100644 --- a/views/api_v1.js +++ b/views/api_v1.js @@ -19,7 +19,7 @@ var settings = require('../settings.js'); var CoreController = require('../lib/CoreController.js'); -var roles = require('../lib/RolesController.js'); +var PasswordHasher = require('../lib/PasswordHasher.js'); var sequence = require('when/sequence'); var parallel = require('when/parallel'); @@ -35,6 +35,9 @@ var path = require('path'); var ursa = require('ursa'); var moment = require('moment'); +var multipart = require('connect-multiparty'); +var multipartMiddleware = multipart(); + /* * TODO: modularize duplicate code * TODO: implement proper session handling / user authentication @@ -43,646 +46,1149 @@ var moment = require('moment'); */ var Api = { - loadViews: function (app) { - - //our middleware - app.param("coreid", Api.loadCore); - - - //core functions / variables - app.post('/v1/devices/:coreid/:func', Api.fn_call); - app.get('/v1/devices/:coreid/:var', Api.get_var); - - app.put('/v1/devices/:coreid', Api.set_core_attributes); - app.get('/v1/devices/:coreid', Api.get_core_attributes); - - //doesn't need per-core permissions, only shows owned cores. - app.get('/v1/devices', Api.list_devices); - - app.post('/v1/provisioning/:coreid', Api.provision_core); - - //app.delete('/v1/devices/:coreid', Api.release_device); - app.post('/v1/devices', Api.claim_device); - - }, - - getSocketID: function (userID) { - return userID + "_" + global._socket_counter++; - }, - - getUserID: function (req) { - if (!req.user) { - logger.log("User obj was empty"); - return null; + loadViews: function (app) { + + //our middleware + app.param("coreid", Api.loadCore); + + + //core functions / variables + app.post('/v1/devices/:coreid/:func', Api.fn_call); + app.get('/v1/devices/:coreid/:var', Api.get_var); + + app.put('/v1/devices/:coreid', multipartMiddleware, Api.set_core_attributes); + app.get('/v1/devices/:coreid', Api.get_core_attributes);//ok customer + + //doesn't need per-core permissions, only shows owned cores. + app.get('/v1/devices', Api.list_devices);//ok customer + + app.post('/v1/provisioning/:coreid', Api.provision_core); + + app.delete('/v1/devices/:coreid', Api.release_device); + + app.post('/v1/devices', Api.claim_device); + app.post('/v1/device_claims', app.oauth.authenticate(), Api.get_claim_code); + + /*products*/ + app.get('/v1/products', app.oauth.authenticate(), Api.list_products); + app.get('/v1/products/:productIdOrSlug', app.oauth.authenticate(), Api.get_product); + app.post('/v1/products/:productIdOrSlug/device_claims', app.oauth.authenticate(), Api.get_product_claim_code); + app.delete('/v1/products/:productIdOrSlug/devices/:coreid', app.oauth.authenticate(), Api.release_product_device); + app.get('/v1/products/:productIdOrSlug/customers', app.oauth.authenticate(), Api.get_product_customers); + + app.post('/v1/products/:productIdOrSlug/devices', app.oauth.authenticate(), Api.add_product_device); + }, + + getSocketID: function (userID) { + return userID + "_" + global._socket_counter++; + }, + + getUserID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); + return false; + } + if(req.app.locals.oauth.token.scope && req.app.locals.oauth.token.scope.indexOf("customer=") > -1) { + logger.log("Customer token"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + getCustomerID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); + return false; + } + if(!req.app.locals.oauth.token.scope || req.app.locals.oauth.token.scope.indexOf("customer=") == -1) { + logger.log("User token"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + getUserOrCustomerID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + hasDevice: function (coreID, userID) { + var userObj = global.roles.getUserByDevice(coreID); + //check core permission + if(userObj && userObj._id == userID) { + return true; + } else { + //logger.log("device Permission Denied"); + return false; + } + }, + + hasOrg: function (userID) { + var orgObj = global.roles.getOrgByUserid(userID); + //check user permission + if(orgObj) { + return true; + } else { + return false; + } + }, + + hasProduct: function (productIdOrSlug, userID) { + var orgObj = global.roles.getOrgByProductid(productIdOrSlug); + //check user permission + if(orgObj && orgObj.user_id == userID) { + return true; + } else { + return false; + } + }, + + list_devices: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + logger.log("ListDevices", { userID: userid }); + + //give me all the cores + + //var allCoreIDs = global.server.getAllCoreIDs(), + var userDevicesIDs = global.roles.devices[userid], + devices = [], + connected_promises = []; + + for (var index in userDevicesIDs) { + var coreid = userDevicesIDs[index]; + + if (!coreid) { + continue; + } + + var core = global.server.getCoreAttributes(coreid); + + var device = { + id: coreid, + name: core ? core.name : null, + last_app: core ? core.last_flashed_app_name : null, + product_id: core ? core.spark_product_id : null, + firmware_version: core ? core.product_firmware_version : null, + system_version: core ? core.spark_system_version : null, + last_heard: null + }; + + if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { + device["requires_deep_update"] = true; + } + + devices.push(device); + connected_promises.push(Api.isDeviceOnline(userid, device.id)); + } + + logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); + + //switched 'done' to 'then' - threw an exception with 'done' here. + when.settle(connected_promises).then(function (descriptors) { + for (var i = 0; i < descriptors.length; i++) { + var desc = descriptors[i]; + devices[i].connected = ('rejected' !== desc.state); + devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; + } + + res.status(200).json(devices); + }); + }, + + get_core_attributes: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + var socketID = Api.getSocketID(userid), + coreID = req.coreID, + socket = new CoreController(socketID); + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.user.id; - }, - list_devices: function (req, res) { + logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); + + var objReady = parallel([ + function () { + return when.resolve(global.server.getCoreAttributes(coreID)); + }, + function () { + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); + }, + function () { + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Ping" }, { cmd: "Pong" })); + } + ]); + + //whatever we get back... + when(objReady).done(function (results) { + try { + + if (!results || (results.length != 3)) { + logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); + res.status(404).json("Oops, I couldn't find that core"); + return; + } + + //we're expecting descResult to be an array: [ sender, {} ] + var doc = results[0], + descResult = results[1], + coreState = null, + descPingResult = results[2]; + + if (!doc || !doc.coreID) { + logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); + res.status(404).json("Oops, I couldn't find that core"); + return; + } + + if (util.isArray(descResult) && (descResult.length > 1)) { + coreState = descResult[1].state || {}; + } + if (!coreState) { + logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); + } + + var device = { + id: doc.coreID, + name: doc.name || null, + last_app: doc.last_flashed, + product_id: doc.spark_product_id || null, + firmware_version: doc.product_firmware_version || null, + system_version: doc.spark_system_version || null, + //connected: !!coreState, + connected: (descPingResult != "Request Timed Out") ? descPingResult[1].online : false, + last_heard: (descPingResult != "Request Timed Out") ? descPingResult[1].lastPing : null, + variables: (coreState) ? coreState.v : null, + functions: (coreState) ? coreState.f : null, + cc3000_patch_version: doc.cc3000_driver_version + }; + + if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { + device["requires_deep_update"] = true; + } + + res.json(device); + } + catch (ex) { + logger.error("get_core_attributes merge error: " + ex); + res.status(500).json({ Error: "get_core_attributes error: " + ex }); + } + }, null); + + //get_core_attribs - end + }, + + set_core_attributes: function (req, res, next) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + if(!Api.hasDevice(coreID, userid) && !Api.hasOrg(userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + var promises = []; + + logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); + + var coreName = req.body ? req.body.name : null; + if (coreName != null) { + logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); + + global.server.setCoreAttribute(req.coreID, "name", coreName); + promises.push(when.resolve({ ok: true, name: coreName })); + } + + var hasFiles = req.files && req.files.file; + if (hasFiles) { + console.log("file"); + //oh hey, you want to flash firmware? + promises.push(Api.compile_and__or_flash_dfd(req)); + } + + var signal = req.body && req.body.signal; + if (signal) { + //get your hands up in the air! Or down. + promises.push(Api.core_signal_dfd(req)); + } + + var flashApp = req.body ? req.body.app : null; + if (flashApp) { + // It makes no sense to flash a known app and also + // either signal or flash a file sent with the request + if (!hasFiles && !signal) { + + // MUST sanitize app name here, before sending to Device Service + if (utilities.contains(settings.known_apps, flashApp)) { + promises.push(Api.flash_known_app_dfd(req)); + } + else { + promises.push(when.reject("Can't flash unknown app " + flashApp)); + } + } + } + + var app_id = req.body ? req.body.app_id : null; + if (app_id && !hasFiles && !signal && !flashApp) { + //we have an app id, and no files, and stuff + //we must be flashing from the db! + promises.push(Api.flash_app_in_db_dfd(req)); + } + + var app_example_id = req.body ? req.body.app_example_id : null; + if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { + //we have an app id, and no files, and stuff + //we must be flashing from the db! + promises.push(Api.flash_example_app_in_db_dfd(req)); + } + + + if (promises.length >= 1) { + when.all(promises).done( + function (results) { + var aggregate = {}; + for (var i in results) { + for (var key in results[i]) { + aggregate[key] = results[i][key]; + } + } + res.json(aggregate); + }, + function (err) { + res.json({ ok: false, errors: [err] }); + } + ); + } + else { + logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); + res.json({error: "Nothing to do?"}); + } + }, + + isDeviceOnline: function (userID, coreID) { + var tmp = when.defer(); + + var socketID = Api.getSocketID(userID); + var socket = new CoreController(socketID); + + var failTimer = setTimeout(function () { + logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); + socket.close(); + tmp.reject("Device is not connected"); + }, settings.isCoreOnlineTimeout); + + + //setup listener for response back from the device service + socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { + clearTimeout(failTimer); + socket.close(); + + logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); + + if (msg && msg.online) { + tmp.resolve(msg); + } + else { + tmp.reject(["Core isn't online", 404]); + } + + }, true); + + logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); + + //send it along to the device service + if (!socket.send(coreID, { cmd: "Ping" })) { + tmp.reject("Device is not connected"); + } + + return tmp.promise; + }, + + get_claim_code: function (req, res, next) { var userid = Api.getUserID(req); - logger.log("ListDevices", { userID: userid }); - - //give me all the cores - - var allCoreIDs = global.server.getAllCoreIDs(), - devices = [], - connected_promises = []; - - for (var coreid in allCoreIDs) { - if (!coreid) { - continue; - } - - var core = global.server.getCoreAttributes(coreid); - - var device = { - id: coreid, - name: (core) ? core.name : null, - last_app: core ? core.last_flashed_app_name : null, - last_heard: null - }; - - if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - devices.push(device); - connected_promises.push(Api.isDeviceOnline(userid, device.id)); + if(!userid) { + return next(); } - - logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); - - //switched 'done' to 'then' - threw an exception with 'done' here. - when.settle(connected_promises).then(function (descriptors) { - for (var i = 0; i < descriptors.length; i++) { - var desc = descriptors[i]; - - devices[i].connected = ('rejected' !== desc.state); - devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; - } - - res.json(200, devices); + + logger.log("GenerateClaimCode", { userID: userid }); + + var userDevicesIDs = global.roles.devices[userid]; + PasswordHasher.generateSalt(function (err, code) { + code = code.toString('base64'); + code = code.substring(0, 63); + + when(global.roles.addClaimCode(code, userid)).then( + function () { + res.json({ + claim_code: code, + device_ids: userDevicesIDs + }); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); }); - }, - - get_core_attributes: function (req, res) { + }, + + claim_device: function (req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + var coreid = req.body.id; + var core = global.server.getCoreAttributes(coreid); + + if(coreid) { + + if(core.claimCode) { + var userObj = global.roles.getUserByClaimCode(core.claimCode); + + if(user) { + when(global.roles.addDevice(coreid, userObj)).then( + function () { + var claimInfo = { + user_id : userObj._id, + id: coreid, + connected: false, + ok: true + } + + when(Api.isDeviceOnline(userid, coreid)) + .then( + function (desc) { + claimInfo.connected = ('rejected' !== desc.state); + + global.server.setCoreAttribute(coreid, "claimed", true); + res.json(claimInfo); + }, + function (err) { + res.status(404).json({ + ok: false, + errors: [ + "Device is not connected" + ] + }); + } + ); + }, + function (err) { + res.status(403).json({ + ok: false, + errors: [ + "That belongs to someone else. To request a transfer add ?request_transfer=true to the URL." + ] + }); + } + ); + } else { + res.status(404).json({ + ok: false, + errors: [ + {} + ] + }); + } + } else { + res.status(404).json({ + ok: false, + errors: [ + {} + ] + }); + } + } else { + res.status(404).json({ + ok: false, + errors: [ + "data.deviceID is empty" + ] + }); + } + }, + + release_device: function (req, res, next) { + var coreID = req.coreID; var userid = Api.getUserID(req); - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - socket = new CoreController(socketID); - - - logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); - - var objReady = parallel([ - function () { - return when.resolve(global.server.getCoreAttributes(coreID)); - }, + if(!userid) { + return next(); + } + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + when(global.roles.removeDevice(coreID, userid)).then( function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); + global.server.setCoreAttribute(coreID, "claimed", false); + res.json({'ok' : true }); + }, function (err) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); } - ]); - - //whatever we get back... - when(objReady).done(function (results) { - try { - - if (!results || (results.length != 2)) { - logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); - res.json(404, "Oops, I couldn't find that core"); - return; - } - - - //we're expecting descResult to be an array: [ sender, {} ] - var doc = results[0], - descResult = results[1], - coreState = null; - - if (!doc || !doc.coreID) { - logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); - res.json(404, "Oops, I couldn't find that core"); - return; - } - - if (util.isArray(descResult) && (descResult.length > 1)) { - coreState = descResult[1].state || {}; - } - if (!coreState) { - logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); + ); + }, + + //called when the core send its claim code + linkDevice: function (coreid, claimCode, productid) { + var userObj = global.roles.getUserByClaimCode(claimCode); + if(userObj) { + logger.log("Linking Device...", { coreID: coreid }); + + if(userObj.org) { //if customer + //check if coreid is present in product devices + var productObj = global.roles.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, coreid); + if (index > -1) { + for (var i = 0; i < global.roles.claim_codes[userObj._id].length; i++) { + var claimCodeObj = global.roles.claim_codes[userObj._id][i]; + //check if the claim code is valid for the product + if (claimCodeObj.code == claimCode && claimCodeObj.product_id != productid) { + logger.error("Claim code not valid for product", { claimCode: claimCode }); + return false; + } + }; + } else { + logger.error("Device not found for product"); + return false; } - - var device = { - id: doc.coreID, - name: doc.name || null, - last_app: doc.last_flashed, - connected: !!coreState, - variables: (coreState) ? coreState.v : null, - functions: (coreState) ? coreState.f : null, - cc3000_patch_version: doc.cc3000_driver_version - }; - - if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; + } + + when(global.roles.addDevice(coreid, userObj)).then( + function () { + global.server.setCoreAttribute(coreid, "claimed", true); + logger.log("Device linked", { coreID: coreid }); + }, + function (err) { + logger.error("Error in linking Device: "+err, { coreID: coreid }); } - - res.json(device); - } - catch (ex) { - logger.error("get_core_attributes merge error: " + ex); - res.json(500, { Error: "get_core_attributes error: " + ex }); - } - }, null); - - //get_core_attribs - end + ); + } else { + logger.error("Claim code not valid", { claimCode: claimCode }); + } }, - - - set_core_attributes: function (req, res) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - - var promises = []; - - logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); - - var coreName = req.body ? req.body.name : null; - if (coreName != null) { - logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); - - global.server.setCoreAttribute(req.coreID, "name", coreName); - promises.push(when.resolve({ ok: true, name: coreName })); + + safeMode: function (coreID, description) { + + logger.log("Device is in SAFE MODE", {coreID: coreID}); + + global.server.publishSpecialEvents('spark/status/safe-mode', description, coreID); + + //# spark/safe-mode-updater/updating + //{"name":"spark/safe-mode-updater/updating","data":"2","ttl":"60","published_at":"2016-01-01T14:41:0.000Z","coreid":"particle-internal"} + }, + + loadCore: function (req, res, next) { + req.coreID = req.params.coreid || req.body.id; + + //load core info! + req.coreInfo = { + "last_app": "", + "last_heard": new Date(), + "connected": false, + "deviceID": req.coreID + }; + + //if that user doesn't own that coreID, maybe they sent us a core name + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + var gotCore = utilities.deferredAny([ + function () { + var core = global.server.getCoreAttributes(req.coreID); + if (core && core.coreID) { + return when.resolve(core); + } + else { + return when.reject(); + } + }, + function () { + var core = global.server.getCoreByName(req.coreID); + if (core && core.coreID) { + return when.resolve(core); + } + else { + return when.reject(); + } + } + ]); + + when(gotCore).then( + function (core) { + if (core) { + req.coreID = core.coreID || req.coreID; + req.coreInfo = { + last_handshake_at: core.last_handshake_at + }; + } + + next(); + }, + function (err) { + //s`okay. + next(); + }) + }, + + get_var: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + var socketID = Api.getSocketID(userid), + coreID = req.coreID, + varName = req.params.var, + format = req.params.format; + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); + + + //send it along to the device service + //and listen for a response back from the device service + var socket = new CoreController(socketID); + var coreResult = socket.sendAndListenForDFD(coreID, + { cmd: "GetVar", name: varName }, + { cmd: "VarReturn", name: varName }, + settings.coreRequestTimeout + ); + + //sendAndListenForDFD resolves arr to ==> [sender, msg] + when(coreResult) + .then(function (arr) { + var msg = arr[1]; + if (msg.error) { + //at this point, either we didn't get a describe return, or that variable + //didn't exist, either way, 404 + return res.status(404).json({ + ok: false, + error: msg.error + }); + } + + //TODO: make me look like the spec. + msg.coreInfo = req.coreInfo; + msg.coreInfo.connected = true; + + if (format && (format == "raw")) { + return res.sendStatus("" + msg.result); + } + else { + return res.json(msg); + } + }, + function () { + res.status(408).json({error: "Timed out."}); + } + ).ensure(function () { + socket.close(); + }); + }, + + fn_call: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req), + coreID = req.coreID, + funcName = req.params.func, + format = req.params.format; + + if(!userid) { + return next(); } - - var hasFiles = req.files && req.files.file; - if (hasFiles) { - //oh hey, you want to flash firmware? - promises.push(Api.compile_and__or_flash_dfd(req)); + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + logger.log("FunCall", { coreID: coreID, userid: userid.toString() }); + + var socketID = Api.getSocketID(userid); + var socket = new CoreController(socketID); + var core = socket.getCore(coreID); + + + var args = req.body; + delete args.access_token; + logger.log("FunCall - calling core ", { coreID: coreID, userid: userid.toString() }); + var coreResult = socket.sendAndListenForDFD(coreID, + { cmd: "CallFn", name: funcName, args: args }, + { cmd: "FnReturn", name: funcName }, + settings.coreRequestTimeout + ); + + //sendAndListenForDFD resolves arr to ==> [sender, msg] + when(coreResult) + .then( + function (arr) { + var sender = arr[0], msg = arr[1]; + + try { + //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); + if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { + res.status(404).json({ + ok: false, + error: "Function not found" + }); + } + else if (msg.error != null) { + res.status(400).json({ + ok: false, + error: msg.error + }); + } + else { + if (format && (format == "raw")) { + res.sendStatus("" + msg.result); + } + else { + res.json({ + id: core.coreID, + name: core.name || null, + last_app: core.last_flashed_app_name || null, + connected: true, + return_value: msg.result + }); + } + } + } + catch (ex) { + logger.error("FunCall handling resp error " + ex); + res.status(500).json({ + ok: false, + error: "Error while api was rendering response" + }); + } + }, + function () { + res.status(408).json({error: "Timed out."}); + } + ).ensure(function () { + socket.close(); + }); + + //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); + + // send the function call along to the device service + }, + + /** + * Ask the core to start / stop the "RaiseYourHand" signal + * @param req + */ + core_signal_dfd: function (req) { + var tmp = when.defer(); + + var userid = Api.getUserID(req), + socketID = Api.getSocketID(userid), + coreID = req.coreID, + showSignal = parseInt(req.body.signal); + + if(!userid) { + return when.reject({ error: "No userID provided" }); } - - var signal = req.body && req.body.signal; - if (signal) { - //get your hands up in the air! Or down. - promises.push(Api.core_signal_dfd(req)); + + logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); + + var socket = new CoreController(socketID); + var failTimer = setTimeout(function () { + socket.close(); + tmp.reject({error: "Timed out, didn't hear back"}); + }, settings.coreSignalTimeout); + + //listen for a response back from the device service + socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, + function () { + clearTimeout(failTimer); + socket.close(); + + tmp.resolve({ + id: coreID, + connected: true, + signaling: showSignal === 1 + }); + }, true); + + + //send it along to the core via the device service + socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); + + return tmp.promise; + }, + + compile_and__or_flash_dfd: function (req) { + var allDone = when.defer(); + var userid = Api.getUserID(req), + coreID = req.coreID; + + if(!userid) { + return when.reject({ error: "No userID provided" }); } - - var flashApp = req.body ? req.body.app : null; - if (flashApp) { - // It makes no sense to flash a known app and also - // either signal or flash a file sent with the request - if (!hasFiles && !signal) { - - // MUST sanitize app name here, before sending to Device Service - if (utilities.contains(settings.known_apps, flashApp)) { - promises.push(Api.flash_known_app_dfd(req)); - } - else { - promises.push(when.reject("Can't flash unknown app " + flashApp)); - } - } + + // + // Did they pass us a source file or a binary file? + // + var hasSourceFiles = false; + var sourceExts = [".cpp", ".c", ".h", ".ino" ]; + if (req.files) { + for (var name in req.files) { + if (!req.files.hasOwnProperty(name)) { + continue; + } + + var ext = utilities.getFilenameExt(req.files[name].path); + if (utilities.contains(sourceExts, ext)) { + hasSourceFiles = true; + break; + } + } + } + + + if (hasSourceFiles) { + //TODO: federate? + allDone.reject("Not yet implemented"); + } + else { + //they sent a binary, just flash it! + var flashDone = Api.flash_core_dfd(req); + + //pipe rejection / resolution of flash to response + utilities.pipeDeferred(flashDone, allDone); + } + + return allDone.promise; + }, + + + /** + * Flashing firmware to the core, binary file! + * @param req + * @returns {promise|*|Function|Promise|when.promise} + */ + flash_core_dfd: function (req) { + var tmp = when.defer(); + + var userid = Api.getUserID(req), + socketID = Api.getSocketID(userid), + coreID = req.coreID; + + if(!userid) { + return when.reject({ error: "No userID provided" }); } - - var app_id = req.body ? req.body.app_id : null; - if (app_id && !hasFiles && !signal && !flashApp) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_app_in_db_dfd(req)); + + logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); + + var args = req.query; + delete args.coreid; + + if (req.files) { + console.log(req.files); + args.data = fs.readFileSync(req.files.file.path); + //args.data = fs.readFileSync(req.files.file.file); + } + + var socket = new CoreController(socketID); + var failTimer = setTimeout(function () { + socket.close(); + tmp.reject({error: "Timed out."}); + }, settings.coreFlashTimeout); + + //listen for the first response back from the device service + socket.listenFor(coreID, { cmd: "Event", name: "Update" }, + function (sender, msg) { + clearTimeout(failTimer); + socket.close(); + + var response = { id: coreID, status: msg.message }; + if ("Update started" === msg.message) { + tmp.resolve(response); + } + else { + logger.error("flash_core_dfd rejected ", response); + tmp.reject(response); + } + + }, true); + + //send it along to the device service + socket.send(coreID, { cmd: "UFlash", args: args }); + + return tmp.promise; + }, + + provision_core: function (req, res, next) { + //if we're here, the user should be allowed to provision cores. + + var done = Api.provision_core_dfd(req); + when(done).then( + function (result) { + res.json(result); + }, + function (err) { + //different status code here? + res.status(400).json(err); + }); + }, + + provision_core_dfd: function (req) { + var result = when.defer(), + userid = Api.getUserID(req), + deviceID = req.body.deviceID, + publicKey = req.body.publicKey; + + if(!userid) { + return when.reject({ error: "No userID provided" }); } - - var app_example_id = req.body ? req.body.app_example_id : null; - if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_example_app_in_db_dfd(req)); + + if (!deviceID) { + return when.reject({ error: "No deviceID provided" }); + } + + try { + var keyObj = ursa.createPublicKey(publicKey); + if (!publicKey || (!ursa.isPublicKey(keyObj))) { + return when.reject({ error: "No key provided" }); + } + } + catch (ex) { + logger.error("error while parsing publicKey " + ex); + return when.reject({ error: "Key error " + ex }); + } + + + global.server.addCoreKey(deviceID, publicKey); + global.server.setCoreAttribute(deviceID, "registrar", userid); + global.server.setCoreAttribute(deviceID, "timestamp", new Date()); + result.resolve("Success!"); + + return result.promise; + }, + + //List products the currently authenticated user has access to. + list_products: function (req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("ListProducts", { userID: userid }); + + var orgObj = global.roles.getOrgByUserid(userid); + if(orgObj) { + var productObjs = global.roles.products[orgObj.slug]; + + //remove devices ?? + res.json({ products : productObjs }); + } else { + res.json({ products : [] }); + } + }, + + //Retrieve details for a product. + get_product: function( req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("GetProduct", { userID: userid }); + + var productid = req.params.productIdOrSlug; + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + var productObj = global.roles.getProductByProductid(productid); + + res.json({ product : productObj }); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); + } + }, + + //Generate a device claim code for a customer, scoped for a specific product. + get_product_claim_code: function (req, res, next) { + var customerid = Api.getCustomerID(req); + if(!customerid) { + return next(); + } + + var productid = req.params.productIdOrSlug; + + logger.log("GenerateProductClaimCode", { customerID: customerid }); + + var productObj = global.roles.getProductByProductid(productid); + var productDevicesIDs = productObj.devices; + PasswordHasher.generateSalt(function (err, code) { + code = code.toString('base64'); + code = code.substring(0, 63); + + when(global.roles.addProductClaimCode(code, customerid, productObj.product_id)).then( + function () { + res.json({ + claim_code: code, + device_ids: productDevicesIDs + }); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); + }); + }, + + //Remove a device from a product and re-assign to a generic Particle product. This endpoint will unclaim the device if it is owned by a customer. + release_product_device: function (req, res, next) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + if(!userid) { + return next(); } - - - if (promises.length >= 1) { - when.all(promises).done( - function (results) { - var aggregate = {}; - for (var i in results) { - for (var key in results[i]) { - aggregate[key] = results[i][key]; - } - } - res.json(aggregate); - }, - function (err) { - res.json({ ok: false, errors: [err] }); + + var productid = req.params.productIdOrSlug; + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + when(global.roles.removeProductDevice(coreID, productid)).then( + function () { + global.server.setCoreAttribute(coreID, "claimed", false); + res.json({ ok:true }); + }, function (err) { + res.status(400).json({ + "code": 400, + "ok": false, + "info": "Device not found for this product" + }); } ); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); } - else { - logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); - res.json({error: "Nothing to do?"}); - } - }, - - - isDeviceOnline: function (userID, coreID) { - var tmp = when.defer(); - - var socketID = Api.getSocketID(userID); - var socket = new CoreController(socketID); - - var failTimer = setTimeout(function () { - logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); - socket.close(); - tmp.reject("Device is not connected"); - }, settings.isCoreOnlineTimeout); - - - //setup listener for response back from the device service - socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); - - if (msg && msg.online) { - tmp.resolve(msg); - } - else { - tmp.reject(["Core isn't online", 404]); - } - - }, true); - - logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); - - //send it along to the device service - if (!socket.send(coreID, { cmd: "Ping" })) { - tmp.reject("send failed"); - } - - return tmp.promise; - }, - - - claim_device: function (req, res) { - res.json({ ok: true }); }, - - - loadCore: function (req, res, next) { - req.coreID = req.param('coreid') || req.body.id; - - //load core info! - req.coreInfo = { - "last_app": "", - "last_heard": new Date(), - "connected": false, - "deviceID": req.coreID - }; - - //if that user doesn't own that coreID, maybe they sent us a core name + + //List Customers for a product. + get_product_customers: function( req, res, next) { var userid = Api.getUserID(req); - var gotCore = utilities.deferredAny([ - function () { - var core = global.server.getCoreAttributes(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - }, - function () { - var core = global.server.getCoreByName(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - } - ]); - - when(gotCore).then( - function (core) { - if (core) { - req.coreID = core.coreID || req.coreID; - req.coreInfo = { - last_handshake_at: core.last_handshake_at - }; - } - - next(); - }, - function (err) { - //s`okay. - next(); - }) - }, - - get_var: function (req, res) { - var userid = Api.getUserID(req); - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - varName = req.param('var'), - format = req.param('format'); - - logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); - - - //send it along to the device service - //and listen for a response back from the device service - var socket = new CoreController(socketID); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "GetVar", name: varName }, - { cmd: "VarReturn", name: varName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then(function (arr) { - var msg = arr[1]; - if (msg.error) { - //at this point, either we didn't get a describe return, or that variable - //didn't exist, either way, 404 - return res.json(404, { - ok: false, - error: msg.error - }); - } - - //TODO: make me look like the spec. - msg.coreInfo = req.coreInfo; - msg.coreInfo.connected = true; - - if (format && (format == "raw")) { - return res.send("" + msg.result); - } - else { - return res.json(msg); - } - }, - function () { - res.json(408, {error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - }, - - fn_call: function (req, res) { - var user_id = Api.getUserID(req), - coreID = req.coreID, - funcName = req.params.func, - format = req.params.format; - - logger.log("FunCall", { coreID: coreID, user_id: user_id.toString() }); - - var socketID = Api.getSocketID(user_id); - var socket = new CoreController(socketID); - var core = socket.getCore(coreID); - - - var args = req.body; - delete args.access_token; - logger.log("FunCall - calling core ", { coreID: coreID, user_id: user_id.toString() }); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "CallFn", name: funcName, args: args }, - { cmd: "FnReturn", name: funcName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then( - function (arr) { - var sender = arr[0], msg = arr[1]; - - try { - //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); - if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { - res.json(404, { - ok: false, - error: "Function not found" - }); - } - else if (msg.error != null) { - res.json(400, { - ok: false, - error: msg.error + if(!userid) { + return next(); + } + + logger.log("GetProductCustomer", { userID: userid }); + + var productid = req.params.productIdOrSlug; + var productObj = global.roles.getProductByProductid(productid); + var productDevices = productObj.devices; + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + var customerObjs = []; + var userObjIds = []; + var devices = []; + for (var k = 0; k < productDevices.length; k++) { + var deviceid = productDevices[k]; + var userObj = global.roles.getUserByDevice(deviceid); + if(userObj && userObj.org) { //if customer + devices.push(deviceid); + var index = utilities.indexOf(userObjIds, userObj._id); + if (index == -1) { + userObjIds.push(userObj._id); + customerObjs.push({ + id: userObj._id, + email: userObj.email, + devices: userObj.devices }); } - else { - if (format && (format == "raw")) { - res.send("" + msg.result); - } - else { - res.json({ - id: core.coreID, - name: core.name || null, - last_app: core.last_flashed_app_name || null, - connected: true, - return_value: msg.result - }); - } - } - } - catch (ex) { - logger.error("FunCall handling resp error " + ex); - res.json(500, { - ok: false, - error: "Error while api was rendering response" - }); - } - }, - function () { - res.json(408, {error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - - //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); - - // send the function call along to the device service - }, - - /** - * Ask the core to start / stop the "RaiseYourHand" signal - * @param req - */ - core_signal_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID, - showSignal = parseInt(req.body.signal); - - logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out, didn't hear back"}); - }, settings.coreSignalTimeout); - - //listen for a response back from the device service - socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, - function () { - clearTimeout(failTimer); - socket.close(); - - tmp.resolve({ - id: coreID, - connected: true, - signaling: showSignal === 1 - }); - }, true); - - - //send it along to the core via the device service - socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); - - return tmp.promise; - }, - - compile_and__or_flash_dfd: function (req) { - var allDone = when.defer(); - var userid = Api.getUserID(req), - coreID = req.coreID; - - - // - // Did they pass us a source file or a binary file? - // - var hasSourceFiles = false; - var sourceExts = [".cpp", ".c", ".h", ".ino" ]; - if (req.files) { - for (var name in req.files) { - if (!req.files.hasOwnProperty(name)) { - continue; - } - - var ext = utilities.getFilenameExt(req.files[name].path); - if (utilities.contains(sourceExts, ext)) { - hasSourceFiles = true; - break; } } + res.json({ customers : customerObjs, devices : devices }); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); } - - - if (hasSourceFiles) { - //TODO: federate? - allDone.reject("Not yet implemented"); - } - else { - //they sent a binary, just flash it! - var flashDone = Api.flash_core_dfd(req); - - //pipe rejection / resolution of flash to response - utilities.pipeDeferred(flashDone, allDone); - } - - return allDone.promise; - }, - - - /** - * Flashing firmware to the core, binary file! - * @param req - * @returns {promise|*|Function|Promise|when.promise} - */ - flash_core_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID; - - logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); - - var args = req.query; - delete args.coreid; - - if (req.files) { - args.data = fs.readFileSync(req.files.file.path); - } - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out."}); - }, settings.coreFlashTimeout); - - //listen for the first response back from the device service - socket.listenFor(coreID, { cmd: "Event", name: "Update" }, - function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - var response = { id: coreID, status: msg.message }; - if ("Update started" === msg.message) { - tmp.resolve(response); - } - else { - logger.error("flash_core_dfd rejected ", response); - tmp.reject(response); - } - - }, true); - - //send it along to the device service - socket.send(coreID, { cmd: "UFlash", args: args }); - - return tmp.promise; }, - - provision_core: function (req, res) { - //if we're here, the user should be allowed to provision cores. - - var done = Api.provision_core_dfd(req); - when(done).then( - function (result) { - res.json(result); - }, - function (err) { - //different status code here? - res.json(400, err); - }); - }, - - provision_core_dfd: function (req) { - var result = when.defer(), - userid = Api.getUserID(req), - deviceID = req.body.deviceID, - publicKey = req.body.publicKey; - - if (!deviceID) { - return when.reject({ error: "No deviceID provided" }); + + add_product_device: function (req, res, next) { + var coreID = req.body.id; + if(!coreID) { + res.status(400).json({ ok: false, errors: [ 'id is required.' ] }); } - - try { - var keyObj = ursa.createPublicKey(publicKey); - if (!publicKey || (!ursa.isPublicKey(keyObj))) { - return when.reject({ error: "No key provided" }); - } + var userid = Api.getUserID(req); + if(!userid) { + return next(); } - catch (ex) { - logger.error("error while parsing publicKey " + ex); - return when.reject({ error: "Key error " + ex }); + + var productid = req.params.productIdOrSlug; + logger.log("AddingProductDevice", { productID: productid }); + + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + when(global.roles.addProductDevice(coreID, productid)).then( + function () { + res.json({ ok:true }); + }, function (err) { + res.status(400).json({ + "code": 400, + "ok": false, + "info": "Device already present for that product" + }); + } + ); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); } - - - global.server.addCoreKey(deviceID, publicKey); - global.server.setCoreAttribute(deviceID, "registrar", userid); - global.server.setCoreAttribute(deviceID, "timestamp", new Date()); - result.resolve("Success!"); - - return result.promise; }, - - _: null + _: null }; -exports = module.exports = Api; +exports = module.exports = global.api = Api; From 1d95c42a05f5f9873a59ca58549179d4bc729560 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 24 Nov 2016 13:12:33 -0800 Subject: [PATCH 049/504] Minor fixes to core controller. --- README.md | 65 +++++++++++++++------------------ lib/CoreController.js | 19 ++-------- main.js | 1 - package.json | 82 +++++++++++++++++++++--------------------- views/EventViews001.js | 7 ++-- 5 files changed, 75 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 15e9abc4..bab8c944 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,12 @@ spark-server An API compatible open source server for interacting with devices speaking the [spark-protocol](https://github.com/spark/spark-protocol) -*Photon users:* This application has not been rigorously tested with the Photon. We will be reviewing and testing it shortly, but in the meantime, YMMV. If you do attempt to use the Photon with this library and run into problems, please open an issue. -
    __  __            __                 __        __                ____
   / /_/ /_  ___     / /___  _________ _/ /  _____/ /___  __  ______/ / /
  / __/ __ \/ _ \   / / __ \/ ___/ __ `/ /  / ___/ / __ \/ / / / __  / /
-/ /_/ / / /  __/  / / /_/ / /__/ /_/ / /  / /__/ / /_/ / /_/ / /_/ /_/
-\__/_/ /_/\___/  /_/\____/\___/\__,_/_/   \___/_/\____/\__,_/\__,_(_)
+/ /_/ / / /  __/  / / /_/ / /__/ /_/ / /  / /__/ / /_/ / /_/ / /_/ /_/  
+\__/_/ /_/\___/  /_/\____/\___/\__,_/_/   \___/_/\____/\__,_/\__,_(_)   
 
@@ -19,7 +17,7 @@ Quick Install ``` git clone https://github.com/spark/spark-server.git -cd spark-server +cd spark-server/js npm install node main.js ``` @@ -42,52 +40,50 @@ node main.js Your server IP address is: 192.168.1.10 ``` - -3.) Load your server public key and IP address onto your cores with the [Spark-CLI](https://github.com/spark/spark-cli) - -First, put your Core in DFU mode by holding the MODE and RESET buttons on the Core, then releasing RESET while continuing to hold MODE for 3 seconds until the LED starts blinking yellow. +3.) We will now create a new server profile on Particle-CLI using the command: ``` -spark keys server default_key.pub.pem 192.168.1.10 +particle config profile_name apiUrl "http://DOMAIN_OR_IP" ``` -Note! The CLI will turn your PEM file into a DER file, but you can also do that yourself with the command: -``` - openssl rsa -in default_key.pem -pubin -pubout -outform DER -out default_key.der -``` +For the local cloud, the port number 8080 needs to be added behind: http://domain_or_ip:8080 -4.) Edit your Spark-CLI config file to point at your Spark-server. Open ~/.spark/spark.config.json in your favorite text editor, and add: +This will create a new profile to point to your server and switching back to the spark cloud is simply particle config particle and other profiles would be particle config profile_name + +4.) We will now point over to the local cloud using particle config profile_name + +5.) On a separate CMD from the one running the server, type ``` -{ - "apiUrl": "http://192.168.1.10:8080" -} +particle setup ``` -For beginners: note that you have to add in a `,` at the end of the previous line +This will create an account on the local cloud -5.) Put your core into listening mode, and run `spark identify` to get your core id. +Perform CTRL + C once you logon with Particle-CLI asking you to send Wifi-credentials etc... -6.) Create a user and login with the Spark-CLI +6.) On Command-line, cd to particle-server and place your core in DFU mode [flashing yellow] + +7.) Create and provision access on your local cloud with the keys doctor: ``` - spark setup + particle keys doctor your_core_id ``` -7.) Create and provision access on your local cloud with the keys doctor: +8.) Change server keys to local cloud key + IP Address ``` - spark keys doctor your_core_id +particle keys server default_key.pub.pem IP_ADDRESS ``` - -7.) See your connected cores! +9.) Go to cores_key directory to place core public key inside ``` - spark list +cd core_keys +place core in DFU-mode +particle keys save INPUT_DEVICE_ID_HERE ``` - What kind of project is this? ====================================== @@ -158,10 +154,10 @@ What features will be added soon? ==================================== - Release a Core - DELETE /v1/devices/:coreid + DELETE /v1/devices/:coreid - Claim a core - POST /v1/devices + POST /v1/devices - per-user / per-core ownership and access restrictions. Right now ANY user on your local cloud can access ANY device. @@ -171,8 +167,8 @@ What features will be added soon? What API features are missing ================================ - - the build IDE is not part of this release, but may be released separately later - - massive enterprise magic super horizontal scaling powers + - the build IDE is not part of this release, but may be released separately later + - massive enterprise magic super horizontal scaling powers Known Limitations @@ -183,8 +179,3 @@ We worked hard to make our cloud services scalable and awesome, but that also pr What features are coming ======================== - - - - - diff --git a/lib/CoreController.js b/lib/CoreController.js index 3bf0bb97..85aa1a66 100644 --- a/lib/CoreController.js +++ b/lib/CoreController.js @@ -131,21 +131,9 @@ CoreController.prototype = { core.on(that.socketID, handler); }, - subscribe: function (isPublic, name, userid) { - if (userid && (userid != "")) { - name = userid + "/" + name; - } - - -// if (!sock) { -// return false; -// } - - //start permitting these messages through on this socket. - global.publisher.subscribe(name, this); - - return false; - }, + subscribe: function (isPublic, name, userid,coreid) { + global.publisher.subscribe(name, userid, coreid, this); + }, unsubscribe: function (isPublic, name, userid) { if (userid && (userid != "")) { @@ -220,4 +208,3 @@ CoreController.prototype = { //}; CoreController.prototype = extend(CoreController.prototype, EventEmitter.prototype); module.exports = CoreController; - diff --git a/main.js b/main.js index 9b7d146e..6d583fb3 100644 --- a/main.js +++ b/main.js @@ -113,4 +113,3 @@ var ips = utilities.getIPAddresses(); for(var i=0;i Date: Thu, 24 Nov 2016 13:15:23 -0800 Subject: [PATCH 050/504] Revert "Cleanup" This reverts commit 559bd02803e58d86d5db924654c3f2e8a5aa1fd2. # Conflicts: # lib/CoreController.js # package.json # views/EventViews001.js --- js/lib/AccessTokenViews.js | 108 ++ js/lib/CoreController.js | 218 ++++ {lib => js/lib}/CustomerViews.js | 0 js/lib/RolesController.js | 728 +++++++++++++ js/lib/utilities.js | 391 +++++++ js/oauth_clients.json | 7 +- {orgs => js/orgs}/company.json | 0 js/package.json | 37 + js/views/EventViews001.js | 339 ++++++ js/views/api_v1.js | 1194 +++++++++++++++++++++ lib/AccessTokenViews.js | 128 ++- lib/RolesController.js | 850 +++------------ lib/utilities.js | 717 ++++++------- oauth_clients.json | 12 - views/api_v1.js | 1724 +++++++++++------------------- 15 files changed, 4217 insertions(+), 2236 deletions(-) create mode 100644 js/lib/AccessTokenViews.js create mode 100644 js/lib/CoreController.js rename {lib => js/lib}/CustomerViews.js (100%) create mode 100644 js/lib/RolesController.js create mode 100644 js/lib/utilities.js rename {orgs => js/orgs}/company.json (100%) create mode 100644 js/package.json create mode 100644 js/views/EventViews001.js create mode 100644 js/views/api_v1.js delete mode 100644 oauth_clients.json diff --git a/js/lib/AccessTokenViews.js b/js/lib/AccessTokenViews.js new file mode 100644 index 00000000..9382e00c --- /dev/null +++ b/js/lib/AccessTokenViews.js @@ -0,0 +1,108 @@ +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +*/ + +var fs = require('fs'); +var path = require('path'); +var when = require('when'); +var sequence = require('when/sequence'); +var pipeline = require('when/pipeline'); +var PasswordHasher = require('./PasswordHasher.js'); +var roles = require('./RolesController.js'); +var logger = require('./logger.js'); + +var AccessTokenViews = function (options) { + this.options = options; +}; + +AccessTokenViews.prototype = { + loadViews: function (app) { + app.get('/v1/access_tokens', this.index.bind(this)); + app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); + }, + index: function (req, res) { + //var credentials = AccessTokenViews.basicAuth(req); + //var credentials = this.basicAuth(req); + var credentials= AccessTokenViews.prototype.basicAuth(req); + if (!credentials) { + return res.status(401).json({ + ok: false, + errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] + }); + } + + //if successful, should return something like: + // [ { token: d.token, expires: d.expires, client: d.client_id } ] + + when(roles.validateLogin(credentials.username, credentials.password)) + .then( + function (userObj) { + res.json(userObj.access_tokens); + }, + function () { + res.status(401).json({ ok: false, errors: ['Bad password']}); + }); + }, + + destroy: function (req, res) { + //var credentials = AccessTokenViews.basicAuth(req); + var credentials= AccessTokenViews.prototype.basicAuth(req); + if (!credentials) { + return res.status(401).json({ + ok: false, + errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] + }); + } + + when(roles.validateLogin(credentials.username, credentials.password)) + .then( + function (userObj) { + try { + roles.destroyAccessToken(req.params.token); + res.json({ ok: true }); + } + catch (ex) { + logger.error("error saving user " + ex); + res.status(401).json({ ok: false, errors: ['Error updating token']}); + } + }, + function () { + res.status(401).json({ ok: false, errors: ['Bad password']}); + }); + }, + + basicAuth: function (req) { + var auth = req.get('Authorization'); + if (!auth) return null; + + var matches = auth.match(/Basic\s+(\S+)/); + if (!matches) return null; + + var creds = new Buffer(matches[1], 'base64').toString(); + var separatorIndex = creds.indexOf(':'); + if (-1 === separatorIndex) + return null; + + return { + username: creds.slice(0, separatorIndex), + password: creds.slice(separatorIndex + 1) + }; + } + +}; + +module.exports = AccessTokenViews; diff --git a/js/lib/CoreController.js b/js/lib/CoreController.js new file mode 100644 index 00000000..c7b4d726 --- /dev/null +++ b/js/lib/CoreController.js @@ -0,0 +1,218 @@ +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +*/ + +var fs = require('fs'); +var when = require('when'); +var extend = require('xtend'); +var EventEmitter = require('events').EventEmitter; + +var logger = require('./logger.js'); +var settings = require("../settings"); +var utilities = require("./utilities.js"); + + +var CoreController = function (socketID) { + //this.coreID = coreID; + this.socketID = socketID; + EventEmitter.call(this); +}; + +CoreController.prototype = { + getCore: function (coreid) { + if (global.server) { + return global.server.getCore(coreid); + } + else { + logger.error("Spark-protocol server not running"); + } + }, + + + sendAndListenFor: function (recipient, msg, filter, callback, once) { + this.listenFor(recipient, filter, callback, once); + this.send(recipient, msg); + }, + + sendAndListenForDFD: function (recipient, msg, filter, failDelay, connectDelay) { + var result = when.defer(); + + failDelay = failDelay || settings.coreRequestTimeout; + var failTimer = setTimeout(function () { + result.reject("Request Timed Out"); + }, failDelay); + + var callback = function (sender, msg) { + clearTimeout(failTimer); + result.resolve([sender, msg]); + }; + + this.sendAndListenFor(recipient, msg, filter, callback, true); + return result.promise; + }, + + + /** + * send a message to a core + * @param recipient + * @param msg + */ + send: function (recipient, msg) { + var that = this; + var core = this.getCore(recipient); + if (!core || !core.onApiMessage) { + logger.error("Couldn't find that core ", recipient); + return false; + } + + process.nextTick(function () { + try { + //console.log("sending message with socketID" + that.socketID); + core.onApiMessage(that.socketID, msg); + } + catch (ex) { + logger.error("error during send: " + ex); + } + }); + return true; + }, + + /** + * starts listening for a message event with the given filter criteria + * @param filter + * @param callback + * @param once - removes the listener after we've heard back + */ + listenFor: function (recipient, filter, callback, once) { + var core = this.getCore(recipient); + if (!core || !core.on) { + logger.error("Couldn't find that core ", recipient); + return; + } + + var that = this, + handler = function (sender, msg) { + //logger.log('heard from ' + ((sender) ? sender.toString() : '(UNKNOWN)')); + + if (!utilities.leftHasRightFilter(msg, filter)) { + //logger.log('filters did not match'); + return; + } + + if (once) { + core.removeListener(that.socketID, handler); + } + + process.nextTick(function () { + try { + //logger.log('passing message to callback ', msg); + callback(sender, msg); + } + catch (ex) { + logger.error("listenFor error: " + ex, (ex) ? ex.stack : ''); + } + }); + }; + + core.on(that.socketID, handler); + }, + + subscribe: function (isPublic, name, userid,coreid) { +// if (!sock) { +// return false; +// } + + //start permitting these messages through on this socket. + global.publisher.subscribe(name,userid,coreid, this); + + return false; + }, + + unsubscribe: function (isPublic, name, userid) { + if (userid && (userid != "")) { + name = userid + "/" + name; + } + +// if (!sock) { +// return; +// } + + global.publisher.unsubscribe(name, this); + }, + + //isPublic, obj.name, obj.userid, obj.data, obj.ttl, obj.published_at + sendEvent: function (isPublic, name, userid, data, ttl, published_at, coreid) { + + if (!global.publisher) { + logger.error("Spark-protocol server not running"); + return; + } + + try { + global.publisher.publish( + isPublic, + name, + userid, + data, + ttl, + published_at, + coreid + ); + } + catch (ex) { + logger.error("sendEvent Error: " + ex); + } + + return true; + }, + + close: function () { + + } +}; + +///** +// * This should be made more efficient, this is too simplistic +// * @returns {{}} +// */ +//CoreController.listAllCores = function() { +// var files = fs.readdirSync(settings.coreKeysDir); +// var cores = []; +// +// +// +// +// +// var corelist = files.map(function(filename) { return utilities.filenameNoExt(filename); }); +// var cores = {}; +// for(var i=0;i. +* +* You can download the source here: https://github.com/spark/spark-server +*/ + +var fs = require('fs'); +var path = require('path'); +var when = require('when'); +var sequence = require('when/sequence'); +var pipeline = require('when/pipeline'); +var PasswordHasher = require('./PasswordHasher.js'); +var roles = require('./RolesController.js'); +var settings = require('../settings.js'); +var logger = require('./logger.js'); +var utilities = require("./utilities.js"); + +function RolesController() { + this.init(); +}; + +RolesController.prototype = { + users: null, + usersByToken: null, + usersByDevice: null, + usersByClaimCode: null, + usersByUsername: null, + + access_tokens: null, + refresh_tokens: null, + claim_codes: null, + + devices: null, + clients: null, + + orgsBySlug: null, + orgsByUserId: null, + + customers: null, + customersByEmail: null, + //customersByToken: null, + + orgsByProduct: null, + products : null, + + init: function () { + this._loadAndCacheUsers(); + }, + + addUser: function (userObj) { + this.users.push(userObj); + this.usersByUsername[ userObj.username ] = userObj; + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var token = userObj.access_tokens[i]; + this.usersByToken[token.token] = userObj; + this.access_tokens[token.token] = token; + this.refresh_tokens[token.refresh_token] = token; + } + + //claim codes + this.claim_codes[userObj._id] = userObj.claim_codes; + for (var i = 0; i < userObj.claim_codes.length; i++) { + var claimCode = userObj.claim_codes[i]; + this.usersByClaimCode[claimCode] = userObj; + } + + //devices claimed + this.devices[userObj._id] = userObj.devices; + for (var i = 0; i < userObj.devices.length; i++) { + var deviceId = userObj.devices[i]; + this.usersByDevice[ deviceId ] = userObj; + } + }, + addCustomer: function (customerObj) { + + console.log("Loading customer " + customerObj.email); + + this.customers.push(customerObj); + this.customersByEmail[ customerObj.email ] = customerObj; + + for (var k = 0; k < customerObj.access_tokens.length; k++) { + var token = customerObj.access_tokens[k]; + this.usersByToken[token.token] = customerObj; + this.access_tokens[token.token] = token; + this.refresh_tokens[token.refresh_token] = token; + } + + //claim codes + this.claim_codes[customerObj._id] = customerObj.claim_codes; + for (var i = 0; i < customerObj.claim_codes.length; i++) { + var claimCode = customerObj.claim_codes[i]; + this.usersByClaimCode[claimCode.code] = customerObj; + } + + //devices claimed + this.devices[customerObj._id] = customerObj.devices; + for (var i = 0; i < customerObj.devices.length; i++) { + var deviceId = customerObj.devices[i]; + this.usersByDevice[ deviceId ] = customerObj; + } + }, + addClient: function (clientObj) { + this.clients.push(clientObj); + }, + addOrg: function (orgObj) { + this.orgsBySlug[orgObj.slug] = orgObj; + this.orgsByUserId[orgObj.user_id] = orgObj; + + for (var i = 0; i < orgObj.customers.length; i++) { + this.addCustomer(orgObj.customers[i]); //add customer + } + + this.products[orgObj.slug] = []; //list product + for (var j = 0; j < orgObj.products.length; j++) { + this.products[orgObj.slug].push(orgObj.products[j]); + this.orgsByProduct[ orgObj.products[j].slug ] = orgObj; + this.orgsByProduct[ orgObj.products[j].product_id ] = orgObj; + } + }, + destroyAccessToken: function (access_token) { + var userObj = this.usersByToken[access_token]; + if (!userObj) { + return true; + } + + delete this.usersByToken[access_token]; + delete this.access_tokens[access_token]; + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var tokenObj = userObj.access_tokens[i]; + if (tokenObj.token == access_token) { + userObj.access_tokens.splice(i, 1); + } + } + + this.saveUser(userObj); + }, + revokeToken: function (token) { + var userObj = this.usersByToken[token.accessToken]; + if (!userObj) { + return false; + } + var tokenObj = this.access_tokens[token.accessToken]; + if(!tokenObj) { + return false; + } + + delete this.usersByToken[token.accessToken]; + delete this.access_tokens[token.accessToken]; + delete this.refresh_tokens[token.refreshToken]; + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var tokenObj = userObj.access_tokens[i]; + if (tokenObj.token == token.accessToken) { + userObj.access_tokens.splice(i, 1); + } + } + if(tokenObj.scope && tokenObj.scope.indexOf("customer=") > -1) { + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } + return token; + }, + addAccessToken: function (token, client, user) { + var tmp = when.defer(); + try { + var tokenObj = { + //user_id: user._id, + token: token.accessToken, + expires_at: token.accessTokenExpiresAt, + client: client.client_id, + refresh_token: token.refreshToken, + scope: token.scope + }; + + if(token.scope && token.scope.indexOf("customer=") > -1) { + //is a customer token + var email = token.scope.split("=")[1]; + var customerObj = this.customersByEmail[email]; + + this.usersByToken[token.accessToken] = customerObj; + this.access_tokens[token.accessToken] = tokenObj; + this.refresh_tokens[token.refreshToken] = tokenObj; + customerObj.access_tokens.push(tokenObj); + this.saveCustomer(customerObj); + + tmp.resolve({ + accessToken: token.accessToken, + client: client, + refreshToken: token.refreshToken, + user: customerObj._id, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt + }); + + } else { + var userObj = this.getUserByUserid(user._id); + if(!userObj) { + //refresh_token + userObj = this.getUserByUserid(user); + } + this.usersByToken[token.accessToken] = userObj; + + this.access_tokens[token.accessToken] = tokenObj; + this.refresh_tokens[token.refreshToken] = tokenObj; + userObj.access_tokens.push(tokenObj); + this.saveUser(userObj); + + tmp.resolve({ + accessToken: token.accessToken, + client: client, + user: userObj._id, + refreshToken: token.refreshToken, + scope: token.scope, + accessTokenExpiresAt: token.accessTokenExpiresAt + }); + } + } + catch (ex) { + logger.error("Error adding access token ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addClaimCode: function (claimCode, userId) { + var tmp = when.defer(); + try { + var userObj = this.getUserByUserid(userId); + + userObj.claim_codes.push(claimCode); + this.saveUser(userObj); + + this.usersByClaimCode[ claimCode ] = userObj; + + tmp.resolve(); + } + catch (ex) { + logger.error("Error adding claim code ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addProductClaimCode: function (claimCode, customerId, productId) { + var tmp = when.defer(); + try { + var claimCodeObj = { + code : claimCode, + product_id : productId + } + + var customerObj = this.getCustomerByCustomerid(customerId); + customerObj.claim_codes.push(claimCodeObj); + this.saveCustomer(customerObj); + + this.usersByClaimCode[ claimCode ] = customerObj; + + tmp.resolve(); + } + catch (ex) { + logger.error("Error adding claim code ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addDevice: function (deviceId, userObj) { + var tmp = when.defer(); + try { + if(!this.usersByDevice[deviceId]) { + userObj.devices.push(deviceId); + if(userObj.org) { //customer + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } + this.usersByDevice[ deviceId ] = userObj; + + tmp.resolve(); + } else if(this.usersByDevice[deviceId] == userObj) { + tmp.resolve(); + } else { + tmp.reject("already claimed"); + } + } + catch (ex) { + logger.error("Error adding device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + removeDevice: function (deviceId, userId) { + var tmp = when.defer(); + try { + var userObj = this.getUserByUserid(userId); + + if(this.usersByDevice[deviceId]) { + var index = utilities.indexOf(userObj.devices, deviceId); + if (index > -1) { + userObj.devices.splice(index, 1); + } + + delete this.usersByDevice[deviceId]; + + if(userObj.org) { //customer + this.saveCustomer(userObj); + } else { + this.saveUser(userObj); + } + tmp.resolve(); + } else { + tmp.reject('Device not found'); + } + } + catch (ex) { + logger.error("Error releasing device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + addProductDevice: function (deviceId, productid) { + var tmp = when.defer(); + try { + var productObj = this.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, deviceId); + if (index == -1) { //if not present + productObj.devices.push(deviceId); + this.saveProduct(productObj); + + tmp.resolve(); + } else { + tmp.reject('Device already present for that product'); + } + } + catch (ex) { + logger.error("Error adding device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + removeProductDevice: function (deviceId, productid) { + var tmp = when.defer(); + try { + var productObj = this.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, deviceId); + if (index > -1) { + /*productObj.devices.splice(index, 1); + this.saveProduct(productObj);*/ + + delete this.usersByDevice[deviceId]; + + var orgObj = this.getOrgByProductid(productObj.product_id); + for (var i = 0; i < orgObj.customers.length; i++) { + var index = utilities.indexOf(orgObj.customers[i].devices, deviceId); + if (index > -1) { + orgObj.customers[i].devices.splice(index, 1); + this.saveCustomer(orgObj.customers[i]); + } + } + + tmp.resolve(); + } else { + tmp.reject('Device not found for product'); + } + } + catch (ex) { + logger.error("Error releasing device ", ex); + tmp.reject(ex); + } + return tmp.promise; + }, + saveUser: function (userObj) { + var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; + var userJson = JSON.stringify(userObj, null, 2); + fs.writeFileSync(userFile, userJson); + }, + saveCustomer: function (customerObj) { + var orgObj = this.orgsBySlug[customerObj.org]; + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + var index = 0; + for (var i = 0; i < orgObj.customers.length; i++) { + var customer = orgObj.customers[i]; + if (customer._id == customerObj._id) { + orgObj.customers[i] = customerObj; + } + } + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); + }, + saveProduct: function (productObj) { + var orgObj = this.orgsByProduct[productObj.slug]; + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + var index = 0; + for (var i = 0; i < orgObj.products.length; i++) { + var product = orgObj.products[i]; + if (product.id == productObj.id) { + orgObj.products[i] = productObj; + } + } + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); + }, + + _loadAndCacheUsers: function () { + this.users = []; + this.usersByToken = {}; + this.usersByDevice = {}; + this.usersByUsername = {}; + this.usersByClaimCode = {}; + + this.access_tokens = {}; + this.refresh_tokens = {}; + this.claim_codes = {}; + + this.devices = []; + this.clients = []; + this.orgsBySlug = {}; + this.orgsByUserId = {}; + + this.customers = []; + this.customersByEmail = {}; + //this.customersByToken = {}; + + this.orgsByProduct = {}; + this.products = {}; + + // list files, load all user objects, index by access_tokens and usernames + // and devices + if (!fs.existsSync(settings.userDataDir)) { + fs.mkdirSync(settings.userDataDir); + } + + var files = fs.readdirSync(settings.userDataDir); + if (!files || (files.length == 0)) { + logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); + } + + for (var i = 0; i < files.length; i++) { + try { + + var filename = path.join(settings.userDataDir, files[i]); + var userObj = JSON.parse(fs.readFileSync(filename)); + + console.log("Loading user " + userObj.username); + this.addUser(userObj); + } + catch (ex) { + logger.error("RolesController - error loading user at " + filename); + } + } + + var filenameClient = settings.oauthClientsFile; + var clients = JSON.parse(fs.readFileSync(filenameClient)); + for (var j = 0; j < clients.length; j++) { + try { + console.log("Loading client " + clients[j].client_id); + this.addClient(clients[j]); + } + catch (ex) { + logger.error("RolesController - error loading client at " + filenameOrg); + } + } + + var filesOrg = fs.readdirSync(settings.orgDataDir); + + for (var k = 0; k < filesOrg.length; k++) { + try { + + var filenameOrg = path.join(settings.orgDataDir, filesOrg[k]); + var orgObj = JSON.parse(fs.readFileSync(filenameOrg)); + + console.log("Loading org " + orgObj.slug); + this.addOrg(orgObj); + } + catch (ex) { + logger.error("RolesController - error loading org at " + filenameOrg); + } + } + }, + + getClient: function ( clientId, clientSecret) { + var clientObj = this.getClientByClientid(clientId); + if (clientObj.client_secret == clientSecret || clientId == 'particle') { + return clientObj; + } + return false; + }, + + getUserByClient: function ( clientId ) { + return this.getUserByUserid(this.orgsBySlug[clientId].user_id); + }, + + getUserByToken: function (access_token) { + return this.usersByToken[access_token]; + }, + getUserByDevice: function (deviceId) { + return this.usersByDevice[deviceId]; + }, + getUserByClaimCode: function (claimCode) { + return this.usersByClaimCode[claimCode]; + }, + getOrgByProductid: function (productid) { //ok + return this.orgsByProduct[productid]; + }, + getOrgBySlug: function (orgSlug) { //ok + return this.orgsBySlug[orgSlug]; + }, + getOrgByUserid: function (user_id) { //ok + return this.orgsByUserId[user_id]; + }, + getCustomerByEmail: function (email) { + return this.customersByEmail[email]; + }, + + getUserByName: function (username) { + return this.usersByUsername[username]; + }, + getTokenInfoByAccessToken: function (token) { + var tokenObj = this.access_tokens[token]; + if(!tokenObj) { + return false; + } + return { + accessToken: tokenObj.token, + client: tokenObj.client, + user: this.getUserByToken(tokenObj.token)._id, + refreshToken: tokenObj.refresh_token, + accessTokenExpiresAt: new Date(tokenObj.expires_at), + scope: tokenObj.scope + }; + }, + getTokenInfoByRefreshToken: function (token) { + var tokenObj = this.refresh_tokens[token]; + if(!tokenObj) { + return false; + } + return { + accessToken: tokenObj.token, + client: tokenObj.client, + user: this.getUserByToken(tokenObj.token)._id, + refreshToken: tokenObj.refresh_token, + refreshTokenExpiresAt: new Date(tokenObj.expires_at), + //refreshTokenExpiresAt not managed return accessTokenExpires + scope: tokenObj.scope + }; + }, + getUserByUserid: function (userid) { + for (var i = 0; i < this.users.length; i++) { + var user = this.users[i]; + if (user._id == userid) { + return user; + } + } + return null; + }, + + getProductByProductid: function (productid) { + var orgObj = this.orgsByProduct[productid]; + for (var i = 0; i < this.products[orgObj.slug].length; i++) { + var product = this.products[orgObj.slug][i]; + if (product.product_id == productid || product.slug == productid) { + return product; + } + } + return null; + }, + + getProductByUserid: function (userid) { + var orgObj = this.orgsByProduct[productid]; + for (var i = 0; i < this.products[orgObj.slug].length; i++) { + var product = this.products[orgObj.slug][i]; + if (product.product_id == productid || product.slug == productid) { + return product; + } + } + return null; + }, + + getCustomerByCustomerid: function (customerid) { + for (var i = 0; i < this.customers.length; i++) { + var customer = this.customers[i]; + if (customer._id == customerid) { + return customer; + } + } + return null; + }, + + getClientByClientid: function (clientId) { + for (var i = 0; i < this.clients.length; i++) { + var client = this.clients[i]; + if (client.client_id == clientId) { + return client; + } + } + return null; + }, + + validateHashPromise: function (user, password) { + var tmp = when.defer(); + + PasswordHasher.hash(password, user.salt, function (err, hash) { + if (err) { + logger.error("hash error " + err); + tmp.reject("Bad password"); + } + else if (hash === user.password_hash) { + tmp.resolve(user); + } + else { + tmp.reject("Bad password"); + } + }); + + return tmp.promise; + }, + + validateLogin: function (username, password) { + var userObj = this.getUserByName(username); + if (!userObj) { + return when.reject("Bad password"); + } + + return this.validateHashPromise(userObj, password); + }, + + validateClient: function (clientId, clientSecret) { + var tmp = when.defer(); + + var clientObj = this.getClient(clientId, clientSecret); + if (!clientObj) { + return tmp.reject("Bad client"); + } + + tmp.resolve(clientObj); + + return tmp.promise; + }, + + createUser: function (username, password) { + var tmp = when.defer(); + var that = this; + + PasswordHasher.generateSalt(function (err, userid) { + userid = userid.toString('base64'); + userid = userid.substring(0, 32); + + PasswordHasher.generateSalt(function (err, salt) { + salt = salt.toString('base64'); + PasswordHasher.hash(password, salt, function (err, hash) { + var user = { + _id: userid, + username: username, + password_hash: hash, + salt: salt, + access_tokens: [], + claim_codes: [], + devices: [] + }; + + var userFile = path.join(settings.userDataDir, username + ".json"); + fs.writeFileSync(userFile, JSON.stringify(user)); + + that.addUser(user); + + tmp.resolve(); + }); + }); + }); + + return tmp.promise; + }, + + createCustomer: function (clientObj, /*product,*/ orgSlug, email) { + var tmp = when.defer(); + var that = this; + + var orgObj = that.getOrgBySlug(orgSlug); + if(orgObj && orgObj.slug == clientObj.client_id) { + var customer = that.getCustomerByEmail(email); + if(!customer) { + + PasswordHasher.generateSalt(function (err, customerid) { + customerid = customerid.toString('base64'); + customerid = customerid.substring(0, 32); + + var customer = { + _id: customerid, + email: email, + org: orgObj.slug, + access_tokens: [], + claim_codes: [], + devices: [] + }; + + var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; + orgObj.customers.push(customer); + + var orgJson = JSON.stringify(orgObj, null, 2); + fs.writeFileSync(orgFile, orgJson); + + that.addCustomer(customer); + + tmp.resolve(); + }); + } else { + tmp.reject('Customer '+email+' already exists'); + } + } else { + tmp.reject('Bad product'); + } + + return tmp.promise; + } +}; +module.exports = global.roles = new RolesController(); \ No newline at end of file diff --git a/js/lib/utilities.js b/js/lib/utilities.js new file mode 100644 index 00000000..683539cf --- /dev/null +++ b/js/lib/utilities.js @@ -0,0 +1,391 @@ +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +*/ + +var os = require('os'); +var when = require('when'); +var logger = require('./logger.js'); +var extend = require('xtend'); +var path = require('path'); +var fs = require('fs'); + +var that; +module.exports = that = { + + /** + * ensures the function in the provided scope + * @param fn + * @param scope + * @returns {Function} + */ + proxy: function (fn, scope) { + return function () { + try { + return fn.apply(scope, arguments); + } + catch (ex) { + logger.error(ex); + logger.error(ex.stack); + logger.log('error bubbled up ' + ex); + } + } + }, + + /** + * Surely there is a better way to do this. + * NOTE! This function does NOT short-circuit when an in-equality is detected. This is + * to avoid timing attacks. + * @param left + * @param right + */ + bufferCompare: function (left, right) { + if ((left == null) && (right == null)) { + return true; + } + else if ((left == null) || (right == null)) { + return false; + } + + if (!Buffer.isBuffer(left)) { + left = new Buffer(left); + } + if (!Buffer.isBuffer(right)) { + right = new Buffer(right); + } + + logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); + + var same = (left.length == right.length), + i = 0, + max = left.length; + + while (i < max) { + same &= (left[i] == right[i]); + i++; + } + + return same; + }, + + /** + * Iterates over the properties of the right object, checking to make + * sure the properties on the left object match. + * @param left + * @param right + */ + leftHasRightFilter: function (left, right) { + if (!left && !right) { + return true; + } + var matches = true; + + for (var prop in right) { + if (!right.hasOwnProperty(prop)) { + continue; + } + matches &= (left[prop] == right[prop]); + } + return matches; + }, + + promiseDoFile: function (filename, callback) { + var deferred = when.defer(); + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + fs.readFile(filename, function (err, data) { + if (err) { + logger.error("error reading " + filename, err); + deferred.reject(); + } + + try { + if (callback(data)) { + deferred.resolve(); + } + } + catch(ex) { + deferred.reject(ex); + } + + }); + } + }); + return deferred; + }, + + promiseGetJsonFile: function (filename) { + var deferred = when.defer(); + + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + fs.readFile(filename, function (err, data) { + if (err) { + logger.error("error reading " + filename, err); + deferred.reject(); + } + + try { + var obj = JSON.parse(data); + deferred.resolve(obj); + } + catch(ex) { + logger.error("Error parsing " + filename + " " + ex); + deferred.reject(ex); + } + }); + } + }); + return deferred; + }, + + promiseStreamFile: function (filename) { + var deferred = when.defer(); + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + var readStream = fs.createReadStream(filename); + + //TODO: catch can't read file stuff. + + deferred.resolve(readStream); + } + }); + return deferred; + }, + + bufferToHexString: function(buf) { + if (!buf || (buf.length <= 0)) { return null; } + + var r = []; + for(var i=0;i= 0) { + return filename.substr(idx); + } + else { + return filename; + } + }, + filenameNoExt: function (filename) { + if (!filename || (filename.length === 0)) { + return filename; + } + + var idx = filename.lastIndexOf('.'); + if (idx >= 0) { + return filename.substr(0, idx); + } + else { + return filename; + } + }, + + indexOf: function (arr, val) { + if (!arr || (arr.length == 0)) { + return -1; + } + for (var i = 0; i < arr.length; i++) { + if (arr[i] == val) { + return i; + } + } + return -1; + }, + contains: function (arr, val) { + return (that.indexOf(arr, val) !== -1); + }, + pipeDeferred: function(left, right) { + when(left).then(function() { + right.resolve.apply(right, arguments); + }, function() { + right.reject.apply(right, arguments); + }) + }, + + /** + * Non-competitive version of when.any + * @param arr + */ + deferredAny: function (arr) { + var tmp = when.defer(); + var index = -1; + var reasons = []; + + //step through a list of FUNCTIONS that return deferreds, + //process in order and resolve with the first one that resolves. + + var doNext = function () { + index++; + if (index > arr.length) { + tmp.reject(reasons); + return; + } + else if (!arr[index]) { + process.nextTick(doNext); + return; + } + + var promise = null; + try { + promise = arr[index](); + } + catch (ex) { + logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); + } + + if (promise) { + when(promise).then( + function () { + //chain this forward and resolve! we're done! + tmp.resolve.apply(tmp, arguments); + }, + function (err) { + reasons.push(err); + + //lets try the next one! + process.nextTick(doNext); + }); + } + else { + process.nextTick(doNext); + } + }; + + process.nextTick(doNext); + return tmp.promise; + }, + + check_requires_update: function(device, target) { + var version = (device && device["cc3000_patch_version"]); + return (version && (version < target)); + }, + + getIPAddresses: function () { + //adapter = adapter || "eth0"; + var results = []; + var nics = os.networkInterfaces(); + + for (var name in nics) { + var nic = nics[name]; + + for (var i = 0; i < nic.length; i++) { + var addy = nic[i]; + + if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { + continue; + } + + results.push(addy.address); + } + } + + return results; + }, + + slugify: function (text) { + return text.toString().toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text + }, + + foo: null +}; \ No newline at end of file diff --git a/js/oauth_clients.json b/js/oauth_clients.json index e7e46d54..82af918d 100644 --- a/js/oauth_clients.json +++ b/js/oauth_clients.json @@ -3,10 +3,5 @@ "client_id" : "particle", "client_secret" : "particle", "grants" : [ "password", "refresh_token" ] - }, - { - "client_id" : "CLI2", - "client_secret" : "client_secret_here", - "grants" : [ "password", "refresh_token" ] } -] +] \ No newline at end of file diff --git a/orgs/company.json b/js/orgs/company.json similarity index 100% rename from orgs/company.json rename to js/orgs/company.json diff --git a/js/package.json b/js/package.json new file mode 100644 index 00000000..a1a660ba --- /dev/null +++ b/js/package.json @@ -0,0 +1,37 @@ +{ + "name": "spark-server", + "version": "0.1.1", + "license": "AGPL-3.0", + "repository": { + "type": "git", + "url": "https://github.com/spark/spark-server" + }, + "homepage": "https://github.com/spark/spark-server", + "bugs": "https://github.com/spark/spark-server/issues", + "author": { + "name": "David Middlecamp", + "email": "david@spark.io", + "url": "https://www.spark.io/" + }, + "dependencies": { + "body-parser": "^1.15.2", + "connect-multiparty": "^2.0.0", + "express": "^4.14.0", + "express-oauth-server": "git://github.com/durielz/express-oauth-server.git", + "hogan-express": "^0.5.2", + "moment": "*", + "morgan": "^1.7.0", + "request": "*", + "spark-protocol": "git://github.com/durielz/spark-protocol.git", + "ursa": "*", + "when": "*", + "xtend": "*" + }, + "scripts": { + "start": "node main.js" + }, + "main": "main.js", + "contributors": [ + "Kenneth Lim " + ] +} diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js new file mode 100644 index 00000000..b2db7679 --- /dev/null +++ b/js/views/EventViews001.js @@ -0,0 +1,339 @@ +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +*/ + +var settings = require('../settings.js'); +var CoreController = require('../lib/CoreController.js'); + +var Api = require('./api_v1.js'); +var utilities = require("../lib/utilities.js"); +var logger = require('../lib/logger.js'); + +var when = require('when'); +var sequence = require('when/sequence'); +var pipeline = require('when/pipeline'); + +var moment = require('moment'); + +var EventsApi = { + loadViews: function (app) { + + // GET /v1/events[/:event_name] + // GET /v1/devices/events[/:event_name] + // GET /v1/devices/:device_id/events[/:event_name] + + app.get('/v1/events', EventsApi.get_events); + app.get('/v1/events/:event_name', EventsApi.get_events); + + app.get('/v1/devices/events', EventsApi.get_my_events); + app.post('/v1/devices/events', EventsApi.send_an_event); + app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); + + app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); + app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); + + app.get('/v1/products/:productIdOrSlug/events', app.oauth.authenticate(), EventsApi.get_product_events); + }, + + + //----------------------------------------------------------------- + + pipeEvents: function (socket, req, res, next, filterCoreId, filterProductId) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + /* + Start SSE + */ + + req.socket.setNoDelay(); + + res.writeHead(200, { + "Connection": "keep-alive", + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no" + }); + res.write(":ok\n\n"); + + + var _idleTimer = null; + var _lastMessage = null; + var keepAlive = function() { + if (((new Date()) - _lastMessage) >= 9000) { + _lastMessage = new Date(); + res.write("\n"); + checkSocket(); + } + }; + + //if nothing gets sent for 9 seconds, send a newline. + var aliveInterval = setInterval(keepAlive, 3000); + + var checkSocket = function () { + try { + if (!socket) { + cleanup(); + return false; + } + + if (res.socket.destroyed) { + logger.log("Socket destroyed, cleaning up Event listener"); + cleanup(); + return false; + } + } + catch (ex) { + logger.error("pipeEvents - error checking socket ", ex); + } + return true; + }; + + var cleanup = function () { + try { + if (socket) { + socket.close(); + socket = null; + } + } + catch (ex) { + logger.error("pipeEvents - event socket close err: ", ex); + } + + try { + if (res.socket) { + res.socket.end(); + } + res.end(); + } + catch (ex) { + logger.error("pipeEvents - response close err: ", ex); + } + + try { + if (aliveInterval) { + clearInterval(aliveInterval); + aliveInterval = null; + } + } + catch (ex) { + logger.error("pipeEvents - clear interval err: ", ex); + } + + }; + + + // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events + var writeEventGen = function (isPublic) { + return function (name, data, ttl, published_at, coreid) { + if (filterCoreId && (filterCoreId != coreid)) { + return; + } + + if (!checkSocket()) { + return; + } + + if(filterProductId == null && !Api.hasDevice(coreid, userid)) { + return; + } + + if(filterProductId != null && !Api.hasProduct(filterProductId, userid)) { + return; + } + + try { + _lastMessage = new Date(); + + //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. + name = (name) ? name.toString().replace(userid + "/", "") : null; + + var obj = { + data: data ? data.toString() : null, + ttl: ttl ? ttl.toString() : null, + published_at: (published_at) ? published_at.toString() : null, + coreid: (coreid) ? coreid.toString() : null + }; + res.write("event: " + name + "\n"); + res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies + } + catch (ex) { + logger.error("pipeEvents - write error: " + ex); + } + + //OTHER HEADERS: + //retry: ? + //id: ? //if we want to support resuming + //TODO: escape newlines in message? + }; + }; + + socket.on('public', writeEventGen(true)); + socket.on('private', writeEventGen(false)); + + req.on("close", cleanup); + req.on("end", cleanup); + res.on("close", cleanup); + res.on("finish", cleanup); + //res.setTimeout(30 * 1000, cleanup); + }, + + + get_events: function (req, res, next) { + var name = req.params.event_name; + name = name || ""; + var socket = new CoreController(); + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } +// if (userid) { +// socket.authorize(userid); +// } + + //----------------------------------- + //get firehose and my private events. + //socket.subscribe(true, name); + //socket.subscribe(true, name); + socket.subscribe(false, name, userid); + + + //send it all through + EventsApi.pipeEvents(socket, req, res, next); + }, + get_my_events: function (req, res, next) { + var name = req.params.event_name; + name = name || ""; + var socket = new CoreController(); + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } +// if (userid) { +// socket.authorize(userid); +// } + + //----------------------------------- + //get my events: + //socket.subscribe(true, name); + socket.subscribe(true, name, userid); + socket.subscribe(false, name, userid); + + //don't filter by core id + EventsApi.pipeEvents(socket, req, res, next); + }, + get_core_events: function (req, res, next) { + var name = req.params.event_name; + var socket = new CoreController(); + name = name || ""; + var coreid = req.coreID || req.params.coreid; + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + //check if core is owned + if(!Api.hasDevice(coreid, userid)) { + return next(); + } +// if (userid) { +// socket.authorize(userid); +// } + + + //----------------------------------- + //get core events + //socket.subscribe(true, name); + socket.subscribe(true, name, userid,coreid); + socket.subscribe(false, name, userid,coreid); + + //----------------------------------- + //filter to core id + EventsApi.pipeEvents(socket, req, res, next, coreid); + }, + + + send_an_event: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req), + socketID = Api.getSocketID(userid), + eventName = req.body.name, + data = req.body.data, + ttl = req.body.ttl || 60, + private_str = req.body.private; + + if(!userid) { + return next(); + } + + var is_public = (!private_str || (private_str == "") || (private_str == "false")); + + var socket = new CoreController(socketID); + var success = socket.sendEvent(is_public, + eventName, + userid, + data, + parseInt(ttl), + moment().toISOString(), + userid + ); + + var autoClose = setTimeout(function () { + socket.close(); + res.json({ok: success}); + }, 250); + }, + + get_product_events: function (req, res, next) { + var name = req.params.event_name; + var socket = new CoreController(); + name = name || ""; + var productid = req.params.productIdOrSlug; + productid = productid || null; + + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + //check if product is owned by user + if(!Api.hasProduct(productid, userid)) { + return next(); + } +// if (userid) { +// socket.authorize(userid); +// } + + + //----------------------------------- + //get product events + //socket.subscribe(true, name); + socket.subscribe(true, name, userid); + socket.subscribe(false, name, userid); + + //----------------------------------- + //filter to core id + EventsApi.pipeEvents(socket, req, res, next, null, productid); + }, + + _: null +}; + + +module.exports = EventsApi; \ No newline at end of file diff --git a/js/views/api_v1.js b/js/views/api_v1.js new file mode 100644 index 00000000..d1b093b6 --- /dev/null +++ b/js/views/api_v1.js @@ -0,0 +1,1194 @@ +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +*/ + +var settings = require('../settings.js'); + +var CoreController = require('../lib/CoreController.js'); +var PasswordHasher = require('../lib/PasswordHasher.js'); + +var sequence = require('when/sequence'); +var parallel = require('when/parallel'); +var pipeline = require('when/pipeline'); + +var logger = require('../lib/logger.js'); +var utilities = require("../lib/utilities.js"); + +var fs = require('fs'); +var when = require('when'); +var util = require('util'); +var path = require('path'); +var ursa = require('ursa'); +var moment = require('moment'); + +var multipart = require('connect-multiparty'); +var multipartMiddleware = multipart(); + +/* + * TODO: modularize duplicate code + * TODO: implement proper session handling / user authentication + * TODO: add cors handler without losing :params support + * + */ + +var Api = { + loadViews: function (app) { + + //our middleware + app.param("coreid", Api.loadCore); + + + //core functions / variables + app.post('/v1/devices/:coreid/:func', Api.fn_call); + app.get('/v1/devices/:coreid/:var', Api.get_var); + + app.put('/v1/devices/:coreid', multipartMiddleware, Api.set_core_attributes); + app.get('/v1/devices/:coreid', Api.get_core_attributes);//ok customer + + //doesn't need per-core permissions, only shows owned cores. + app.get('/v1/devices', Api.list_devices);//ok customer + + app.post('/v1/provisioning/:coreid', Api.provision_core); + + app.delete('/v1/devices/:coreid', Api.release_device); + + app.post('/v1/devices', Api.claim_device); + app.post('/v1/device_claims', app.oauth.authenticate(), Api.get_claim_code); + + /*products*/ + app.get('/v1/products', app.oauth.authenticate(), Api.list_products); + app.get('/v1/products/:productIdOrSlug', app.oauth.authenticate(), Api.get_product); + app.post('/v1/products/:productIdOrSlug/device_claims', app.oauth.authenticate(), Api.get_product_claim_code); + app.delete('/v1/products/:productIdOrSlug/devices/:coreid', app.oauth.authenticate(), Api.release_product_device); + app.get('/v1/products/:productIdOrSlug/customers', app.oauth.authenticate(), Api.get_product_customers); + + app.post('/v1/products/:productIdOrSlug/devices', app.oauth.authenticate(), Api.add_product_device); + }, + + getSocketID: function (userID) { + return userID + "_" + global._socket_counter++; + }, + + getUserID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); + return false; + } + if(req.app.locals.oauth.token.scope && req.app.locals.oauth.token.scope.indexOf("customer=") > -1) { + logger.log("Customer token"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + getCustomerID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); + return false; + } + if(!req.app.locals.oauth.token.scope || req.app.locals.oauth.token.scope.indexOf("customer=") == -1) { + logger.log("User token"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + getUserOrCustomerID: function (req) { + if (!req.app.locals.oauth) { + logger.log("Token obj was empty"); + return false; + } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.app.locals.oauth.token.user; + }, + + hasDevice: function (coreID, userID) { + var userObj = global.roles.getUserByDevice(coreID); + //check core permission + if(userObj && userObj._id == userID) { + return true; + } else { + //logger.log("device Permission Denied"); + return false; + } + }, + + hasOrg: function (userID) { + var orgObj = global.roles.getOrgByUserid(userID); + //check user permission + if(orgObj) { + return true; + } else { + return false; + } + }, + + hasProduct: function (productIdOrSlug, userID) { + var orgObj = global.roles.getOrgByProductid(productIdOrSlug); + //check user permission + if(orgObj && orgObj.user_id == userID) { + return true; + } else { + return false; + } + }, + + list_devices: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + logger.log("ListDevices", { userID: userid }); + + //give me all the cores + + //var allCoreIDs = global.server.getAllCoreIDs(), + var userDevicesIDs = global.roles.devices[userid], + devices = [], + connected_promises = []; + + for (var index in userDevicesIDs) { + var coreid = userDevicesIDs[index]; + + if (!coreid) { + continue; + } + + var core = global.server.getCoreAttributes(coreid); + + var device = { + id: coreid, + name: core ? core.name : null, + last_app: core ? core.last_flashed_app_name : null, + product_id: core ? core.spark_product_id : null, + firmware_version: core ? core.product_firmware_version : null, + system_version: core ? core.spark_system_version : null, + last_heard: null + }; + + if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { + device["requires_deep_update"] = true; + } + + devices.push(device); + connected_promises.push(Api.isDeviceOnline(userid, device.id)); + } + + logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); + + //switched 'done' to 'then' - threw an exception with 'done' here. + when.settle(connected_promises).then(function (descriptors) { + for (var i = 0; i < descriptors.length; i++) { + var desc = descriptors[i]; + devices[i].connected = ('rejected' !== desc.state); + devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; + } + + res.status(200).json(devices); + }); + }, + + get_core_attributes: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + var socketID = Api.getSocketID(userid), + coreID = req.coreID, + socket = new CoreController(socketID); + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); + + var objReady = parallel([ + function () { + return when.resolve(global.server.getCoreAttributes(coreID)); + }, + function () { + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); + }, + function () { + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Ping" }, { cmd: "Pong" })); + } + ]); + + //whatever we get back... + when(objReady).done(function (results) { + try { + + if (!results || (results.length != 3)) { + logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); + res.status(404).json("Oops, I couldn't find that core"); + return; + } + + //we're expecting descResult to be an array: [ sender, {} ] + var doc = results[0], + descResult = results[1], + coreState = null, + descPingResult = results[2]; + + if (!doc || !doc.coreID) { + logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); + res.status(404).json("Oops, I couldn't find that core"); + return; + } + + if (util.isArray(descResult) && (descResult.length > 1)) { + coreState = descResult[1].state || {}; + } + if (!coreState) { + logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); + } + + var device = { + id: doc.coreID, + name: doc.name || null, + last_app: doc.last_flashed, + product_id: doc.spark_product_id || null, + firmware_version: doc.product_firmware_version || null, + system_version: doc.spark_system_version || null, + //connected: !!coreState, + connected: (descPingResult != "Request Timed Out") ? descPingResult[1].online : false, + last_heard: (descPingResult != "Request Timed Out") ? descPingResult[1].lastPing : null, + variables: (coreState) ? coreState.v : null, + functions: (coreState) ? coreState.f : null, + cc3000_patch_version: doc.cc3000_driver_version + }; + + if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { + device["requires_deep_update"] = true; + } + + res.json(device); + } + catch (ex) { + logger.error("get_core_attributes merge error: " + ex); + res.status(500).json({ Error: "get_core_attributes error: " + ex }); + } + }, null); + + //get_core_attribs - end + }, + + set_core_attributes: function (req, res, next) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + if(!Api.hasDevice(coreID, userid) && !Api.hasOrg(userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + var promises = []; + + logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); + + var coreName = req.body ? req.body.name : null; + if (coreName != null) { + logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); + + global.server.setCoreAttribute(req.coreID, "name", coreName); + promises.push(when.resolve({ ok: true, name: coreName })); + } + + var hasFiles = req.files && req.files.file; + if (hasFiles) { + console.log("file"); + //oh hey, you want to flash firmware? + promises.push(Api.compile_and__or_flash_dfd(req)); + } + + var signal = req.body && req.body.signal; + if (signal) { + //get your hands up in the air! Or down. + promises.push(Api.core_signal_dfd(req)); + } + + var flashApp = req.body ? req.body.app : null; + if (flashApp) { + // It makes no sense to flash a known app and also + // either signal or flash a file sent with the request + if (!hasFiles && !signal) { + + // MUST sanitize app name here, before sending to Device Service + if (utilities.contains(settings.known_apps, flashApp)) { + promises.push(Api.flash_known_app_dfd(req)); + } + else { + promises.push(when.reject("Can't flash unknown app " + flashApp)); + } + } + } + + var app_id = req.body ? req.body.app_id : null; + if (app_id && !hasFiles && !signal && !flashApp) { + //we have an app id, and no files, and stuff + //we must be flashing from the db! + promises.push(Api.flash_app_in_db_dfd(req)); + } + + var app_example_id = req.body ? req.body.app_example_id : null; + if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { + //we have an app id, and no files, and stuff + //we must be flashing from the db! + promises.push(Api.flash_example_app_in_db_dfd(req)); + } + + + if (promises.length >= 1) { + when.all(promises).done( + function (results) { + var aggregate = {}; + for (var i in results) { + for (var key in results[i]) { + aggregate[key] = results[i][key]; + } + } + res.json(aggregate); + }, + function (err) { + res.json({ ok: false, errors: [err] }); + } + ); + } + else { + logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); + res.json({error: "Nothing to do?"}); + } + }, + + isDeviceOnline: function (userID, coreID) { + var tmp = when.defer(); + + var socketID = Api.getSocketID(userID); + var socket = new CoreController(socketID); + + var failTimer = setTimeout(function () { + logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); + socket.close(); + tmp.reject("Device is not connected"); + }, settings.isCoreOnlineTimeout); + + + //setup listener for response back from the device service + socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { + clearTimeout(failTimer); + socket.close(); + + logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); + + if (msg && msg.online) { + tmp.resolve(msg); + } + else { + tmp.reject(["Core isn't online", 404]); + } + + }, true); + + logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); + + //send it along to the device service + if (!socket.send(coreID, { cmd: "Ping" })) { + tmp.reject("Device is not connected"); + } + + return tmp.promise; + }, + + get_claim_code: function (req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("GenerateClaimCode", { userID: userid }); + + var userDevicesIDs = global.roles.devices[userid]; + PasswordHasher.generateSalt(function (err, code) { + code = code.toString('base64'); + code = code.substring(0, 63); + + when(global.roles.addClaimCode(code, userid)).then( + function () { + res.json({ + claim_code: code, + device_ids: userDevicesIDs + }); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); + }); + }, + + claim_device: function (req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + var coreid = req.body.id; + var core = global.server.getCoreAttributes(coreid); + + if(coreid) { + + if(core.claimCode) { + var userObj = global.roles.getUserByClaimCode(core.claimCode); + + if(user) { + when(global.roles.addDevice(coreid, userObj)).then( + function () { + var claimInfo = { + user_id : userObj._id, + id: coreid, + connected: false, + ok: true + } + + when(Api.isDeviceOnline(userid, coreid)) + .then( + function (desc) { + claimInfo.connected = ('rejected' !== desc.state); + + global.server.setCoreAttribute(coreid, "claimed", true); + res.json(claimInfo); + }, + function (err) { + res.status(404).json({ + ok: false, + errors: [ + "Device is not connected" + ] + }); + } + ); + }, + function (err) { + res.status(403).json({ + ok: false, + errors: [ + "That belongs to someone else. To request a transfer add ?request_transfer=true to the URL." + ] + }); + } + ); + } else { + res.status(404).json({ + ok: false, + errors: [ + {} + ] + }); + } + } else { + res.status(404).json({ + ok: false, + errors: [ + {} + ] + }); + } + } else { + res.status(404).json({ + ok: false, + errors: [ + "data.deviceID is empty" + ] + }); + } + }, + + release_device: function (req, res, next) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + when(global.roles.removeDevice(coreID, userid)).then( + function () { + global.server.setCoreAttribute(coreID, "claimed", false); + res.json({'ok' : true }); + }, function (err) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + } + ); + }, + + //called when the core send its claim code + linkDevice: function (coreid, claimCode, productid) { + var userObj = global.roles.getUserByClaimCode(claimCode); + if(userObj) { + logger.log("Linking Device...", { coreID: coreid }); + + if(userObj.org) { //if customer + //check if coreid is present in product devices + var productObj = global.roles.getProductByProductid(productid); + var index = utilities.indexOf(productObj.devices, coreid); + if (index > -1) { + for (var i = 0; i < global.roles.claim_codes[userObj._id].length; i++) { + var claimCodeObj = global.roles.claim_codes[userObj._id][i]; + //check if the claim code is valid for the product + if (claimCodeObj.code == claimCode && claimCodeObj.product_id != productid) { + logger.error("Claim code not valid for product", { claimCode: claimCode }); + return false; + } + }; + } else { + logger.error("Device not found for product"); + return false; + } + } + + when(global.roles.addDevice(coreid, userObj)).then( + function () { + global.server.setCoreAttribute(coreid, "claimed", true); + logger.log("Device linked", { coreID: coreid }); + }, + function (err) { + logger.error("Error in linking Device: "+err, { coreID: coreid }); + } + ); + } else { + logger.error("Claim code not valid", { claimCode: claimCode }); + } + }, + + safeMode: function (coreID, description) { + + logger.log("Device is in SAFE MODE", {coreID: coreID}); + + global.server.publishSpecialEvents('spark/status/safe-mode', description, coreID); + + //# spark/safe-mode-updater/updating + //{"name":"spark/safe-mode-updater/updating","data":"2","ttl":"60","published_at":"2016-01-01T14:41:0.000Z","coreid":"particle-internal"} + }, + + loadCore: function (req, res, next) { + req.coreID = req.params.coreid || req.body.id; + + //load core info! + req.coreInfo = { + "last_app": "", + "last_heard": new Date(), + "connected": false, + "deviceID": req.coreID + }; + + //if that user doesn't own that coreID, maybe they sent us a core name + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + var gotCore = utilities.deferredAny([ + function () { + var core = global.server.getCoreAttributes(req.coreID); + if (core && core.coreID) { + return when.resolve(core); + } + else { + return when.reject(); + } + }, + function () { + var core = global.server.getCoreByName(req.coreID); + if (core && core.coreID) { + return when.resolve(core); + } + else { + return when.reject(); + } + } + ]); + + when(gotCore).then( + function (core) { + if (core) { + req.coreID = core.coreID || req.coreID; + req.coreInfo = { + last_handshake_at: core.last_handshake_at + }; + } + + next(); + }, + function (err) { + //s`okay. + next(); + }) + }, + + get_var: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req); + if(!userid) { + return next(); + } + + var socketID = Api.getSocketID(userid), + coreID = req.coreID, + varName = req.params.var, + format = req.params.format; + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); + + + //send it along to the device service + //and listen for a response back from the device service + var socket = new CoreController(socketID); + var coreResult = socket.sendAndListenForDFD(coreID, + { cmd: "GetVar", name: varName }, + { cmd: "VarReturn", name: varName }, + settings.coreRequestTimeout + ); + + //sendAndListenForDFD resolves arr to ==> [sender, msg] + when(coreResult) + .then(function (arr) { + var msg = arr[1]; + if (msg.error) { + //at this point, either we didn't get a describe return, or that variable + //didn't exist, either way, 404 + return res.status(404).json({ + ok: false, + error: msg.error + }); + } + + //TODO: make me look like the spec. + msg.coreInfo = req.coreInfo; + msg.coreInfo.connected = true; + + if (format && (format == "raw")) { + return res.sendStatus("" + msg.result); + } + else { + return res.json(msg); + } + }, + function () { + res.status(408).json({error: "Timed out."}); + } + ).ensure(function () { + socket.close(); + }); + }, + + fn_call: function (req, res, next) { + var userid = Api.getUserOrCustomerID(req), + coreID = req.coreID, + funcName = req.params.func, + format = req.params.format; + + if(!userid) { + return next(); + } + + if(!Api.hasDevice(coreID, userid)) { + res.status(403).json({ + "error": "device Permission Denied", + "info": "I didn't recognize that device name or ID" + }); + return; + } + + logger.log("FunCall", { coreID: coreID, userid: userid.toString() }); + + var socketID = Api.getSocketID(userid); + var socket = new CoreController(socketID); + var core = socket.getCore(coreID); + + + var args = req.body; + delete args.access_token; + logger.log("FunCall - calling core ", { coreID: coreID, userid: userid.toString() }); + var coreResult = socket.sendAndListenForDFD(coreID, + { cmd: "CallFn", name: funcName, args: args }, + { cmd: "FnReturn", name: funcName }, + settings.coreRequestTimeout + ); + + //sendAndListenForDFD resolves arr to ==> [sender, msg] + when(coreResult) + .then( + function (arr) { + var sender = arr[0], msg = arr[1]; + + try { + //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); + if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { + res.status(404).json({ + ok: false, + error: "Function not found" + }); + } + else if (msg.error != null) { + res.status(400).json({ + ok: false, + error: msg.error + }); + } + else { + if (format && (format == "raw")) { + res.sendStatus("" + msg.result); + } + else { + res.json({ + id: core.coreID, + name: core.name || null, + last_app: core.last_flashed_app_name || null, + connected: true, + return_value: msg.result + }); + } + } + } + catch (ex) { + logger.error("FunCall handling resp error " + ex); + res.status(500).json({ + ok: false, + error: "Error while api was rendering response" + }); + } + }, + function () { + res.status(408).json({error: "Timed out."}); + } + ).ensure(function () { + socket.close(); + }); + + //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); + + // send the function call along to the device service + }, + + /** + * Ask the core to start / stop the "RaiseYourHand" signal + * @param req + */ + core_signal_dfd: function (req) { + var tmp = when.defer(); + + var userid = Api.getUserID(req), + socketID = Api.getSocketID(userid), + coreID = req.coreID, + showSignal = parseInt(req.body.signal); + + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + + logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); + + var socket = new CoreController(socketID); + var failTimer = setTimeout(function () { + socket.close(); + tmp.reject({error: "Timed out, didn't hear back"}); + }, settings.coreSignalTimeout); + + //listen for a response back from the device service + socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, + function () { + clearTimeout(failTimer); + socket.close(); + + tmp.resolve({ + id: coreID, + connected: true, + signaling: showSignal === 1 + }); + }, true); + + + //send it along to the core via the device service + socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); + + return tmp.promise; + }, + + compile_and__or_flash_dfd: function (req) { + var allDone = when.defer(); + var userid = Api.getUserID(req), + coreID = req.coreID; + + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + + // + // Did they pass us a source file or a binary file? + // + var hasSourceFiles = false; + var sourceExts = [".cpp", ".c", ".h", ".ino" ]; + if (req.files) { + for (var name in req.files) { + if (!req.files.hasOwnProperty(name)) { + continue; + } + + var ext = utilities.getFilenameExt(req.files[name].path); + if (utilities.contains(sourceExts, ext)) { + hasSourceFiles = true; + break; + } + } + } + + + if (hasSourceFiles) { + //TODO: federate? + allDone.reject("Not yet implemented"); + } + else { + //they sent a binary, just flash it! + var flashDone = Api.flash_core_dfd(req); + + //pipe rejection / resolution of flash to response + utilities.pipeDeferred(flashDone, allDone); + } + + return allDone.promise; + }, + + + /** + * Flashing firmware to the core, binary file! + * @param req + * @returns {promise|*|Function|Promise|when.promise} + */ + flash_core_dfd: function (req) { + var tmp = when.defer(); + + var userid = Api.getUserID(req), + socketID = Api.getSocketID(userid), + coreID = req.coreID; + + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + + logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); + + var args = req.query; + delete args.coreid; + + if (req.files) { + console.log(req.files); + args.data = fs.readFileSync(req.files.file.path); + //args.data = fs.readFileSync(req.files.file.file); + } + + var socket = new CoreController(socketID); + var failTimer = setTimeout(function () { + socket.close(); + tmp.reject({error: "Timed out."}); + }, settings.coreFlashTimeout); + + //listen for the first response back from the device service + socket.listenFor(coreID, { cmd: "Event", name: "Update" }, + function (sender, msg) { + clearTimeout(failTimer); + socket.close(); + + var response = { id: coreID, status: msg.message }; + if ("Update started" === msg.message) { + tmp.resolve(response); + } + else { + logger.error("flash_core_dfd rejected ", response); + tmp.reject(response); + } + + }, true); + + //send it along to the device service + socket.send(coreID, { cmd: "UFlash", args: args }); + + return tmp.promise; + }, + + provision_core: function (req, res, next) { + //if we're here, the user should be allowed to provision cores. + + var done = Api.provision_core_dfd(req); + when(done).then( + function (result) { + res.json(result); + }, + function (err) { + //different status code here? + res.status(400).json(err); + }); + }, + + provision_core_dfd: function (req) { + var result = when.defer(), + userid = Api.getUserID(req), + deviceID = req.body.deviceID, + publicKey = req.body.publicKey; + + if(!userid) { + return when.reject({ error: "No userID provided" }); + } + + if (!deviceID) { + return when.reject({ error: "No deviceID provided" }); + } + + try { + var keyObj = ursa.createPublicKey(publicKey); + if (!publicKey || (!ursa.isPublicKey(keyObj))) { + return when.reject({ error: "No key provided" }); + } + } + catch (ex) { + logger.error("error while parsing publicKey " + ex); + return when.reject({ error: "Key error " + ex }); + } + + + global.server.addCoreKey(deviceID, publicKey); + global.server.setCoreAttribute(deviceID, "registrar", userid); + global.server.setCoreAttribute(deviceID, "timestamp", new Date()); + result.resolve("Success!"); + + return result.promise; + }, + + //List products the currently authenticated user has access to. + list_products: function (req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("ListProducts", { userID: userid }); + + var orgObj = global.roles.getOrgByUserid(userid); + if(orgObj) { + var productObjs = global.roles.products[orgObj.slug]; + + //remove devices ?? + res.json({ products : productObjs }); + } else { + res.json({ products : [] }); + } + }, + + //Retrieve details for a product. + get_product: function( req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("GetProduct", { userID: userid }); + + var productid = req.params.productIdOrSlug; + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + var productObj = global.roles.getProductByProductid(productid); + + res.json({ product : productObj }); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); + } + }, + + //Generate a device claim code for a customer, scoped for a specific product. + get_product_claim_code: function (req, res, next) { + var customerid = Api.getCustomerID(req); + if(!customerid) { + return next(); + } + + var productid = req.params.productIdOrSlug; + + logger.log("GenerateProductClaimCode", { customerID: customerid }); + + var productObj = global.roles.getProductByProductid(productid); + var productDevicesIDs = productObj.devices; + PasswordHasher.generateSalt(function (err, code) { + code = code.toString('base64'); + code = code.substring(0, 63); + + when(global.roles.addProductClaimCode(code, customerid, productObj.product_id)).then( + function () { + res.json({ + claim_code: code, + device_ids: productDevicesIDs + }); + }, + function (err) { + res.json({ + ok: false, + errors: [ + err + ] + }); + } + ); + }); + }, + + //Remove a device from a product and re-assign to a generic Particle product. This endpoint will unclaim the device if it is owned by a customer. + release_product_device: function (req, res, next) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + var productid = req.params.productIdOrSlug; + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + when(global.roles.removeProductDevice(coreID, productid)).then( + function () { + global.server.setCoreAttribute(coreID, "claimed", false); + res.json({ ok:true }); + }, function (err) { + res.status(400).json({ + "code": 400, + "ok": false, + "info": "Device not found for this product" + }); + } + ); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); + } + }, + + //List Customers for a product. + get_product_customers: function( req, res, next) { + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + logger.log("GetProductCustomer", { userID: userid }); + + var productid = req.params.productIdOrSlug; + var productObj = global.roles.getProductByProductid(productid); + var productDevices = productObj.devices; + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + var customerObjs = []; + var userObjIds = []; + var devices = []; + for (var k = 0; k < productDevices.length; k++) { + var deviceid = productDevices[k]; + var userObj = global.roles.getUserByDevice(deviceid); + if(userObj && userObj.org) { //if customer + devices.push(deviceid); + var index = utilities.indexOf(userObjIds, userObj._id); + if (index == -1) { + userObjIds.push(userObj._id); + customerObjs.push({ + id: userObj._id, + email: userObj.email, + devices: userObj.devices + }); + } + } + } + res.json({ customers : customerObjs, devices : devices }); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); + } + }, + + add_product_device: function (req, res, next) { + var coreID = req.body.id; + if(!coreID) { + res.status(400).json({ ok: false, errors: [ 'id is required.' ] }); + } + var userid = Api.getUserID(req); + if(!userid) { + return next(); + } + + var productid = req.params.productIdOrSlug; + logger.log("AddingProductDevice", { productID: productid }); + + var orgObj = global.roles.getOrgByProductid(productid); + if(orgObj && orgObj.user_id == userid) { + when(global.roles.addProductDevice(coreID, productid)).then( + function () { + res.json({ ok:true }); + }, function (err) { + res.status(400).json({ + "code": 400, + "ok": false, + "info": "Device already present for that product" + }); + } + ); + } else { + res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); + } + }, + + _: null +}; + +exports = module.exports = global.api = Api; diff --git a/lib/AccessTokenViews.js b/lib/AccessTokenViews.js index 9382e00c..b5b02a0d 100644 --- a/lib/AccessTokenViews.js +++ b/lib/AccessTokenViews.js @@ -23,85 +23,81 @@ var sequence = require('when/sequence'); var pipeline = require('when/pipeline'); var PasswordHasher = require('./PasswordHasher.js'); var roles = require('./RolesController.js'); -var logger = require('./logger.js'); var AccessTokenViews = function (options) { - this.options = options; + this.options = options; }; AccessTokenViews.prototype = { - loadViews: function (app) { - app.get('/v1/access_tokens', this.index.bind(this)); - app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); - }, - index: function (req, res) { - //var credentials = AccessTokenViews.basicAuth(req); - //var credentials = this.basicAuth(req); - var credentials= AccessTokenViews.prototype.basicAuth(req); - if (!credentials) { - return res.status(401).json({ - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] - }); - } + loadViews: function (app) { + app.get('/v1/access_tokens', this.index.bind(this)); + app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); + }, + index: function (req, res) { + var credentials = AccessTokenViews.basicAuth(req); + if (!credentials) { + return res.json(401, { + ok: false, + errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] + }); + } - //if successful, should return something like: - // [ { token: d.token, expires: d.expires, client: d.client_id } ] + //if successful, should return something like: + // [ { token: d.token, expires: d.expires, client: d.client_id } ] - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - res.json(userObj.access_tokens); - }, - function () { - res.status(401).json({ ok: false, errors: ['Bad password']}); - }); - }, + when(roles.validateLogin(credentials.username, credentials.password)) + .then( + function (userObj) { + res.json(userObj.access_tokens); + }, + function () { + res.json(401, { ok: false, errors: ['Bad password']}); + }); + }, - destroy: function (req, res) { - //var credentials = AccessTokenViews.basicAuth(req); - var credentials= AccessTokenViews.prototype.basicAuth(req); - if (!credentials) { - return res.status(401).json({ - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] - }); - } + destroy: function (req, res) { + var credentials = AccessTokenViews.basicAuth(req); + if (!credentials) { + return res.json(401, { + ok: false, + errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] + }); + } - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - try { - roles.destroyAccessToken(req.params.token); - res.json({ ok: true }); - } - catch (ex) { - logger.error("error saving user " + ex); - res.status(401).json({ ok: false, errors: ['Error updating token']}); - } - }, - function () { - res.status(401).json({ ok: false, errors: ['Bad password']}); - }); - }, + when(roles.validateLogin(credentials.username, credentials.password)) + .then( + function (userObj) { + try { + roles.destroyAccessToken(req.params.token); + res.json({ ok: true }); + } + catch (ex) { + logger.error("error saving user " + ex); + res.json(401, { ok: false, errors: ['Error updating token']}); + } + }, + function () { + res.json(401, { ok: false, errors: ['Bad password']}); + }); + }, - basicAuth: function (req) { - var auth = req.get('Authorization'); - if (!auth) return null; + basicAuth: function (req) { + var auth = req.get('Authorization'); + if (!auth) return null; - var matches = auth.match(/Basic\s+(\S+)/); - if (!matches) return null; + var matches = auth.match(/Basic\s+(\S+)/); + if (!matches) return null; - var creds = new Buffer(matches[1], 'base64').toString(); - var separatorIndex = creds.indexOf(':'); - if (-1 === separatorIndex) - return null; + var creds = new Buffer(matches[1], 'base64').toString(); + var separatorIndex = creds.indexOf(':'); + if (-1 === separatorIndex) + return null; - return { - username: creds.slice(0, separatorIndex), - password: creds.slice(separatorIndex + 1) - }; - } + return { + username: creds.slice(0, separatorIndex), + password: creds.slice(separatorIndex + 1) + }; + } }; diff --git a/lib/RolesController.js b/lib/RolesController.js index 88047157..0d6c8e54 100644 --- a/lib/RolesController.js +++ b/lib/RolesController.js @@ -25,704 +25,206 @@ var PasswordHasher = require('./PasswordHasher.js'); var roles = require('./RolesController.js'); var settings = require('../settings.js'); var logger = require('./logger.js'); -var utilities = require("./utilities.js"); + function RolesController() { - this.init(); + this.init(); }; RolesController.prototype = { - users: null, - usersByToken: null, - usersByDevice: null, - usersByClaimCode: null, - usersByUsername: null, - - access_tokens: null, - refresh_tokens: null, - claim_codes: null, - - devices: null, - clients: null, - - orgsBySlug: null, - orgsByUserId: null, - - customers: null, - customersByEmail: null, - //customersByToken: null, - - orgsByProduct: null, - products : null, - - init: function () { - this._loadAndCacheUsers(); - }, - - addUser: function (userObj) { - this.users.push(userObj); - this.usersByUsername[ userObj.username ] = userObj; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var token = userObj.access_tokens[i]; - this.usersByToken[token.token] = userObj; - this.access_tokens[token.token] = token; - this.refresh_tokens[token.refresh_token] = token; - } - - //claim codes - this.claim_codes[userObj._id] = userObj.claim_codes; - for (var i = 0; i < userObj.claim_codes.length; i++) { - var claimCode = userObj.claim_codes[i]; - this.usersByClaimCode[claimCode] = userObj; - } - - //devices claimed - this.devices[userObj._id] = userObj.devices; - for (var i = 0; i < userObj.devices.length; i++) { - var deviceId = userObj.devices[i]; - this.usersByDevice[ deviceId ] = userObj; - } - }, - addCustomer: function (customerObj) { - - console.log("Loading customer " + customerObj.email); - - this.customers.push(customerObj); - this.customersByEmail[ customerObj.email ] = customerObj; - - for (var k = 0; k < customerObj.access_tokens.length; k++) { - var token = customerObj.access_tokens[k]; - this.usersByToken[token.token] = customerObj; - this.access_tokens[token.token] = token; - this.refresh_tokens[token.refresh_token] = token; - } - - //claim codes - this.claim_codes[customerObj._id] = customerObj.claim_codes; - for (var i = 0; i < customerObj.claim_codes.length; i++) { - var claimCode = customerObj.claim_codes[i]; - this.usersByClaimCode[claimCode.code] = customerObj; - } - - //devices claimed - this.devices[customerObj._id] = customerObj.devices; - for (var i = 0; i < customerObj.devices.length; i++) { - var deviceId = customerObj.devices[i]; - this.usersByDevice[ deviceId ] = customerObj; - } - }, - addClient: function (clientObj) { - this.clients.push(clientObj); - }, - addOrg: function (orgObj) { - this.orgsBySlug[orgObj.slug] = orgObj; - this.orgsByUserId[orgObj.user_id] = orgObj; - - for (var i = 0; i < orgObj.customers.length; i++) { - this.addCustomer(orgObj.customers[i]); //add customer - } - - this.products[orgObj.slug] = []; //list product - for (var j = 0; j < orgObj.products.length; j++) { - this.products[orgObj.slug].push(orgObj.products[j]); - this.orgsByProduct[ orgObj.products[j].slug ] = orgObj; - this.orgsByProduct[ orgObj.products[j].product_id ] = orgObj; - } - }, - destroyAccessToken: function (access_token) { - var userObj = this.usersByToken[access_token]; - if (!userObj) { - return true; - } - - delete this.usersByToken[access_token]; - delete this.access_tokens[access_token]; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var tokenObj = userObj.access_tokens[i]; - if (tokenObj.token == access_token) { - userObj.access_tokens.splice(i, 1); - } - } - - this.saveUser(userObj); - }, - revokeToken: function (token) { - var userObj = this.usersByToken[token.accessToken]; - if (!userObj) { - return false; - } - var tokenObj = this.access_tokens[token.accessToken]; - if(!tokenObj) { - return false; - } - - delete this.usersByToken[token.accessToken]; - delete this.access_tokens[token.accessToken]; - delete this.refresh_tokens[token.refreshToken]; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var tokenObj = userObj.access_tokens[i]; - if (tokenObj.token == token.accessToken) { - userObj.access_tokens.splice(i, 1); - } - } - if(tokenObj.scope && tokenObj.scope.indexOf("customer=") > -1) { - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - return token; - }, - addAccessToken: function (token, client, user) { - var tmp = when.defer(); - try { - var tokenObj = { - //user_id: user._id, - token: token.accessToken, - expires_at: token.accessTokenExpiresAt, - client: client.client_id, - refresh_token: token.refreshToken, - scope: token.scope - }; - - if(token.scope && token.scope.indexOf("customer=") > -1) { - //is a customer token - var email = token.scope.split("=")[1]; - var customerObj = this.customersByEmail[email]; - - this.usersByToken[token.accessToken] = customerObj; - this.access_tokens[token.accessToken] = tokenObj; - this.refresh_tokens[token.refreshToken] = tokenObj; - customerObj.access_tokens.push(tokenObj); - this.saveCustomer(customerObj); - - tmp.resolve({ - accessToken: token.accessToken, - client: client, - refreshToken: token.refreshToken, - user: customerObj._id, - scope: token.scope, - accessTokenExpiresAt: token.accessTokenExpiresAt - }); - - } else { - var userObj = this.getUserByUserid(user._id); - if(!userObj) { - //refresh_token - userObj = this.getUserByUserid(user); - } - this.usersByToken[token.accessToken] = userObj; - - this.access_tokens[token.accessToken] = tokenObj; - this.refresh_tokens[token.refreshToken] = tokenObj; - userObj.access_tokens.push(tokenObj); - this.saveUser(userObj); - - tmp.resolve({ - accessToken: token.accessToken, - client: client, - user: userObj._id, - refreshToken: token.refreshToken, - scope: token.scope, - accessTokenExpiresAt: token.accessTokenExpiresAt - }); - } - } - catch (ex) { - logger.error("Error adding access token ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addClaimCode: function (claimCode, userId) { - var tmp = when.defer(); - try { - var userObj = this.getUserByUserid(userId); - - userObj.claim_codes.push(claimCode); - this.saveUser(userObj); - - this.usersByClaimCode[ claimCode ] = userObj; - - tmp.resolve(); - } - catch (ex) { - logger.error("Error adding claim code ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addProductClaimCode: function (claimCode, customerId, productId) { - var tmp = when.defer(); - try { - var claimCodeObj = { - code : claimCode, - product_id : productId - } - - var customerObj = this.getCustomerByCustomerid(customerId); - customerObj.claim_codes.push(claimCodeObj); - this.saveCustomer(customerObj); - - this.usersByClaimCode[ claimCode ] = customerObj; - - tmp.resolve(); - } - catch (ex) { - logger.error("Error adding claim code ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addDevice: function (deviceId, userObj) { - var tmp = when.defer(); - try { - if(!this.usersByDevice[deviceId]) { - userObj.devices.push(deviceId); - if(userObj.org) { //customer - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - this.usersByDevice[ deviceId ] = userObj; - - tmp.resolve(); - } else if(this.usersByDevice[deviceId] == userObj) { - tmp.resolve(); - } else { - tmp.reject("already claimed"); - } - } - catch (ex) { - logger.error("Error adding device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - removeDevice: function (deviceId, userId) { + users: null, + usersByToken: null, + usersByUsername: null, + tokens: null, + + + init: function () { + this._loadAndCacheUsers(); + }, + + addUser: function (userObj) { + this.users.push(userObj); + this.usersByUsername[ userObj.username ] = userObj; + + if (userObj.access_token) { + this.usersByToken[userObj.access_token] = userObj; + this.tokens.push({ + user_id: userObj._id, + expires: userObj.access_token_expires_at + }); + } + + for (var i = 0; i < userObj.access_tokens.length; i++) { + var token = userObj.access_tokens[i]; + this.usersByToken[ token ] = userObj; + this.tokens[token.token] = token; + } + }, + destroyAccessToken: function (access_token) { + var userObj = this.usersByToken[access_token]; + if (!userObj) { + return true; + } + + delete this.usersByToken[access_token]; + if (userObj.access_token == access_token) { + userObj.access_token = null; + } + var idx = utilities.indexOf(userObj.access_tokens, req.params.token); + if (idx >= 0) { + userObj.access_tokens.splice(idx, 1); + } + + this.saveUser(); + }, + addAccessToken: function (accessToken, clientId, userId, expires) { var tmp = when.defer(); try { - var userObj = this.getUserByUserid(userId); - - if(this.usersByDevice[deviceId]) { - var index = utilities.indexOf(userObj.devices, deviceId); - if (index > -1) { - userObj.devices.splice(index, 1); - } - - delete this.usersByDevice[deviceId]; - - if(userObj.org) { //customer - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - tmp.resolve(); - } else { - tmp.reject('Device not found'); - } + var userObj = this.getUserByUserid(userId); + this.usersByToken[accessToken] = userObj; + + var tokenObj = { + user_id: userId, + client_id: clientId, + token: accessToken, + expires: expires, + _id: accessToken + }; + + this.tokens[accessToken] = tokenObj; + userObj.access_tokens.push(tokenObj); + this.saveUser(userObj); + tmp.resolve(); } catch (ex) { - logger.error("Error releasing device ", ex); - tmp.reject(ex); + logger.error("Error adding access token ", ex); + tmp.reject(ex); } return tmp.promise; - }, - addProductDevice: function (deviceId, productid) { - var tmp = when.defer(); - try { - var productObj = this.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, deviceId); - if (index == -1) { //if not present - productObj.devices.push(deviceId); - this.saveProduct(productObj); - - tmp.resolve(); - } else { - tmp.reject('Device already present for that product'); - } - } - catch (ex) { - logger.error("Error adding device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - removeProductDevice: function (deviceId, productid) { - var tmp = when.defer(); - try { - var productObj = this.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, deviceId); - if (index > -1) { - /*productObj.devices.splice(index, 1); - this.saveProduct(productObj);*/ - - delete this.usersByDevice[deviceId]; - - var orgObj = this.getOrgByProductid(productObj.product_id); - for (var i = 0; i < orgObj.customers.length; i++) { - var index = utilities.indexOf(orgObj.customers[i].devices, deviceId); - if (index > -1) { - orgObj.customers[i].devices.splice(index, 1); - this.saveCustomer(orgObj.customers[i]); - } - } - - tmp.resolve(); - } else { - tmp.reject('Device not found for product'); - } - } - catch (ex) { - logger.error("Error releasing device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - saveUser: function (userObj) { - var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; - var userJson = JSON.stringify(userObj, null, 2); - fs.writeFileSync(userFile, userJson); - }, - saveCustomer: function (customerObj) { - var orgObj = this.orgsBySlug[customerObj.org]; - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - var index = 0; - for (var i = 0; i < orgObj.customers.length; i++) { - var customer = orgObj.customers[i]; - if (customer._id == customerObj._id) { - orgObj.customers[i] = customerObj; - } - } - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); }, - saveProduct: function (productObj) { - var orgObj = this.orgsByProduct[productObj.slug]; - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - var index = 0; - for (var i = 0; i < orgObj.products.length; i++) { - var product = orgObj.products[i]; - if (product.id == productObj.id) { - orgObj.products[i] = productObj; - } - } - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); + + + saveUser: function (userObj) { + var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; + var userJson = JSON.stringify(userObj, null, 2); + fs.writeFileSync(userFile, userJson); }, - _loadAndCacheUsers: function () { - this.users = []; - this.usersByToken = {}; - this.usersByDevice = {}; - this.usersByUsername = {}; - this.usersByClaimCode = {}; - - this.access_tokens = {}; - this.refresh_tokens = {}; - this.claim_codes = {}; - - this.devices = []; - this.clients = []; - this.orgsBySlug = {}; - this.orgsByUserId = {}; - - this.customers = []; - this.customersByEmail = {}; - //this.customersByToken = {}; - - this.orgsByProduct = {}; - this.products = {}; - - // list files, load all user objects, index by access_tokens and usernames - // and devices - if (!fs.existsSync(settings.userDataDir)) { - fs.mkdirSync(settings.userDataDir); - } - - var files = fs.readdirSync(settings.userDataDir); - if (!files || (files.length == 0)) { - logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); - } - - for (var i = 0; i < files.length; i++) { - try { - - var filename = path.join(settings.userDataDir, files[i]); - var userObj = JSON.parse(fs.readFileSync(filename)); - - console.log("Loading user " + userObj.username); - this.addUser(userObj); - } - catch (ex) { - logger.error("RolesController - error loading user at " + filename); - } - } - - var filenameClient = settings.oauthClientsFile; - var clients = JSON.parse(fs.readFileSync(filenameClient)); - for (var j = 0; j < clients.length; j++) { - try { - console.log("Loading client " + clients[j].client_id); - this.addClient(clients[j]); - } - catch (ex) { - logger.error("RolesController - error loading client at " + filenameOrg); - } - } - - var filesOrg = fs.readdirSync(settings.orgDataDir); - - for (var k = 0; k < filesOrg.length; k++) { - try { - - var filenameOrg = path.join(settings.orgDataDir, filesOrg[k]); - var orgObj = JSON.parse(fs.readFileSync(filenameOrg)); - - console.log("Loading org " + orgObj.slug); - this.addOrg(orgObj); - } - catch (ex) { - logger.error("RolesController - error loading org at " + filenameOrg); - } - } - }, - - getClient: function ( clientId, clientSecret) { - var clientObj = this.getClientByClientid(clientId); - if (clientObj.client_secret == clientSecret || clientId == 'particle') { - return clientObj; + _loadAndCacheUsers: function () { + this.users = []; + this.usersByToken = {}; + this.usersByUsername = {}; + this.tokens = {}; + + + // list files, load all user objects, index by access_tokens and usernames + + if (!fs.existsSync(settings.userDataDir)) { + fs.mkdirSync(settings.userDataDir); } - return false; - }, - getUserByClient: function ( clientId ) { - return this.getUserByUserid(this.orgsBySlug[clientId].user_id); - }, - getUserByToken: function (access_token) { - return this.usersByToken[access_token]; - }, - getUserByDevice: function (deviceId) { - return this.usersByDevice[deviceId]; - }, - getUserByClaimCode: function (claimCode) { - return this.usersByClaimCode[claimCode]; + var files = fs.readdirSync(settings.userDataDir); + if (!files || (files.length == 0)) { + logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); + } + + for (var i = 0; i < files.length; i++) { + try { + + var filename = path.join(settings.userDataDir, files[i]); + var userObj = JSON.parse(fs.readFileSync(filename)); + + console.log("Loading user " + userObj.username); + this.addUser(userObj); + } + catch (ex) { + logger.error("RolesController - error loading user at " + filename); + } + } }, - getOrgByProductid: function (productid) { //ok - return this.orgsByProduct[productid]; + + + getUserByToken: function (access_token) { + return this.usersByToken[access_token]; }, - getOrgBySlug: function (orgSlug) { //ok - return this.orgsBySlug[orgSlug]; + + getUserByName: function (username) { + return this.usersByUsername[username]; }, - getOrgByUserid: function (user_id) { //ok - return this.orgsByUserId[user_id]; + getTokenInfoByToken: function (token) { + return this.tokens[token]; }, - getCustomerByEmail: function (email) { - return this.customersByEmail[email]; + getUserByUserid: function (userid) { + for (var i = 0; i < this.users.length; i++) { + var user = this.users[i]; + if (user._id == userid) { + return user; + } + } + return null; }, - getUserByName: function (username) { - return this.usersByUsername[username]; - }, - getTokenInfoByAccessToken: function (token) { - var tokenObj = this.access_tokens[token]; - if(!tokenObj) { - return false; - } - return { - accessToken: tokenObj.token, - client: tokenObj.client, - user: this.getUserByToken(tokenObj.token)._id, - refreshToken: tokenObj.refresh_token, - accessTokenExpiresAt: new Date(tokenObj.expires_at), - scope: tokenObj.scope - }; - }, - getTokenInfoByRefreshToken: function (token) { - var tokenObj = this.refresh_tokens[token]; - if(!tokenObj) { - return false; - } - return { - accessToken: tokenObj.token, - client: tokenObj.client, - user: this.getUserByToken(tokenObj.token)._id, - refreshToken: tokenObj.refresh_token, - refreshTokenExpiresAt: new Date(tokenObj.expires_at), - //refreshTokenExpiresAt not managed return accessTokenExpires - scope: tokenObj.scope - }; - }, - getUserByUserid: function (userid) { - for (var i = 0; i < this.users.length; i++) { - var user = this.users[i]; - if (user._id == userid) { - return user; - } - } - return null; - }, - - getProductByProductid: function (productid) { - var orgObj = this.orgsByProduct[productid]; - for (var i = 0; i < this.products[orgObj.slug].length; i++) { - var product = this.products[orgObj.slug][i]; - if (product.product_id == productid || product.slug == productid) { - return product; - } - } - return null; - }, - - getProductByUserid: function (userid) { - var orgObj = this.orgsByProduct[productid]; - for (var i = 0; i < this.products[orgObj.slug].length; i++) { - var product = this.products[orgObj.slug][i]; - if (product.product_id == productid || product.slug == productid) { - return product; - } - } - return null; - }, - - getCustomerByCustomerid: function (customerid) { - for (var i = 0; i < this.customers.length; i++) { - var customer = this.customers[i]; - if (customer._id == customerid) { - return customer; - } - } - return null; - }, - - getClientByClientid: function (clientId) { - for (var i = 0; i < this.clients.length; i++) { - var client = this.clients[i]; - if (client.client_id == clientId) { - return client; - } - } - return null; - }, - - validateHashPromise: function (user, password) { - var tmp = when.defer(); - - PasswordHasher.hash(password, user.salt, function (err, hash) { - if (err) { - logger.error("hash error " + err); - tmp.reject("Bad password"); - } - else if (hash === user.password_hash) { - tmp.resolve(user); - } - else { - tmp.reject("Bad password"); - } - }); - - return tmp.promise; - }, - - validateLogin: function (username, password) { - var userObj = this.getUserByName(username); - if (!userObj) { - return when.reject("Bad password"); - } - - return this.validateHashPromise(userObj, password); - }, - - validateClient: function (clientId, clientSecret) { - var tmp = when.defer(); - - var clientObj = this.getClient(clientId, clientSecret); - if (!clientObj) { - return tmp.reject("Bad client"); - } - - tmp.resolve(clientObj); - - return tmp.promise; - }, - - createUser: function (username, password) { - var tmp = when.defer(); - var that = this; - - PasswordHasher.generateSalt(function (err, userid) { - userid = userid.toString('base64'); - userid = userid.substring(0, 32); - - PasswordHasher.generateSalt(function (err, salt) { - salt = salt.toString('base64'); - PasswordHasher.hash(password, salt, function (err, hash) { - var user = { - _id: userid, - username: username, - password_hash: hash, - salt: salt, - access_tokens: [], - claim_codes: [], - devices: [] - }; - - var userFile = path.join(settings.userDataDir, username + ".json"); - fs.writeFileSync(userFile, JSON.stringify(user)); - - that.addUser(user); - - tmp.resolve(); - }); - }); - }); - - return tmp.promise; - }, - - createCustomer: function (clientObj, /*product,*/ orgSlug, email) { - var tmp = when.defer(); - var that = this; - - var orgObj = that.getOrgBySlug(orgSlug); - if(orgObj && orgObj.slug == clientObj.client_id) { - var customer = that.getCustomerByEmail(email); - if(!customer) { - - PasswordHasher.generateSalt(function (err, customerid) { - customerid = customerid.toString('base64'); - customerid = customerid.substring(0, 32); - - var customer = { - _id: customerid, - email: email, - org: orgObj.slug, - access_tokens: [], - claim_codes: [], - devices: [] - }; - - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - orgObj.customers.push(customer); - - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); - - that.addCustomer(customer); - - tmp.resolve(); - }); - } else { - tmp.reject('Customer '+email+' already exists'); + + validateHashPromise: function (user, password) { + var tmp = when.defer(); + + PasswordHasher.hash(password, user.salt, function (err, hash) { + if (err) { + logger.error("hash error " + err); + tmp.reject("Bad password"); } - } else { - tmp.reject('Bad product'); + else if (hash === user.password_hash) { + tmp.resolve(user); + } + else { + tmp.reject("Bad password"); + } + }); + + return tmp.promise; + }, + + + validateLogin: function (username, password) { + var userObj = this.getUserByName(username); + if (!userObj) { + return when.reject("Bad password"); } - return tmp.promise; - } + return this.validateHashPromise(userObj, password); + }, + + createUser: function (username, password) { + var tmp = when.defer(); + var that = this; + + PasswordHasher.generateSalt(function (err, userid) { + userid = userid.toString('base64'); + userid = userid.substring(0, 32); + + PasswordHasher.generateSalt(function (err, salt) { + salt = salt.toString('base64'); + PasswordHasher.hash(password, salt, function (err, hash) { + var user = { + _id: userid, + username: username, + password_hash: hash, + salt: salt, + access_tokens: [] + }; + + var userFile = path.join(settings.userDataDir, username + ".json"); + fs.writeFileSync(userFile, JSON.stringify(user)); + + that.addUser(user); + + tmp.resolve(); + }); + }); + }); + + return tmp.promise; + } }; -module.exports = global.roles = new RolesController(); +module.exports = global.roles = new RolesController(); \ No newline at end of file diff --git a/lib/utilities.js b/lib/utilities.js index 683539cf..bfa5b69d 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -25,367 +25,358 @@ var fs = require('fs'); var that; module.exports = that = { - /** - * ensures the function in the provided scope - * @param fn - * @param scope - * @returns {Function} - */ - proxy: function (fn, scope) { - return function () { - try { - return fn.apply(scope, arguments); - } - catch (ex) { - logger.error(ex); - logger.error(ex.stack); - logger.log('error bubbled up ' + ex); - } - } - }, - - /** - * Surely there is a better way to do this. - * NOTE! This function does NOT short-circuit when an in-equality is detected. This is - * to avoid timing attacks. - * @param left - * @param right - */ - bufferCompare: function (left, right) { - if ((left == null) && (right == null)) { - return true; - } - else if ((left == null) || (right == null)) { - return false; - } - - if (!Buffer.isBuffer(left)) { - left = new Buffer(left); - } - if (!Buffer.isBuffer(right)) { - right = new Buffer(right); - } - - logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); - - var same = (left.length == right.length), - i = 0, - max = left.length; - - while (i < max) { - same &= (left[i] == right[i]); - i++; - } - - return same; - }, - - /** - * Iterates over the properties of the right object, checking to make - * sure the properties on the left object match. - * @param left - * @param right - */ - leftHasRightFilter: function (left, right) { - if (!left && !right) { - return true; - } - var matches = true; - - for (var prop in right) { - if (!right.hasOwnProperty(prop)) { - continue; - } - matches &= (left[prop] == right[prop]); - } - return matches; - }, - - promiseDoFile: function (filename, callback) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - if (callback(data)) { - deferred.resolve(); - } - } - catch(ex) { - deferred.reject(ex); - } - - }); - } - }); - return deferred; - }, - - promiseGetJsonFile: function (filename) { - var deferred = when.defer(); - - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - var obj = JSON.parse(data); - deferred.resolve(obj); - } - catch(ex) { - logger.error("Error parsing " + filename + " " + ex); - deferred.reject(ex); - } - }); - } - }); - return deferred; - }, - - promiseStreamFile: function (filename) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - var readStream = fs.createReadStream(filename); - - //TODO: catch can't read file stuff. - - deferred.resolve(readStream); - } - }); - return deferred; - }, - - bufferToHexString: function(buf) { - if (!buf || (buf.length <= 0)) { return null; } - - var r = []; - for(var i=0;i= 0) { - return filename.substr(idx); - } - else { - return filename; - } - }, - filenameNoExt: function (filename) { - if (!filename || (filename.length === 0)) { - return filename; - } - - var idx = filename.lastIndexOf('.'); - if (idx >= 0) { - return filename.substr(0, idx); - } - else { - return filename; - } - }, - - indexOf: function (arr, val) { - if (!arr || (arr.length == 0)) { - return -1; - } - for (var i = 0; i < arr.length; i++) { - if (arr[i] == val) { - return i; - } - } - return -1; - }, - contains: function (arr, val) { - return (that.indexOf(arr, val) !== -1); - }, - pipeDeferred: function(left, right) { - when(left).then(function() { - right.resolve.apply(right, arguments); - }, function() { - right.reject.apply(right, arguments); - }) - }, - - /** - * Non-competitive version of when.any - * @param arr - */ - deferredAny: function (arr) { - var tmp = when.defer(); - var index = -1; - var reasons = []; - - //step through a list of FUNCTIONS that return deferreds, - //process in order and resolve with the first one that resolves. - - var doNext = function () { - index++; - if (index > arr.length) { - tmp.reject(reasons); - return; - } - else if (!arr[index]) { - process.nextTick(doNext); - return; - } - - var promise = null; - try { - promise = arr[index](); - } - catch (ex) { - logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); - } - - if (promise) { - when(promise).then( - function () { - //chain this forward and resolve! we're done! - tmp.resolve.apply(tmp, arguments); - }, - function (err) { - reasons.push(err); - - //lets try the next one! - process.nextTick(doNext); - }); - } - else { - process.nextTick(doNext); - } - }; - - process.nextTick(doNext); - return tmp.promise; - }, - - check_requires_update: function(device, target) { - var version = (device && device["cc3000_patch_version"]); - return (version && (version < target)); - }, - - getIPAddresses: function () { - //adapter = adapter || "eth0"; - var results = []; - var nics = os.networkInterfaces(); - - for (var name in nics) { - var nic = nics[name]; - - for (var i = 0; i < nic.length; i++) { - var addy = nic[i]; - - if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { - continue; - } - - results.push(addy.address); - } - } - - return results; - }, - - slugify: function (text) { - return text.toString().toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/[^\w\-]+/g, '') // Remove all non-word chars - .replace(/\-\-+/g, '-') // Replace multiple - with single - - .replace(/^-+/, '') // Trim - from start of text - .replace(/-+$/, ''); // Trim - from end of text - }, - - foo: null + /** + * ensures the function in the provided scope + * @param fn + * @param scope + * @returns {Function} + */ + proxy: function (fn, scope) { + return function () { + try { + return fn.apply(scope, arguments); + } + catch (ex) { + logger.error(ex); + logger.error(ex.stack); + logger.log('error bubbled up ' + ex); + } + } + }, + + /** + * Surely there is a better way to do this. + * NOTE! This function does NOT short-circuit when an in-equality is detected. This is + * to avoid timing attacks. + * @param left + * @param right + */ + bufferCompare: function (left, right) { + if ((left == null) && (right == null)) { + return true; + } + else if ((left == null) || (right == null)) { + return false; + } + + if (!Buffer.isBuffer(left)) { + left = new Buffer(left); + } + if (!Buffer.isBuffer(right)) { + right = new Buffer(right); + } + + logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); + + var same = (left.length == right.length), + i = 0, + max = left.length; + + while (i < max) { + same &= (left[i] == right[i]); + i++; + } + + return same; + }, + + /** + * Iterates over the properties of the right object, checking to make + * sure the properties on the left object match. + * @param left + * @param right + */ + leftHasRightFilter: function (left, right) { + if (!left && !right) { + return true; + } + var matches = true; + + for (var prop in right) { + if (!right.hasOwnProperty(prop)) { + continue; + } + matches &= (left[prop] == right[prop]); + } + return matches; + }, + + promiseDoFile: function (filename, callback) { + var deferred = when.defer(); + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + fs.readFile(filename, function (err, data) { + if (err) { + logger.error("error reading " + filename, err); + deferred.reject(); + } + + try { + if (callback(data)) { + deferred.resolve(); + } + } + catch(ex) { + deferred.reject(ex); + } + + }); + } + }); + return deferred; + }, + + promiseGetJsonFile: function (filename) { + var deferred = when.defer(); + + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + fs.readFile(filename, function (err, data) { + if (err) { + logger.error("error reading " + filename, err); + deferred.reject(); + } + + try { + var obj = JSON.parse(data); + deferred.resolve(obj); + } + catch(ex) { + logger.error("Error parsing " + filename + " " + ex); + deferred.reject(ex); + } + }); + } + }); + return deferred; + }, + + promiseStreamFile: function (filename) { + var deferred = when.defer(); + fs.exists(filename, function (exists) { + if (!exists) { + logger.error("File: " + filename + " doesn't exist."); + deferred.reject(); + } + else { + var readStream = fs.createReadStream(filename); + + //TODO: catch can't read file stuff. + + deferred.resolve(readStream); + } + }); + return deferred; + }, + + bufferToHexString: function(buf) { + if (!buf || (buf.length <= 0)) { return null; } + + var r = []; + for(var i=0;i= 0) { + return filename.substr(idx); + } + else { + return filename; + } + }, + filenameNoExt: function (filename) { + if (!filename || (filename.length === 0)) { + return filename; + } + + var idx = filename.lastIndexOf('.'); + if (idx >= 0) { + return filename.substr(0, idx); + } + else { + return filename; + } + }, + + indexOf: function (arr, val) { + if (!arr || (arr.length == 0)) { + return -1; + } + for (var i = 0; i < arr.length; i++) { + if (arr[i] == val) { + return i; + } + } + return -1; + }, + contains: function (arr, val) { + return (that.indexOf(arr, val) !== -1); + }, + pipeDeferred: function(left, right) { + when(left).then(function() { + right.resolve.apply(right, arguments); + }, function() { + right.reject.apply(right, arguments); + }) + }, + + /** + * Non-competitive version of when.any + * @param arr + */ + deferredAny: function (arr) { + var tmp = when.defer(); + var index = -1; + var reasons = []; + + //step through a list of FUNCTIONS that return deferreds, + //process in order and resolve with the first one that resolves. + + var doNext = function () { + index++; + if (index > arr.length) { + tmp.reject(reasons); + return; + } + else if (!arr[index]) { + process.nextTick(doNext); + return; + } + + var promise = null; + try { + promise = arr[index](); + } + catch (ex) { + logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); + } + + if (promise) { + when(promise).then( + function () { + //chain this forward and resolve! we're done! + tmp.resolve.apply(tmp, arguments); + }, + function (err) { + reasons.push(err); + + //lets try the next one! + process.nextTick(doNext); + }); + } + else { + process.nextTick(doNext); + } + }; + + process.nextTick(doNext); + return tmp.promise; + }, + + check_requires_update: function(device, target) { + var version = (device && device["cc3000_patch_version"]); + return (version && (version < target)); + }, + + getIPAddresses: function () { + //adapter = adapter || "eth0"; + var results = []; + var nics = os.networkInterfaces(); + + for (var name in nics) { + var nic = nics[name]; + + for (var i = 0; i < nic.length; i++) { + var addy = nic[i]; + + if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { + continue; + } + + results.push(addy.address); + } + } + + return results; + }, + + foo: null }; \ No newline at end of file diff --git a/oauth_clients.json b/oauth_clients.json deleted file mode 100644 index e7e46d54..00000000 --- a/oauth_clients.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "client_id" : "particle", - "client_secret" : "particle", - "grants" : [ "password", "refresh_token" ] - }, - { - "client_id" : "CLI2", - "client_secret" : "client_secret_here", - "grants" : [ "password", "refresh_token" ] - } -] diff --git a/views/api_v1.js b/views/api_v1.js index d1b093b6..53e66de7 100644 --- a/views/api_v1.js +++ b/views/api_v1.js @@ -19,7 +19,7 @@ var settings = require('../settings.js'); var CoreController = require('../lib/CoreController.js'); -var PasswordHasher = require('../lib/PasswordHasher.js'); +var roles = require('../lib/RolesController.js'); var sequence = require('when/sequence'); var parallel = require('when/parallel'); @@ -35,9 +35,6 @@ var path = require('path'); var ursa = require('ursa'); var moment = require('moment'); -var multipart = require('connect-multiparty'); -var multipartMiddleware = multipart(); - /* * TODO: modularize duplicate code * TODO: implement proper session handling / user authentication @@ -46,1149 +43,646 @@ var multipartMiddleware = multipart(); */ var Api = { - loadViews: function (app) { - - //our middleware - app.param("coreid", Api.loadCore); - - - //core functions / variables - app.post('/v1/devices/:coreid/:func', Api.fn_call); - app.get('/v1/devices/:coreid/:var', Api.get_var); - - app.put('/v1/devices/:coreid', multipartMiddleware, Api.set_core_attributes); - app.get('/v1/devices/:coreid', Api.get_core_attributes);//ok customer - - //doesn't need per-core permissions, only shows owned cores. - app.get('/v1/devices', Api.list_devices);//ok customer - - app.post('/v1/provisioning/:coreid', Api.provision_core); - - app.delete('/v1/devices/:coreid', Api.release_device); - - app.post('/v1/devices', Api.claim_device); - app.post('/v1/device_claims', app.oauth.authenticate(), Api.get_claim_code); - - /*products*/ - app.get('/v1/products', app.oauth.authenticate(), Api.list_products); - app.get('/v1/products/:productIdOrSlug', app.oauth.authenticate(), Api.get_product); - app.post('/v1/products/:productIdOrSlug/device_claims', app.oauth.authenticate(), Api.get_product_claim_code); - app.delete('/v1/products/:productIdOrSlug/devices/:coreid', app.oauth.authenticate(), Api.release_product_device); - app.get('/v1/products/:productIdOrSlug/customers', app.oauth.authenticate(), Api.get_product_customers); - - app.post('/v1/products/:productIdOrSlug/devices', app.oauth.authenticate(), Api.add_product_device); - }, - - getSocketID: function (userID) { - return userID + "_" + global._socket_counter++; - }, - - getUserID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - if(req.app.locals.oauth.token.scope && req.app.locals.oauth.token.scope.indexOf("customer=") > -1) { - logger.log("Customer token"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - getCustomerID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - if(!req.app.locals.oauth.token.scope || req.app.locals.oauth.token.scope.indexOf("customer=") == -1) { - logger.log("User token"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - getUserOrCustomerID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - hasDevice: function (coreID, userID) { - var userObj = global.roles.getUserByDevice(coreID); - //check core permission - if(userObj && userObj._id == userID) { - return true; - } else { - //logger.log("device Permission Denied"); - return false; - } - }, - - hasOrg: function (userID) { - var orgObj = global.roles.getOrgByUserid(userID); - //check user permission - if(orgObj) { - return true; - } else { - return false; - } - }, - - hasProduct: function (productIdOrSlug, userID) { - var orgObj = global.roles.getOrgByProductid(productIdOrSlug); - //check user permission - if(orgObj && orgObj.user_id == userID) { - return true; - } else { - return false; - } - }, - - list_devices: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - logger.log("ListDevices", { userID: userid }); - - //give me all the cores - - //var allCoreIDs = global.server.getAllCoreIDs(), - var userDevicesIDs = global.roles.devices[userid], - devices = [], - connected_promises = []; - - for (var index in userDevicesIDs) { - var coreid = userDevicesIDs[index]; - - if (!coreid) { - continue; - } - - var core = global.server.getCoreAttributes(coreid); - - var device = { - id: coreid, - name: core ? core.name : null, - last_app: core ? core.last_flashed_app_name : null, - product_id: core ? core.spark_product_id : null, - firmware_version: core ? core.product_firmware_version : null, - system_version: core ? core.spark_system_version : null, - last_heard: null - }; - - if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - devices.push(device); - connected_promises.push(Api.isDeviceOnline(userid, device.id)); - } - - logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); - - //switched 'done' to 'then' - threw an exception with 'done' here. - when.settle(connected_promises).then(function (descriptors) { - for (var i = 0; i < descriptors.length; i++) { - var desc = descriptors[i]; - devices[i].connected = ('rejected' !== desc.state); - devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; - } - - res.status(200).json(devices); - }); - }, - - get_core_attributes: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - socket = new CoreController(socketID); - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; + loadViews: function (app) { + + //our middleware + app.param("coreid", Api.loadCore); + + + //core functions / variables + app.post('/v1/devices/:coreid/:func', Api.fn_call); + app.get('/v1/devices/:coreid/:var', Api.get_var); + + app.put('/v1/devices/:coreid', Api.set_core_attributes); + app.get('/v1/devices/:coreid', Api.get_core_attributes); + + //doesn't need per-core permissions, only shows owned cores. + app.get('/v1/devices', Api.list_devices); + + app.post('/v1/provisioning/:coreid', Api.provision_core); + + //app.delete('/v1/devices/:coreid', Api.release_device); + app.post('/v1/devices', Api.claim_device); + + }, + + getSocketID: function (userID) { + return userID + "_" + global._socket_counter++; + }, + + getUserID: function (req) { + if (!req.user) { + logger.log("User obj was empty"); + return null; } + //req.user.id is set in authorise.validateAccessToken in the OAUTH code + return req.user.id; + }, - logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); - - var objReady = parallel([ - function () { - return when.resolve(global.server.getCoreAttributes(coreID)); - }, - function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); - }, - function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Ping" }, { cmd: "Pong" })); - } - ]); - - //whatever we get back... - when(objReady).done(function (results) { - try { - - if (!results || (results.length != 3)) { - logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); - res.status(404).json("Oops, I couldn't find that core"); - return; - } - - //we're expecting descResult to be an array: [ sender, {} ] - var doc = results[0], - descResult = results[1], - coreState = null, - descPingResult = results[2]; - - if (!doc || !doc.coreID) { - logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); - res.status(404).json("Oops, I couldn't find that core"); - return; - } - - if (util.isArray(descResult) && (descResult.length > 1)) { - coreState = descResult[1].state || {}; - } - if (!coreState) { - logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); - } - - var device = { - id: doc.coreID, - name: doc.name || null, - last_app: doc.last_flashed, - product_id: doc.spark_product_id || null, - firmware_version: doc.product_firmware_version || null, - system_version: doc.spark_system_version || null, - //connected: !!coreState, - connected: (descPingResult != "Request Timed Out") ? descPingResult[1].online : false, - last_heard: (descPingResult != "Request Timed Out") ? descPingResult[1].lastPing : null, - variables: (coreState) ? coreState.v : null, - functions: (coreState) ? coreState.f : null, - cc3000_patch_version: doc.cc3000_driver_version - }; - - if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - res.json(device); - } - catch (ex) { - logger.error("get_core_attributes merge error: " + ex); - res.status(500).json({ Error: "get_core_attributes error: " + ex }); - } - }, null); - - //get_core_attribs - end - }, - - set_core_attributes: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid) && !Api.hasOrg(userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - var promises = []; - - logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); - - var coreName = req.body ? req.body.name : null; - if (coreName != null) { - logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); - - global.server.setCoreAttribute(req.coreID, "name", coreName); - promises.push(when.resolve({ ok: true, name: coreName })); - } - - var hasFiles = req.files && req.files.file; - if (hasFiles) { - console.log("file"); - //oh hey, you want to flash firmware? - promises.push(Api.compile_and__or_flash_dfd(req)); - } - - var signal = req.body && req.body.signal; - if (signal) { - //get your hands up in the air! Or down. - promises.push(Api.core_signal_dfd(req)); - } - - var flashApp = req.body ? req.body.app : null; - if (flashApp) { - // It makes no sense to flash a known app and also - // either signal or flash a file sent with the request - if (!hasFiles && !signal) { - - // MUST sanitize app name here, before sending to Device Service - if (utilities.contains(settings.known_apps, flashApp)) { - promises.push(Api.flash_known_app_dfd(req)); - } - else { - promises.push(when.reject("Can't flash unknown app " + flashApp)); - } - } - } - - var app_id = req.body ? req.body.app_id : null; - if (app_id && !hasFiles && !signal && !flashApp) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_app_in_db_dfd(req)); - } - - var app_example_id = req.body ? req.body.app_example_id : null; - if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_example_app_in_db_dfd(req)); - } - - - if (promises.length >= 1) { - when.all(promises).done( - function (results) { - var aggregate = {}; - for (var i in results) { - for (var key in results[i]) { - aggregate[key] = results[i][key]; - } - } - res.json(aggregate); - }, - function (err) { - res.json({ ok: false, errors: [err] }); - } - ); - } - else { - logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); - res.json({error: "Nothing to do?"}); - } - }, - - isDeviceOnline: function (userID, coreID) { - var tmp = when.defer(); - - var socketID = Api.getSocketID(userID); - var socket = new CoreController(socketID); - - var failTimer = setTimeout(function () { - logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); - socket.close(); - tmp.reject("Device is not connected"); - }, settings.isCoreOnlineTimeout); - - - //setup listener for response back from the device service - socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); - - if (msg && msg.online) { - tmp.resolve(msg); - } - else { - tmp.reject(["Core isn't online", 404]); - } - - }, true); - - logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); - - //send it along to the device service - if (!socket.send(coreID, { cmd: "Ping" })) { - tmp.reject("Device is not connected"); - } - - return tmp.promise; - }, - - get_claim_code: function (req, res, next) { + list_devices: function (req, res) { var userid = Api.getUserID(req); - if(!userid) { - return next(); + logger.log("ListDevices", { userID: userid }); + + //give me all the cores + + var allCoreIDs = global.server.getAllCoreIDs(), + devices = [], + connected_promises = []; + + for (var coreid in allCoreIDs) { + if (!coreid) { + continue; + } + + var core = global.server.getCoreAttributes(coreid); + + var device = { + id: coreid, + name: (core) ? core.name : null, + last_app: core ? core.last_flashed_app_name : null, + last_heard: null + }; + + if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { + device["requires_deep_update"] = true; + } + + devices.push(device); + connected_promises.push(Api.isDeviceOnline(userid, device.id)); } - - logger.log("GenerateClaimCode", { userID: userid }); - - var userDevicesIDs = global.roles.devices[userid]; - PasswordHasher.generateSalt(function (err, code) { - code = code.toString('base64'); - code = code.substring(0, 63); - - when(global.roles.addClaimCode(code, userid)).then( - function () { - res.json({ - claim_code: code, - device_ids: userDevicesIDs - }); - }, - function (err) { - res.json({ - ok: false, - errors: [ - err - ] - }); - } - ); + + logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); + + //switched 'done' to 'then' - threw an exception with 'done' here. + when.settle(connected_promises).then(function (descriptors) { + for (var i = 0; i < descriptors.length; i++) { + var desc = descriptors[i]; + + devices[i].connected = ('rejected' !== desc.state); + devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; + } + + res.json(200, devices); }); - }, - - claim_device: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - var coreid = req.body.id; - var core = global.server.getCoreAttributes(coreid); - - if(coreid) { - - if(core.claimCode) { - var userObj = global.roles.getUserByClaimCode(core.claimCode); - - if(user) { - when(global.roles.addDevice(coreid, userObj)).then( - function () { - var claimInfo = { - user_id : userObj._id, - id: coreid, - connected: false, - ok: true - } - - when(Api.isDeviceOnline(userid, coreid)) - .then( - function (desc) { - claimInfo.connected = ('rejected' !== desc.state); - - global.server.setCoreAttribute(coreid, "claimed", true); - res.json(claimInfo); - }, - function (err) { - res.status(404).json({ - ok: false, - errors: [ - "Device is not connected" - ] - }); - } - ); - }, - function (err) { - res.status(403).json({ - ok: false, - errors: [ - "That belongs to someone else. To request a transfer add ?request_transfer=true to the URL." - ] - }); - } - ); - } else { - res.status(404).json({ - ok: false, - errors: [ - {} - ] - }); - } - } else { - res.status(404).json({ - ok: false, - errors: [ - {} - ] - }); - } - } else { - res.status(404).json({ - ok: false, - errors: [ - "data.deviceID is empty" - ] - }); - } - }, - - release_device: function (req, res, next) { - var coreID = req.coreID; + }, + + get_core_attributes: function (req, res) { var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - when(global.roles.removeDevice(coreID, userid)).then( + var socketID = Api.getSocketID(userid), + coreID = req.coreID, + socket = new CoreController(socketID); + + + logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); + + var objReady = parallel([ function () { - global.server.setCoreAttribute(coreID, "claimed", false); - res.json({'ok' : true }); - }, function (err) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); + return when.resolve(global.server.getCoreAttributes(coreID)); + }, + function () { + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); } - ); - }, - - //called when the core send its claim code - linkDevice: function (coreid, claimCode, productid) { - var userObj = global.roles.getUserByClaimCode(claimCode); - if(userObj) { - logger.log("Linking Device...", { coreID: coreid }); - - if(userObj.org) { //if customer - //check if coreid is present in product devices - var productObj = global.roles.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, coreid); - if (index > -1) { - for (var i = 0; i < global.roles.claim_codes[userObj._id].length; i++) { - var claimCodeObj = global.roles.claim_codes[userObj._id][i]; - //check if the claim code is valid for the product - if (claimCodeObj.code == claimCode && claimCodeObj.product_id != productid) { - logger.error("Claim code not valid for product", { claimCode: claimCode }); - return false; - } - }; - } else { - logger.error("Device not found for product"); - return false; + ]); + + //whatever we get back... + when(objReady).done(function (results) { + try { + + if (!results || (results.length != 2)) { + logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); + res.json(404, "Oops, I couldn't find that core"); + return; } - } - - when(global.roles.addDevice(coreid, userObj)).then( - function () { - global.server.setCoreAttribute(coreid, "claimed", true); - logger.log("Device linked", { coreID: coreid }); - }, - function (err) { - logger.error("Error in linking Device: "+err, { coreID: coreid }); + + + //we're expecting descResult to be an array: [ sender, {} ] + var doc = results[0], + descResult = results[1], + coreState = null; + + if (!doc || !doc.coreID) { + logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); + res.json(404, "Oops, I couldn't find that core"); + return; } - ); - } else { - logger.error("Claim code not valid", { claimCode: claimCode }); - } + + if (util.isArray(descResult) && (descResult.length > 1)) { + coreState = descResult[1].state || {}; + } + if (!coreState) { + logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); + } + + var device = { + id: doc.coreID, + name: doc.name || null, + last_app: doc.last_flashed, + connected: !!coreState, + variables: (coreState) ? coreState.v : null, + functions: (coreState) ? coreState.f : null, + cc3000_patch_version: doc.cc3000_driver_version + }; + + if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { + device["requires_deep_update"] = true; + } + + res.json(device); + } + catch (ex) { + logger.error("get_core_attributes merge error: " + ex); + res.json(500, { Error: "get_core_attributes error: " + ex }); + } + }, null); + + //get_core_attribs - end }, - - safeMode: function (coreID, description) { - - logger.log("Device is in SAFE MODE", {coreID: coreID}); - - global.server.publishSpecialEvents('spark/status/safe-mode', description, coreID); - - //# spark/safe-mode-updater/updating - //{"name":"spark/safe-mode-updater/updating","data":"2","ttl":"60","published_at":"2016-01-01T14:41:0.000Z","coreid":"particle-internal"} - }, - - loadCore: function (req, res, next) { - req.coreID = req.params.coreid || req.body.id; - - //load core info! - req.coreInfo = { - "last_app": "", - "last_heard": new Date(), - "connected": false, - "deviceID": req.coreID - }; - - //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var gotCore = utilities.deferredAny([ - function () { - var core = global.server.getCoreAttributes(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - }, - function () { - var core = global.server.getCoreByName(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - } - ]); - - when(gotCore).then( - function (core) { - if (core) { - req.coreID = core.coreID || req.coreID; - req.coreInfo = { - last_handshake_at: core.last_handshake_at - }; - } - - next(); - }, - function (err) { - //s`okay. - next(); - }) - }, - - get_var: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - varName = req.params.var, - format = req.params.format; - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); - - - //send it along to the device service - //and listen for a response back from the device service - var socket = new CoreController(socketID); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "GetVar", name: varName }, - { cmd: "VarReturn", name: varName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then(function (arr) { - var msg = arr[1]; - if (msg.error) { - //at this point, either we didn't get a describe return, or that variable - //didn't exist, either way, 404 - return res.status(404).json({ - ok: false, - error: msg.error - }); - } - - //TODO: make me look like the spec. - msg.coreInfo = req.coreInfo; - msg.coreInfo.connected = true; - - if (format && (format == "raw")) { - return res.sendStatus("" + msg.result); - } - else { - return res.json(msg); - } - }, - function () { - res.status(408).json({error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - }, - - fn_call: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req), - coreID = req.coreID, - funcName = req.params.func, - format = req.params.format; - - if(!userid) { - return next(); + + + set_core_attributes: function (req, res) { + var coreID = req.coreID; + var userid = Api.getUserID(req); + + var promises = []; + + logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); + + var coreName = req.body ? req.body.name : null; + if (coreName != null) { + logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); + + global.server.setCoreAttribute(req.coreID, "name", coreName); + promises.push(when.resolve({ ok: true, name: coreName })); } - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("FunCall", { coreID: coreID, userid: userid.toString() }); - - var socketID = Api.getSocketID(userid); - var socket = new CoreController(socketID); - var core = socket.getCore(coreID); - - - var args = req.body; - delete args.access_token; - logger.log("FunCall - calling core ", { coreID: coreID, userid: userid.toString() }); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "CallFn", name: funcName, args: args }, - { cmd: "FnReturn", name: funcName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then( - function (arr) { - var sender = arr[0], msg = arr[1]; - - try { - //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); - if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { - res.status(404).json({ - ok: false, - error: "Function not found" - }); - } - else if (msg.error != null) { - res.status(400).json({ - ok: false, - error: msg.error - }); - } - else { - if (format && (format == "raw")) { - res.sendStatus("" + msg.result); - } - else { - res.json({ - id: core.coreID, - name: core.name || null, - last_app: core.last_flashed_app_name || null, - connected: true, - return_value: msg.result - }); - } - } - } - catch (ex) { - logger.error("FunCall handling resp error " + ex); - res.status(500).json({ - ok: false, - error: "Error while api was rendering response" - }); - } - }, - function () { - res.status(408).json({error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - - //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); - - // send the function call along to the device service - }, - - /** - * Ask the core to start / stop the "RaiseYourHand" signal - * @param req - */ - core_signal_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID, - showSignal = parseInt(req.body.signal); - - if(!userid) { - return when.reject({ error: "No userID provided" }); + + var hasFiles = req.files && req.files.file; + if (hasFiles) { + //oh hey, you want to flash firmware? + promises.push(Api.compile_and__or_flash_dfd(req)); } - - logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out, didn't hear back"}); - }, settings.coreSignalTimeout); - - //listen for a response back from the device service - socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, - function () { - clearTimeout(failTimer); - socket.close(); - - tmp.resolve({ - id: coreID, - connected: true, - signaling: showSignal === 1 - }); - }, true); - - - //send it along to the core via the device service - socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); - - return tmp.promise; - }, - - compile_and__or_flash_dfd: function (req) { - var allDone = when.defer(); - var userid = Api.getUserID(req), - coreID = req.coreID; - - if(!userid) { - return when.reject({ error: "No userID provided" }); + + var signal = req.body && req.body.signal; + if (signal) { + //get your hands up in the air! Or down. + promises.push(Api.core_signal_dfd(req)); } - - // - // Did they pass us a source file or a binary file? - // - var hasSourceFiles = false; - var sourceExts = [".cpp", ".c", ".h", ".ino" ]; - if (req.files) { - for (var name in req.files) { - if (!req.files.hasOwnProperty(name)) { - continue; - } - - var ext = utilities.getFilenameExt(req.files[name].path); - if (utilities.contains(sourceExts, ext)) { - hasSourceFiles = true; - break; - } - } - } - - - if (hasSourceFiles) { - //TODO: federate? - allDone.reject("Not yet implemented"); - } - else { - //they sent a binary, just flash it! - var flashDone = Api.flash_core_dfd(req); - - //pipe rejection / resolution of flash to response - utilities.pipeDeferred(flashDone, allDone); - } - - return allDone.promise; - }, - - - /** - * Flashing firmware to the core, binary file! - * @param req - * @returns {promise|*|Function|Promise|when.promise} - */ - flash_core_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID; - - if(!userid) { - return when.reject({ error: "No userID provided" }); + + var flashApp = req.body ? req.body.app : null; + if (flashApp) { + // It makes no sense to flash a known app and also + // either signal or flash a file sent with the request + if (!hasFiles && !signal) { + + // MUST sanitize app name here, before sending to Device Service + if (utilities.contains(settings.known_apps, flashApp)) { + promises.push(Api.flash_known_app_dfd(req)); + } + else { + promises.push(when.reject("Can't flash unknown app " + flashApp)); + } + } } - - logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); - - var args = req.query; - delete args.coreid; - - if (req.files) { - console.log(req.files); - args.data = fs.readFileSync(req.files.file.path); - //args.data = fs.readFileSync(req.files.file.file); - } - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out."}); - }, settings.coreFlashTimeout); - - //listen for the first response back from the device service - socket.listenFor(coreID, { cmd: "Event", name: "Update" }, - function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - var response = { id: coreID, status: msg.message }; - if ("Update started" === msg.message) { - tmp.resolve(response); - } - else { - logger.error("flash_core_dfd rejected ", response); - tmp.reject(response); - } - - }, true); - - //send it along to the device service - socket.send(coreID, { cmd: "UFlash", args: args }); - - return tmp.promise; - }, - - provision_core: function (req, res, next) { - //if we're here, the user should be allowed to provision cores. - - var done = Api.provision_core_dfd(req); - when(done).then( - function (result) { - res.json(result); - }, - function (err) { - //different status code here? - res.status(400).json(err); - }); - }, - - provision_core_dfd: function (req) { - var result = when.defer(), - userid = Api.getUserID(req), - deviceID = req.body.deviceID, - publicKey = req.body.publicKey; - - if(!userid) { - return when.reject({ error: "No userID provided" }); + + var app_id = req.body ? req.body.app_id : null; + if (app_id && !hasFiles && !signal && !flashApp) { + //we have an app id, and no files, and stuff + //we must be flashing from the db! + promises.push(Api.flash_app_in_db_dfd(req)); } - - if (!deviceID) { - return when.reject({ error: "No deviceID provided" }); - } - - try { - var keyObj = ursa.createPublicKey(publicKey); - if (!publicKey || (!ursa.isPublicKey(keyObj))) { - return when.reject({ error: "No key provided" }); - } - } - catch (ex) { - logger.error("error while parsing publicKey " + ex); - return when.reject({ error: "Key error " + ex }); - } - - - global.server.addCoreKey(deviceID, publicKey); - global.server.setCoreAttribute(deviceID, "registrar", userid); - global.server.setCoreAttribute(deviceID, "timestamp", new Date()); - result.resolve("Success!"); - - return result.promise; - }, - - //List products the currently authenticated user has access to. - list_products: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("ListProducts", { userID: userid }); - - var orgObj = global.roles.getOrgByUserid(userid); - if(orgObj) { - var productObjs = global.roles.products[orgObj.slug]; - - //remove devices ?? - res.json({ products : productObjs }); - } else { - res.json({ products : [] }); - } - }, - - //Retrieve details for a product. - get_product: function( req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("GetProduct", { userID: userid }); - - var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - var productObj = global.roles.getProductByProductid(productid); - - res.json({ product : productObj }); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); - } - }, - - //Generate a device claim code for a customer, scoped for a specific product. - get_product_claim_code: function (req, res, next) { - var customerid = Api.getCustomerID(req); - if(!customerid) { - return next(); - } - - var productid = req.params.productIdOrSlug; - - logger.log("GenerateProductClaimCode", { customerID: customerid }); - - var productObj = global.roles.getProductByProductid(productid); - var productDevicesIDs = productObj.devices; - PasswordHasher.generateSalt(function (err, code) { - code = code.toString('base64'); - code = code.substring(0, 63); - - when(global.roles.addProductClaimCode(code, customerid, productObj.product_id)).then( - function () { - res.json({ - claim_code: code, - device_ids: productDevicesIDs - }); - }, - function (err) { - res.json({ - ok: false, - errors: [ - err - ] - }); - } - ); - }); - }, - - //Remove a device from a product and re-assign to a generic Particle product. This endpoint will unclaim the device if it is owned by a customer. - release_product_device: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); + + var app_example_id = req.body ? req.body.app_example_id : null; + if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { + //we have an app id, and no files, and stuff + //we must be flashing from the db! + promises.push(Api.flash_example_app_in_db_dfd(req)); } - - var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - when(global.roles.removeProductDevice(coreID, productid)).then( - function () { - global.server.setCoreAttribute(coreID, "claimed", false); - res.json({ ok:true }); - }, function (err) { - res.status(400).json({ - "code": 400, - "ok": false, - "info": "Device not found for this product" - }); + + + if (promises.length >= 1) { + when.all(promises).done( + function (results) { + var aggregate = {}; + for (var i in results) { + for (var key in results[i]) { + aggregate[key] = results[i][key]; + } + } + res.json(aggregate); + }, + function (err) { + res.json({ ok: false, errors: [err] }); } ); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); + } + else { + logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); + res.json({error: "Nothing to do?"}); } }, - - //List Customers for a product. - get_product_customers: function( req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); + + + isDeviceOnline: function (userID, coreID) { + var tmp = when.defer(); + + var socketID = Api.getSocketID(userID); + var socket = new CoreController(socketID); + + var failTimer = setTimeout(function () { + logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); + socket.close(); + tmp.reject("Device is not connected"); + }, settings.isCoreOnlineTimeout); + + + //setup listener for response back from the device service + socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { + clearTimeout(failTimer); + socket.close(); + + logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); + + if (msg && msg.online) { + tmp.resolve(msg); + } + else { + tmp.reject(["Core isn't online", 404]); + } + + }, true); + + logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); + + //send it along to the device service + if (!socket.send(coreID, { cmd: "Ping" })) { + tmp.reject("send failed"); } - - logger.log("GetProductCustomer", { userID: userid }); - - var productid = req.params.productIdOrSlug; - var productObj = global.roles.getProductByProductid(productid); - var productDevices = productObj.devices; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - var customerObjs = []; - var userObjIds = []; - var devices = []; - for (var k = 0; k < productDevices.length; k++) { - var deviceid = productDevices[k]; - var userObj = global.roles.getUserByDevice(deviceid); - if(userObj && userObj.org) { //if customer - devices.push(deviceid); - var index = utilities.indexOf(userObjIds, userObj._id); - if (index == -1) { - userObjIds.push(userObj._id); - customerObjs.push({ - id: userObj._id, - email: userObj.email, - devices: userObj.devices + + return tmp.promise; + }, + + + claim_device: function (req, res) { + res.json({ ok: true }); + }, + + + loadCore: function (req, res, next) { + req.coreID = req.param('coreid') || req.body.id; + + //load core info! + req.coreInfo = { + "last_app": "", + "last_heard": new Date(), + "connected": false, + "deviceID": req.coreID + }; + + //if that user doesn't own that coreID, maybe they sent us a core name + var userid = Api.getUserID(req); + var gotCore = utilities.deferredAny([ + function () { + var core = global.server.getCoreAttributes(req.coreID); + if (core && core.coreID) { + return when.resolve(core); + } + else { + return when.reject(); + } + }, + function () { + var core = global.server.getCoreByName(req.coreID); + if (core && core.coreID) { + return when.resolve(core); + } + else { + return when.reject(); + } + } + ]); + + when(gotCore).then( + function (core) { + if (core) { + req.coreID = core.coreID || req.coreID; + req.coreInfo = { + last_handshake_at: core.last_handshake_at + }; + } + + next(); + }, + function (err) { + //s`okay. + next(); + }) + }, + + get_var: function (req, res) { + var userid = Api.getUserID(req); + var socketID = Api.getSocketID(userid), + coreID = req.coreID, + varName = req.param('var'), + format = req.param('format'); + + logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); + + + //send it along to the device service + //and listen for a response back from the device service + var socket = new CoreController(socketID); + var coreResult = socket.sendAndListenForDFD(coreID, + { cmd: "GetVar", name: varName }, + { cmd: "VarReturn", name: varName }, + settings.coreRequestTimeout + ); + + //sendAndListenForDFD resolves arr to ==> [sender, msg] + when(coreResult) + .then(function (arr) { + var msg = arr[1]; + if (msg.error) { + //at this point, either we didn't get a describe return, or that variable + //didn't exist, either way, 404 + return res.json(404, { + ok: false, + error: msg.error + }); + } + + //TODO: make me look like the spec. + msg.coreInfo = req.coreInfo; + msg.coreInfo.connected = true; + + if (format && (format == "raw")) { + return res.send("" + msg.result); + } + else { + return res.json(msg); + } + }, + function () { + res.json(408, {error: "Timed out."}); + } + ).ensure(function () { + socket.close(); + }); + }, + + fn_call: function (req, res) { + var user_id = Api.getUserID(req), + coreID = req.coreID, + funcName = req.params.func, + format = req.params.format; + + logger.log("FunCall", { coreID: coreID, user_id: user_id.toString() }); + + var socketID = Api.getSocketID(user_id); + var socket = new CoreController(socketID); + var core = socket.getCore(coreID); + + + var args = req.body; + delete args.access_token; + logger.log("FunCall - calling core ", { coreID: coreID, user_id: user_id.toString() }); + var coreResult = socket.sendAndListenForDFD(coreID, + { cmd: "CallFn", name: funcName, args: args }, + { cmd: "FnReturn", name: funcName }, + settings.coreRequestTimeout + ); + + //sendAndListenForDFD resolves arr to ==> [sender, msg] + when(coreResult) + .then( + function (arr) { + var sender = arr[0], msg = arr[1]; + + try { + //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); + if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { + res.json(404, { + ok: false, + error: "Function not found" }); } + else if (msg.error != null) { + res.json(400, { + ok: false, + error: msg.error + }); + } + else { + if (format && (format == "raw")) { + res.send("" + msg.result); + } + else { + res.json({ + id: core.coreID, + name: core.name || null, + last_app: core.last_flashed_app_name || null, + connected: true, + return_value: msg.result + }); + } + } + } + catch (ex) { + logger.error("FunCall handling resp error " + ex); + res.json(500, { + ok: false, + error: "Error while api was rendering response" + }); } + }, + function () { + res.json(408, {error: "Timed out."}); } - res.json({ customers : customerObjs, devices : devices }); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); - } + ).ensure(function () { + socket.close(); + }); + + //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); + + // send the function call along to the device service + }, + + /** + * Ask the core to start / stop the "RaiseYourHand" signal + * @param req + */ + core_signal_dfd: function (req) { + var tmp = when.defer(); + + var userid = Api.getUserID(req), + socketID = Api.getSocketID(userid), + coreID = req.coreID, + showSignal = parseInt(req.body.signal); + + logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); + + var socket = new CoreController(socketID); + var failTimer = setTimeout(function () { + socket.close(); + tmp.reject({error: "Timed out, didn't hear back"}); + }, settings.coreSignalTimeout); + + //listen for a response back from the device service + socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, + function () { + clearTimeout(failTimer); + socket.close(); + + tmp.resolve({ + id: coreID, + connected: true, + signaling: showSignal === 1 + }); + }, true); + + + //send it along to the core via the device service + socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); + + return tmp.promise; }, - - add_product_device: function (req, res, next) { - var coreID = req.body.id; - if(!coreID) { - res.status(400).json({ ok: false, errors: [ 'id is required.' ] }); + + compile_and__or_flash_dfd: function (req) { + var allDone = when.defer(); + var userid = Api.getUserID(req), + coreID = req.coreID; + + + // + // Did they pass us a source file or a binary file? + // + var hasSourceFiles = false; + var sourceExts = [".cpp", ".c", ".h", ".ino" ]; + if (req.files) { + for (var name in req.files) { + if (!req.files.hasOwnProperty(name)) { + continue; + } + + var ext = utilities.getFilenameExt(req.files[name].path); + if (utilities.contains(sourceExts, ext)) { + hasSourceFiles = true; + break; + } + } } - var userid = Api.getUserID(req); - if(!userid) { - return next(); + + + if (hasSourceFiles) { + //TODO: federate? + allDone.reject("Not yet implemented"); } - - var productid = req.params.productIdOrSlug; - logger.log("AddingProductDevice", { productID: productid }); - - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - when(global.roles.addProductDevice(coreID, productid)).then( - function () { - res.json({ ok:true }); - }, function (err) { - res.status(400).json({ - "code": 400, - "ok": false, - "info": "Device already present for that product" - }); + else { + //they sent a binary, just flash it! + var flashDone = Api.flash_core_dfd(req); + + //pipe rejection / resolution of flash to response + utilities.pipeDeferred(flashDone, allDone); + } + + return allDone.promise; + }, + + + /** + * Flashing firmware to the core, binary file! + * @param req + * @returns {promise|*|Function|Promise|when.promise} + */ + flash_core_dfd: function (req) { + var tmp = when.defer(); + + var userid = Api.getUserID(req), + socketID = Api.getSocketID(userid), + coreID = req.coreID; + + logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); + + var args = req.query; + delete args.coreid; + + if (req.files) { + args.data = fs.readFileSync(req.files.file.path); + } + + var socket = new CoreController(socketID); + var failTimer = setTimeout(function () { + socket.close(); + tmp.reject({error: "Timed out."}); + }, settings.coreFlashTimeout); + + //listen for the first response back from the device service + socket.listenFor(coreID, { cmd: "Event", name: "Update" }, + function (sender, msg) { + clearTimeout(failTimer); + socket.close(); + + var response = { id: coreID, status: msg.message }; + if ("Update started" === msg.message) { + tmp.resolve(response); } - ); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); + else { + logger.error("flash_core_dfd rejected ", response); + tmp.reject(response); + } + + }, true); + + //send it along to the device service + socket.send(coreID, { cmd: "UFlash", args: args }); + + return tmp.promise; + }, + + provision_core: function (req, res) { + //if we're here, the user should be allowed to provision cores. + + var done = Api.provision_core_dfd(req); + when(done).then( + function (result) { + res.json(result); + }, + function (err) { + //different status code here? + res.json(400, err); + }); + }, + + provision_core_dfd: function (req) { + var result = when.defer(), + userid = Api.getUserID(req), + deviceID = req.body.deviceID, + publicKey = req.body.publicKey; + + if (!deviceID) { + return when.reject({ error: "No deviceID provided" }); } + + try { + var keyObj = ursa.createPublicKey(publicKey); + if (!publicKey || (!ursa.isPublicKey(keyObj))) { + return when.reject({ error: "No key provided" }); + } + } + catch (ex) { + logger.error("error while parsing publicKey " + ex); + return when.reject({ error: "Key error " + ex }); + } + + + global.server.addCoreKey(deviceID, publicKey); + global.server.setCoreAttribute(deviceID, "registrar", userid); + global.server.setCoreAttribute(deviceID, "timestamp", new Date()); + result.resolve("Success!"); + + return result.promise; }, - _: null + + _: null }; -exports = module.exports = global.api = Api; +exports = module.exports = Api; From 00df221c2bbf04d66d630451eed8bdcaeb0d719e Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 24 Nov 2016 13:21:31 -0800 Subject: [PATCH 051/504] Reverted some breaking changes. --- js/lib/AccessTokenViews.js | 108 ---- js/lib/CoreController.js | 218 ------- js/lib/CustomerViews.js | 82 --- js/lib/RolesController.js | 728 ---------------------- js/lib/utilities.js | 391 ------------ js/oauth_clients.json | 7 - js/orgs/company.json | 8 - js/package.json | 37 -- js/views/EventViews001.js | 339 ---------- js/views/api_v1.js | 1194 ------------------------------------ lib/OAuth2ServerModel.js | 61 +- main.js | 88 ++- settings.js | 21 +- 13 files changed, 77 insertions(+), 3205 deletions(-) delete mode 100644 js/lib/AccessTokenViews.js delete mode 100644 js/lib/CoreController.js delete mode 100644 js/lib/CustomerViews.js delete mode 100644 js/lib/RolesController.js delete mode 100644 js/lib/utilities.js delete mode 100644 js/oauth_clients.json delete mode 100644 js/orgs/company.json delete mode 100644 js/package.json delete mode 100644 js/views/EventViews001.js delete mode 100644 js/views/api_v1.js diff --git a/js/lib/AccessTokenViews.js b/js/lib/AccessTokenViews.js deleted file mode 100644 index 9382e00c..00000000 --- a/js/lib/AccessTokenViews.js +++ /dev/null @@ -1,108 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var path = require('path'); -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); -var PasswordHasher = require('./PasswordHasher.js'); -var roles = require('./RolesController.js'); -var logger = require('./logger.js'); - -var AccessTokenViews = function (options) { - this.options = options; -}; - -AccessTokenViews.prototype = { - loadViews: function (app) { - app.get('/v1/access_tokens', this.index.bind(this)); - app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); - }, - index: function (req, res) { - //var credentials = AccessTokenViews.basicAuth(req); - //var credentials = this.basicAuth(req); - var credentials= AccessTokenViews.prototype.basicAuth(req); - if (!credentials) { - return res.status(401).json({ - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] - }); - } - - //if successful, should return something like: - // [ { token: d.token, expires: d.expires, client: d.client_id } ] - - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - res.json(userObj.access_tokens); - }, - function () { - res.status(401).json({ ok: false, errors: ['Bad password']}); - }); - }, - - destroy: function (req, res) { - //var credentials = AccessTokenViews.basicAuth(req); - var credentials= AccessTokenViews.prototype.basicAuth(req); - if (!credentials) { - return res.status(401).json({ - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] - }); - } - - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - try { - roles.destroyAccessToken(req.params.token); - res.json({ ok: true }); - } - catch (ex) { - logger.error("error saving user " + ex); - res.status(401).json({ ok: false, errors: ['Error updating token']}); - } - }, - function () { - res.status(401).json({ ok: false, errors: ['Bad password']}); - }); - }, - - basicAuth: function (req) { - var auth = req.get('Authorization'); - if (!auth) return null; - - var matches = auth.match(/Basic\s+(\S+)/); - if (!matches) return null; - - var creds = new Buffer(matches[1], 'base64').toString(); - var separatorIndex = creds.indexOf(':'); - if (-1 === separatorIndex) - return null; - - return { - username: creds.slice(0, separatorIndex), - password: creds.slice(separatorIndex + 1) - }; - } - -}; - -module.exports = AccessTokenViews; diff --git a/js/lib/CoreController.js b/js/lib/CoreController.js deleted file mode 100644 index c7b4d726..00000000 --- a/js/lib/CoreController.js +++ /dev/null @@ -1,218 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var when = require('when'); -var extend = require('xtend'); -var EventEmitter = require('events').EventEmitter; - -var logger = require('./logger.js'); -var settings = require("../settings"); -var utilities = require("./utilities.js"); - - -var CoreController = function (socketID) { - //this.coreID = coreID; - this.socketID = socketID; - EventEmitter.call(this); -}; - -CoreController.prototype = { - getCore: function (coreid) { - if (global.server) { - return global.server.getCore(coreid); - } - else { - logger.error("Spark-protocol server not running"); - } - }, - - - sendAndListenFor: function (recipient, msg, filter, callback, once) { - this.listenFor(recipient, filter, callback, once); - this.send(recipient, msg); - }, - - sendAndListenForDFD: function (recipient, msg, filter, failDelay, connectDelay) { - var result = when.defer(); - - failDelay = failDelay || settings.coreRequestTimeout; - var failTimer = setTimeout(function () { - result.reject("Request Timed Out"); - }, failDelay); - - var callback = function (sender, msg) { - clearTimeout(failTimer); - result.resolve([sender, msg]); - }; - - this.sendAndListenFor(recipient, msg, filter, callback, true); - return result.promise; - }, - - - /** - * send a message to a core - * @param recipient - * @param msg - */ - send: function (recipient, msg) { - var that = this; - var core = this.getCore(recipient); - if (!core || !core.onApiMessage) { - logger.error("Couldn't find that core ", recipient); - return false; - } - - process.nextTick(function () { - try { - //console.log("sending message with socketID" + that.socketID); - core.onApiMessage(that.socketID, msg); - } - catch (ex) { - logger.error("error during send: " + ex); - } - }); - return true; - }, - - /** - * starts listening for a message event with the given filter criteria - * @param filter - * @param callback - * @param once - removes the listener after we've heard back - */ - listenFor: function (recipient, filter, callback, once) { - var core = this.getCore(recipient); - if (!core || !core.on) { - logger.error("Couldn't find that core ", recipient); - return; - } - - var that = this, - handler = function (sender, msg) { - //logger.log('heard from ' + ((sender) ? sender.toString() : '(UNKNOWN)')); - - if (!utilities.leftHasRightFilter(msg, filter)) { - //logger.log('filters did not match'); - return; - } - - if (once) { - core.removeListener(that.socketID, handler); - } - - process.nextTick(function () { - try { - //logger.log('passing message to callback ', msg); - callback(sender, msg); - } - catch (ex) { - logger.error("listenFor error: " + ex, (ex) ? ex.stack : ''); - } - }); - }; - - core.on(that.socketID, handler); - }, - - subscribe: function (isPublic, name, userid,coreid) { -// if (!sock) { -// return false; -// } - - //start permitting these messages through on this socket. - global.publisher.subscribe(name,userid,coreid, this); - - return false; - }, - - unsubscribe: function (isPublic, name, userid) { - if (userid && (userid != "")) { - name = userid + "/" + name; - } - -// if (!sock) { -// return; -// } - - global.publisher.unsubscribe(name, this); - }, - - //isPublic, obj.name, obj.userid, obj.data, obj.ttl, obj.published_at - sendEvent: function (isPublic, name, userid, data, ttl, published_at, coreid) { - - if (!global.publisher) { - logger.error("Spark-protocol server not running"); - return; - } - - try { - global.publisher.publish( - isPublic, - name, - userid, - data, - ttl, - published_at, - coreid - ); - } - catch (ex) { - logger.error("sendEvent Error: " + ex); - } - - return true; - }, - - close: function () { - - } -}; - -///** -// * This should be made more efficient, this is too simplistic -// * @returns {{}} -// */ -//CoreController.listAllCores = function() { -// var files = fs.readdirSync(settings.coreKeysDir); -// var cores = []; -// -// -// -// -// -// var corelist = files.map(function(filename) { return utilities.filenameNoExt(filename); }); -// var cores = {}; -// for(var i=0;i. -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var path = require('path'); -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); -var PasswordHasher = require('./PasswordHasher.js'); -var roles = require('./RolesController.js'); -var settings = require('../settings.js'); -var logger = require('./logger.js'); -var utilities = require("./utilities.js"); - -function RolesController() { - this.init(); -}; - -RolesController.prototype = { - users: null, - usersByToken: null, - usersByDevice: null, - usersByClaimCode: null, - usersByUsername: null, - - access_tokens: null, - refresh_tokens: null, - claim_codes: null, - - devices: null, - clients: null, - - orgsBySlug: null, - orgsByUserId: null, - - customers: null, - customersByEmail: null, - //customersByToken: null, - - orgsByProduct: null, - products : null, - - init: function () { - this._loadAndCacheUsers(); - }, - - addUser: function (userObj) { - this.users.push(userObj); - this.usersByUsername[ userObj.username ] = userObj; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var token = userObj.access_tokens[i]; - this.usersByToken[token.token] = userObj; - this.access_tokens[token.token] = token; - this.refresh_tokens[token.refresh_token] = token; - } - - //claim codes - this.claim_codes[userObj._id] = userObj.claim_codes; - for (var i = 0; i < userObj.claim_codes.length; i++) { - var claimCode = userObj.claim_codes[i]; - this.usersByClaimCode[claimCode] = userObj; - } - - //devices claimed - this.devices[userObj._id] = userObj.devices; - for (var i = 0; i < userObj.devices.length; i++) { - var deviceId = userObj.devices[i]; - this.usersByDevice[ deviceId ] = userObj; - } - }, - addCustomer: function (customerObj) { - - console.log("Loading customer " + customerObj.email); - - this.customers.push(customerObj); - this.customersByEmail[ customerObj.email ] = customerObj; - - for (var k = 0; k < customerObj.access_tokens.length; k++) { - var token = customerObj.access_tokens[k]; - this.usersByToken[token.token] = customerObj; - this.access_tokens[token.token] = token; - this.refresh_tokens[token.refresh_token] = token; - } - - //claim codes - this.claim_codes[customerObj._id] = customerObj.claim_codes; - for (var i = 0; i < customerObj.claim_codes.length; i++) { - var claimCode = customerObj.claim_codes[i]; - this.usersByClaimCode[claimCode.code] = customerObj; - } - - //devices claimed - this.devices[customerObj._id] = customerObj.devices; - for (var i = 0; i < customerObj.devices.length; i++) { - var deviceId = customerObj.devices[i]; - this.usersByDevice[ deviceId ] = customerObj; - } - }, - addClient: function (clientObj) { - this.clients.push(clientObj); - }, - addOrg: function (orgObj) { - this.orgsBySlug[orgObj.slug] = orgObj; - this.orgsByUserId[orgObj.user_id] = orgObj; - - for (var i = 0; i < orgObj.customers.length; i++) { - this.addCustomer(orgObj.customers[i]); //add customer - } - - this.products[orgObj.slug] = []; //list product - for (var j = 0; j < orgObj.products.length; j++) { - this.products[orgObj.slug].push(orgObj.products[j]); - this.orgsByProduct[ orgObj.products[j].slug ] = orgObj; - this.orgsByProduct[ orgObj.products[j].product_id ] = orgObj; - } - }, - destroyAccessToken: function (access_token) { - var userObj = this.usersByToken[access_token]; - if (!userObj) { - return true; - } - - delete this.usersByToken[access_token]; - delete this.access_tokens[access_token]; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var tokenObj = userObj.access_tokens[i]; - if (tokenObj.token == access_token) { - userObj.access_tokens.splice(i, 1); - } - } - - this.saveUser(userObj); - }, - revokeToken: function (token) { - var userObj = this.usersByToken[token.accessToken]; - if (!userObj) { - return false; - } - var tokenObj = this.access_tokens[token.accessToken]; - if(!tokenObj) { - return false; - } - - delete this.usersByToken[token.accessToken]; - delete this.access_tokens[token.accessToken]; - delete this.refresh_tokens[token.refreshToken]; - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var tokenObj = userObj.access_tokens[i]; - if (tokenObj.token == token.accessToken) { - userObj.access_tokens.splice(i, 1); - } - } - if(tokenObj.scope && tokenObj.scope.indexOf("customer=") > -1) { - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - return token; - }, - addAccessToken: function (token, client, user) { - var tmp = when.defer(); - try { - var tokenObj = { - //user_id: user._id, - token: token.accessToken, - expires_at: token.accessTokenExpiresAt, - client: client.client_id, - refresh_token: token.refreshToken, - scope: token.scope - }; - - if(token.scope && token.scope.indexOf("customer=") > -1) { - //is a customer token - var email = token.scope.split("=")[1]; - var customerObj = this.customersByEmail[email]; - - this.usersByToken[token.accessToken] = customerObj; - this.access_tokens[token.accessToken] = tokenObj; - this.refresh_tokens[token.refreshToken] = tokenObj; - customerObj.access_tokens.push(tokenObj); - this.saveCustomer(customerObj); - - tmp.resolve({ - accessToken: token.accessToken, - client: client, - refreshToken: token.refreshToken, - user: customerObj._id, - scope: token.scope, - accessTokenExpiresAt: token.accessTokenExpiresAt - }); - - } else { - var userObj = this.getUserByUserid(user._id); - if(!userObj) { - //refresh_token - userObj = this.getUserByUserid(user); - } - this.usersByToken[token.accessToken] = userObj; - - this.access_tokens[token.accessToken] = tokenObj; - this.refresh_tokens[token.refreshToken] = tokenObj; - userObj.access_tokens.push(tokenObj); - this.saveUser(userObj); - - tmp.resolve({ - accessToken: token.accessToken, - client: client, - user: userObj._id, - refreshToken: token.refreshToken, - scope: token.scope, - accessTokenExpiresAt: token.accessTokenExpiresAt - }); - } - } - catch (ex) { - logger.error("Error adding access token ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addClaimCode: function (claimCode, userId) { - var tmp = when.defer(); - try { - var userObj = this.getUserByUserid(userId); - - userObj.claim_codes.push(claimCode); - this.saveUser(userObj); - - this.usersByClaimCode[ claimCode ] = userObj; - - tmp.resolve(); - } - catch (ex) { - logger.error("Error adding claim code ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addProductClaimCode: function (claimCode, customerId, productId) { - var tmp = when.defer(); - try { - var claimCodeObj = { - code : claimCode, - product_id : productId - } - - var customerObj = this.getCustomerByCustomerid(customerId); - customerObj.claim_codes.push(claimCodeObj); - this.saveCustomer(customerObj); - - this.usersByClaimCode[ claimCode ] = customerObj; - - tmp.resolve(); - } - catch (ex) { - logger.error("Error adding claim code ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addDevice: function (deviceId, userObj) { - var tmp = when.defer(); - try { - if(!this.usersByDevice[deviceId]) { - userObj.devices.push(deviceId); - if(userObj.org) { //customer - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - this.usersByDevice[ deviceId ] = userObj; - - tmp.resolve(); - } else if(this.usersByDevice[deviceId] == userObj) { - tmp.resolve(); - } else { - tmp.reject("already claimed"); - } - } - catch (ex) { - logger.error("Error adding device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - removeDevice: function (deviceId, userId) { - var tmp = when.defer(); - try { - var userObj = this.getUserByUserid(userId); - - if(this.usersByDevice[deviceId]) { - var index = utilities.indexOf(userObj.devices, deviceId); - if (index > -1) { - userObj.devices.splice(index, 1); - } - - delete this.usersByDevice[deviceId]; - - if(userObj.org) { //customer - this.saveCustomer(userObj); - } else { - this.saveUser(userObj); - } - tmp.resolve(); - } else { - tmp.reject('Device not found'); - } - } - catch (ex) { - logger.error("Error releasing device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - addProductDevice: function (deviceId, productid) { - var tmp = when.defer(); - try { - var productObj = this.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, deviceId); - if (index == -1) { //if not present - productObj.devices.push(deviceId); - this.saveProduct(productObj); - - tmp.resolve(); - } else { - tmp.reject('Device already present for that product'); - } - } - catch (ex) { - logger.error("Error adding device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - removeProductDevice: function (deviceId, productid) { - var tmp = when.defer(); - try { - var productObj = this.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, deviceId); - if (index > -1) { - /*productObj.devices.splice(index, 1); - this.saveProduct(productObj);*/ - - delete this.usersByDevice[deviceId]; - - var orgObj = this.getOrgByProductid(productObj.product_id); - for (var i = 0; i < orgObj.customers.length; i++) { - var index = utilities.indexOf(orgObj.customers[i].devices, deviceId); - if (index > -1) { - orgObj.customers[i].devices.splice(index, 1); - this.saveCustomer(orgObj.customers[i]); - } - } - - tmp.resolve(); - } else { - tmp.reject('Device not found for product'); - } - } - catch (ex) { - logger.error("Error releasing device ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - saveUser: function (userObj) { - var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; - var userJson = JSON.stringify(userObj, null, 2); - fs.writeFileSync(userFile, userJson); - }, - saveCustomer: function (customerObj) { - var orgObj = this.orgsBySlug[customerObj.org]; - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - var index = 0; - for (var i = 0; i < orgObj.customers.length; i++) { - var customer = orgObj.customers[i]; - if (customer._id == customerObj._id) { - orgObj.customers[i] = customerObj; - } - } - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); - }, - saveProduct: function (productObj) { - var orgObj = this.orgsByProduct[productObj.slug]; - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - var index = 0; - for (var i = 0; i < orgObj.products.length; i++) { - var product = orgObj.products[i]; - if (product.id == productObj.id) { - orgObj.products[i] = productObj; - } - } - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); - }, - - _loadAndCacheUsers: function () { - this.users = []; - this.usersByToken = {}; - this.usersByDevice = {}; - this.usersByUsername = {}; - this.usersByClaimCode = {}; - - this.access_tokens = {}; - this.refresh_tokens = {}; - this.claim_codes = {}; - - this.devices = []; - this.clients = []; - this.orgsBySlug = {}; - this.orgsByUserId = {}; - - this.customers = []; - this.customersByEmail = {}; - //this.customersByToken = {}; - - this.orgsByProduct = {}; - this.products = {}; - - // list files, load all user objects, index by access_tokens and usernames - // and devices - if (!fs.existsSync(settings.userDataDir)) { - fs.mkdirSync(settings.userDataDir); - } - - var files = fs.readdirSync(settings.userDataDir); - if (!files || (files.length == 0)) { - logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); - } - - for (var i = 0; i < files.length; i++) { - try { - - var filename = path.join(settings.userDataDir, files[i]); - var userObj = JSON.parse(fs.readFileSync(filename)); - - console.log("Loading user " + userObj.username); - this.addUser(userObj); - } - catch (ex) { - logger.error("RolesController - error loading user at " + filename); - } - } - - var filenameClient = settings.oauthClientsFile; - var clients = JSON.parse(fs.readFileSync(filenameClient)); - for (var j = 0; j < clients.length; j++) { - try { - console.log("Loading client " + clients[j].client_id); - this.addClient(clients[j]); - } - catch (ex) { - logger.error("RolesController - error loading client at " + filenameOrg); - } - } - - var filesOrg = fs.readdirSync(settings.orgDataDir); - - for (var k = 0; k < filesOrg.length; k++) { - try { - - var filenameOrg = path.join(settings.orgDataDir, filesOrg[k]); - var orgObj = JSON.parse(fs.readFileSync(filenameOrg)); - - console.log("Loading org " + orgObj.slug); - this.addOrg(orgObj); - } - catch (ex) { - logger.error("RolesController - error loading org at " + filenameOrg); - } - } - }, - - getClient: function ( clientId, clientSecret) { - var clientObj = this.getClientByClientid(clientId); - if (clientObj.client_secret == clientSecret || clientId == 'particle') { - return clientObj; - } - return false; - }, - - getUserByClient: function ( clientId ) { - return this.getUserByUserid(this.orgsBySlug[clientId].user_id); - }, - - getUserByToken: function (access_token) { - return this.usersByToken[access_token]; - }, - getUserByDevice: function (deviceId) { - return this.usersByDevice[deviceId]; - }, - getUserByClaimCode: function (claimCode) { - return this.usersByClaimCode[claimCode]; - }, - getOrgByProductid: function (productid) { //ok - return this.orgsByProduct[productid]; - }, - getOrgBySlug: function (orgSlug) { //ok - return this.orgsBySlug[orgSlug]; - }, - getOrgByUserid: function (user_id) { //ok - return this.orgsByUserId[user_id]; - }, - getCustomerByEmail: function (email) { - return this.customersByEmail[email]; - }, - - getUserByName: function (username) { - return this.usersByUsername[username]; - }, - getTokenInfoByAccessToken: function (token) { - var tokenObj = this.access_tokens[token]; - if(!tokenObj) { - return false; - } - return { - accessToken: tokenObj.token, - client: tokenObj.client, - user: this.getUserByToken(tokenObj.token)._id, - refreshToken: tokenObj.refresh_token, - accessTokenExpiresAt: new Date(tokenObj.expires_at), - scope: tokenObj.scope - }; - }, - getTokenInfoByRefreshToken: function (token) { - var tokenObj = this.refresh_tokens[token]; - if(!tokenObj) { - return false; - } - return { - accessToken: tokenObj.token, - client: tokenObj.client, - user: this.getUserByToken(tokenObj.token)._id, - refreshToken: tokenObj.refresh_token, - refreshTokenExpiresAt: new Date(tokenObj.expires_at), - //refreshTokenExpiresAt not managed return accessTokenExpires - scope: tokenObj.scope - }; - }, - getUserByUserid: function (userid) { - for (var i = 0; i < this.users.length; i++) { - var user = this.users[i]; - if (user._id == userid) { - return user; - } - } - return null; - }, - - getProductByProductid: function (productid) { - var orgObj = this.orgsByProduct[productid]; - for (var i = 0; i < this.products[orgObj.slug].length; i++) { - var product = this.products[orgObj.slug][i]; - if (product.product_id == productid || product.slug == productid) { - return product; - } - } - return null; - }, - - getProductByUserid: function (userid) { - var orgObj = this.orgsByProduct[productid]; - for (var i = 0; i < this.products[orgObj.slug].length; i++) { - var product = this.products[orgObj.slug][i]; - if (product.product_id == productid || product.slug == productid) { - return product; - } - } - return null; - }, - - getCustomerByCustomerid: function (customerid) { - for (var i = 0; i < this.customers.length; i++) { - var customer = this.customers[i]; - if (customer._id == customerid) { - return customer; - } - } - return null; - }, - - getClientByClientid: function (clientId) { - for (var i = 0; i < this.clients.length; i++) { - var client = this.clients[i]; - if (client.client_id == clientId) { - return client; - } - } - return null; - }, - - validateHashPromise: function (user, password) { - var tmp = when.defer(); - - PasswordHasher.hash(password, user.salt, function (err, hash) { - if (err) { - logger.error("hash error " + err); - tmp.reject("Bad password"); - } - else if (hash === user.password_hash) { - tmp.resolve(user); - } - else { - tmp.reject("Bad password"); - } - }); - - return tmp.promise; - }, - - validateLogin: function (username, password) { - var userObj = this.getUserByName(username); - if (!userObj) { - return when.reject("Bad password"); - } - - return this.validateHashPromise(userObj, password); - }, - - validateClient: function (clientId, clientSecret) { - var tmp = when.defer(); - - var clientObj = this.getClient(clientId, clientSecret); - if (!clientObj) { - return tmp.reject("Bad client"); - } - - tmp.resolve(clientObj); - - return tmp.promise; - }, - - createUser: function (username, password) { - var tmp = when.defer(); - var that = this; - - PasswordHasher.generateSalt(function (err, userid) { - userid = userid.toString('base64'); - userid = userid.substring(0, 32); - - PasswordHasher.generateSalt(function (err, salt) { - salt = salt.toString('base64'); - PasswordHasher.hash(password, salt, function (err, hash) { - var user = { - _id: userid, - username: username, - password_hash: hash, - salt: salt, - access_tokens: [], - claim_codes: [], - devices: [] - }; - - var userFile = path.join(settings.userDataDir, username + ".json"); - fs.writeFileSync(userFile, JSON.stringify(user)); - - that.addUser(user); - - tmp.resolve(); - }); - }); - }); - - return tmp.promise; - }, - - createCustomer: function (clientObj, /*product,*/ orgSlug, email) { - var tmp = when.defer(); - var that = this; - - var orgObj = that.getOrgBySlug(orgSlug); - if(orgObj && orgObj.slug == clientObj.client_id) { - var customer = that.getCustomerByEmail(email); - if(!customer) { - - PasswordHasher.generateSalt(function (err, customerid) { - customerid = customerid.toString('base64'); - customerid = customerid.substring(0, 32); - - var customer = { - _id: customerid, - email: email, - org: orgObj.slug, - access_tokens: [], - claim_codes: [], - devices: [] - }; - - var orgFile = path.join(settings.orgDataDir, orgObj.slug) + ".json"; - orgObj.customers.push(customer); - - var orgJson = JSON.stringify(orgObj, null, 2); - fs.writeFileSync(orgFile, orgJson); - - that.addCustomer(customer); - - tmp.resolve(); - }); - } else { - tmp.reject('Customer '+email+' already exists'); - } - } else { - tmp.reject('Bad product'); - } - - return tmp.promise; - } -}; -module.exports = global.roles = new RolesController(); \ No newline at end of file diff --git a/js/lib/utilities.js b/js/lib/utilities.js deleted file mode 100644 index 683539cf..00000000 --- a/js/lib/utilities.js +++ /dev/null @@ -1,391 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -*/ - -var os = require('os'); -var when = require('when'); -var logger = require('./logger.js'); -var extend = require('xtend'); -var path = require('path'); -var fs = require('fs'); - -var that; -module.exports = that = { - - /** - * ensures the function in the provided scope - * @param fn - * @param scope - * @returns {Function} - */ - proxy: function (fn, scope) { - return function () { - try { - return fn.apply(scope, arguments); - } - catch (ex) { - logger.error(ex); - logger.error(ex.stack); - logger.log('error bubbled up ' + ex); - } - } - }, - - /** - * Surely there is a better way to do this. - * NOTE! This function does NOT short-circuit when an in-equality is detected. This is - * to avoid timing attacks. - * @param left - * @param right - */ - bufferCompare: function (left, right) { - if ((left == null) && (right == null)) { - return true; - } - else if ((left == null) || (right == null)) { - return false; - } - - if (!Buffer.isBuffer(left)) { - left = new Buffer(left); - } - if (!Buffer.isBuffer(right)) { - right = new Buffer(right); - } - - logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); - - var same = (left.length == right.length), - i = 0, - max = left.length; - - while (i < max) { - same &= (left[i] == right[i]); - i++; - } - - return same; - }, - - /** - * Iterates over the properties of the right object, checking to make - * sure the properties on the left object match. - * @param left - * @param right - */ - leftHasRightFilter: function (left, right) { - if (!left && !right) { - return true; - } - var matches = true; - - for (var prop in right) { - if (!right.hasOwnProperty(prop)) { - continue; - } - matches &= (left[prop] == right[prop]); - } - return matches; - }, - - promiseDoFile: function (filename, callback) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - if (callback(data)) { - deferred.resolve(); - } - } - catch(ex) { - deferred.reject(ex); - } - - }); - } - }); - return deferred; - }, - - promiseGetJsonFile: function (filename) { - var deferred = when.defer(); - - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - var obj = JSON.parse(data); - deferred.resolve(obj); - } - catch(ex) { - logger.error("Error parsing " + filename + " " + ex); - deferred.reject(ex); - } - }); - } - }); - return deferred; - }, - - promiseStreamFile: function (filename) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - var readStream = fs.createReadStream(filename); - - //TODO: catch can't read file stuff. - - deferred.resolve(readStream); - } - }); - return deferred; - }, - - bufferToHexString: function(buf) { - if (!buf || (buf.length <= 0)) { return null; } - - var r = []; - for(var i=0;i= 0) { - return filename.substr(idx); - } - else { - return filename; - } - }, - filenameNoExt: function (filename) { - if (!filename || (filename.length === 0)) { - return filename; - } - - var idx = filename.lastIndexOf('.'); - if (idx >= 0) { - return filename.substr(0, idx); - } - else { - return filename; - } - }, - - indexOf: function (arr, val) { - if (!arr || (arr.length == 0)) { - return -1; - } - for (var i = 0; i < arr.length; i++) { - if (arr[i] == val) { - return i; - } - } - return -1; - }, - contains: function (arr, val) { - return (that.indexOf(arr, val) !== -1); - }, - pipeDeferred: function(left, right) { - when(left).then(function() { - right.resolve.apply(right, arguments); - }, function() { - right.reject.apply(right, arguments); - }) - }, - - /** - * Non-competitive version of when.any - * @param arr - */ - deferredAny: function (arr) { - var tmp = when.defer(); - var index = -1; - var reasons = []; - - //step through a list of FUNCTIONS that return deferreds, - //process in order and resolve with the first one that resolves. - - var doNext = function () { - index++; - if (index > arr.length) { - tmp.reject(reasons); - return; - } - else if (!arr[index]) { - process.nextTick(doNext); - return; - } - - var promise = null; - try { - promise = arr[index](); - } - catch (ex) { - logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); - } - - if (promise) { - when(promise).then( - function () { - //chain this forward and resolve! we're done! - tmp.resolve.apply(tmp, arguments); - }, - function (err) { - reasons.push(err); - - //lets try the next one! - process.nextTick(doNext); - }); - } - else { - process.nextTick(doNext); - } - }; - - process.nextTick(doNext); - return tmp.promise; - }, - - check_requires_update: function(device, target) { - var version = (device && device["cc3000_patch_version"]); - return (version && (version < target)); - }, - - getIPAddresses: function () { - //adapter = adapter || "eth0"; - var results = []; - var nics = os.networkInterfaces(); - - for (var name in nics) { - var nic = nics[name]; - - for (var i = 0; i < nic.length; i++) { - var addy = nic[i]; - - if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { - continue; - } - - results.push(addy.address); - } - } - - return results; - }, - - slugify: function (text) { - return text.toString().toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/[^\w\-]+/g, '') // Remove all non-word chars - .replace(/\-\-+/g, '-') // Replace multiple - with single - - .replace(/^-+/, '') // Trim - from start of text - .replace(/-+$/, ''); // Trim - from end of text - }, - - foo: null -}; \ No newline at end of file diff --git a/js/oauth_clients.json b/js/oauth_clients.json deleted file mode 100644 index 82af918d..00000000 --- a/js/oauth_clients.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "client_id" : "particle", - "client_secret" : "particle", - "grants" : [ "password", "refresh_token" ] - } -] \ No newline at end of file diff --git a/js/orgs/company.json b/js/orgs/company.json deleted file mode 100644 index 47c3c38c..00000000 --- a/js/orgs/company.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "_id": "wepqelo12ke12em13e", - "user_id": "5IMm6JpaQFkT2gh3UZBF0onKSphWYRXH", - "name": "Company", - "slug": "company", - "customers": [], - "products": [] -} \ No newline at end of file diff --git a/js/package.json b/js/package.json deleted file mode 100644 index a1a660ba..00000000 --- a/js/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "spark-server", - "version": "0.1.1", - "license": "AGPL-3.0", - "repository": { - "type": "git", - "url": "https://github.com/spark/spark-server" - }, - "homepage": "https://github.com/spark/spark-server", - "bugs": "https://github.com/spark/spark-server/issues", - "author": { - "name": "David Middlecamp", - "email": "david@spark.io", - "url": "https://www.spark.io/" - }, - "dependencies": { - "body-parser": "^1.15.2", - "connect-multiparty": "^2.0.0", - "express": "^4.14.0", - "express-oauth-server": "git://github.com/durielz/express-oauth-server.git", - "hogan-express": "^0.5.2", - "moment": "*", - "morgan": "^1.7.0", - "request": "*", - "spark-protocol": "git://github.com/durielz/spark-protocol.git", - "ursa": "*", - "when": "*", - "xtend": "*" - }, - "scripts": { - "start": "node main.js" - }, - "main": "main.js", - "contributors": [ - "Kenneth Lim " - ] -} diff --git a/js/views/EventViews001.js b/js/views/EventViews001.js deleted file mode 100644 index b2db7679..00000000 --- a/js/views/EventViews001.js +++ /dev/null @@ -1,339 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var settings = require('../settings.js'); -var CoreController = require('../lib/CoreController.js'); - -var Api = require('./api_v1.js'); -var utilities = require("../lib/utilities.js"); -var logger = require('../lib/logger.js'); - -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); - -var moment = require('moment'); - -var EventsApi = { - loadViews: function (app) { - - // GET /v1/events[/:event_name] - // GET /v1/devices/events[/:event_name] - // GET /v1/devices/:device_id/events[/:event_name] - - app.get('/v1/events', EventsApi.get_events); - app.get('/v1/events/:event_name', EventsApi.get_events); - - app.get('/v1/devices/events', EventsApi.get_my_events); - app.post('/v1/devices/events', EventsApi.send_an_event); - app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); - - app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); - app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); - - app.get('/v1/products/:productIdOrSlug/events', app.oauth.authenticate(), EventsApi.get_product_events); - }, - - - //----------------------------------------------------------------- - - pipeEvents: function (socket, req, res, next, filterCoreId, filterProductId) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - /* - Start SSE - */ - - req.socket.setNoDelay(); - - res.writeHead(200, { - "Connection": "keep-alive", - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - "X-Accel-Buffering": "no" - }); - res.write(":ok\n\n"); - - - var _idleTimer = null; - var _lastMessage = null; - var keepAlive = function() { - if (((new Date()) - _lastMessage) >= 9000) { - _lastMessage = new Date(); - res.write("\n"); - checkSocket(); - } - }; - - //if nothing gets sent for 9 seconds, send a newline. - var aliveInterval = setInterval(keepAlive, 3000); - - var checkSocket = function () { - try { - if (!socket) { - cleanup(); - return false; - } - - if (res.socket.destroyed) { - logger.log("Socket destroyed, cleaning up Event listener"); - cleanup(); - return false; - } - } - catch (ex) { - logger.error("pipeEvents - error checking socket ", ex); - } - return true; - }; - - var cleanup = function () { - try { - if (socket) { - socket.close(); - socket = null; - } - } - catch (ex) { - logger.error("pipeEvents - event socket close err: ", ex); - } - - try { - if (res.socket) { - res.socket.end(); - } - res.end(); - } - catch (ex) { - logger.error("pipeEvents - response close err: ", ex); - } - - try { - if (aliveInterval) { - clearInterval(aliveInterval); - aliveInterval = null; - } - } - catch (ex) { - logger.error("pipeEvents - clear interval err: ", ex); - } - - }; - - - // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events - var writeEventGen = function (isPublic) { - return function (name, data, ttl, published_at, coreid) { - if (filterCoreId && (filterCoreId != coreid)) { - return; - } - - if (!checkSocket()) { - return; - } - - if(filterProductId == null && !Api.hasDevice(coreid, userid)) { - return; - } - - if(filterProductId != null && !Api.hasProduct(filterProductId, userid)) { - return; - } - - try { - _lastMessage = new Date(); - - //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. - name = (name) ? name.toString().replace(userid + "/", "") : null; - - var obj = { - data: data ? data.toString() : null, - ttl: ttl ? ttl.toString() : null, - published_at: (published_at) ? published_at.toString() : null, - coreid: (coreid) ? coreid.toString() : null - }; - res.write("event: " + name + "\n"); - res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies - } - catch (ex) { - logger.error("pipeEvents - write error: " + ex); - } - - //OTHER HEADERS: - //retry: ? - //id: ? //if we want to support resuming - //TODO: escape newlines in message? - }; - }; - - socket.on('public', writeEventGen(true)); - socket.on('private', writeEventGen(false)); - - req.on("close", cleanup); - req.on("end", cleanup); - res.on("close", cleanup); - res.on("finish", cleanup); - //res.setTimeout(30 * 1000, cleanup); - }, - - - get_events: function (req, res, next) { - var name = req.params.event_name; - name = name || ""; - var socket = new CoreController(); - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - //----------------------------------- - //get firehose and my private events. - //socket.subscribe(true, name); - //socket.subscribe(true, name); - socket.subscribe(false, name, userid); - - - //send it all through - EventsApi.pipeEvents(socket, req, res, next); - }, - get_my_events: function (req, res, next) { - var name = req.params.event_name; - name = name || ""; - var socket = new CoreController(); - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - //----------------------------------- - //get my events: - //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); - - //don't filter by core id - EventsApi.pipeEvents(socket, req, res, next); - }, - get_core_events: function (req, res, next) { - var name = req.params.event_name; - var socket = new CoreController(); - name = name || ""; - var coreid = req.coreID || req.params.coreid; - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - //check if core is owned - if(!Api.hasDevice(coreid, userid)) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - - //----------------------------------- - //get core events - //socket.subscribe(true, name); - socket.subscribe(true, name, userid,coreid); - socket.subscribe(false, name, userid,coreid); - - //----------------------------------- - //filter to core id - EventsApi.pipeEvents(socket, req, res, next, coreid); - }, - - - send_an_event: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req), - socketID = Api.getSocketID(userid), - eventName = req.body.name, - data = req.body.data, - ttl = req.body.ttl || 60, - private_str = req.body.private; - - if(!userid) { - return next(); - } - - var is_public = (!private_str || (private_str == "") || (private_str == "false")); - - var socket = new CoreController(socketID); - var success = socket.sendEvent(is_public, - eventName, - userid, - data, - parseInt(ttl), - moment().toISOString(), - userid - ); - - var autoClose = setTimeout(function () { - socket.close(); - res.json({ok: success}); - }, 250); - }, - - get_product_events: function (req, res, next) { - var name = req.params.event_name; - var socket = new CoreController(); - name = name || ""; - var productid = req.params.productIdOrSlug; - productid = productid || null; - - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - //check if product is owned by user - if(!Api.hasProduct(productid, userid)) { - return next(); - } -// if (userid) { -// socket.authorize(userid); -// } - - - //----------------------------------- - //get product events - //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); - - //----------------------------------- - //filter to core id - EventsApi.pipeEvents(socket, req, res, next, null, productid); - }, - - _: null -}; - - -module.exports = EventsApi; \ No newline at end of file diff --git a/js/views/api_v1.js b/js/views/api_v1.js deleted file mode 100644 index d1b093b6..00000000 --- a/js/views/api_v1.js +++ /dev/null @@ -1,1194 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var settings = require('../settings.js'); - -var CoreController = require('../lib/CoreController.js'); -var PasswordHasher = require('../lib/PasswordHasher.js'); - -var sequence = require('when/sequence'); -var parallel = require('when/parallel'); -var pipeline = require('when/pipeline'); - -var logger = require('../lib/logger.js'); -var utilities = require("../lib/utilities.js"); - -var fs = require('fs'); -var when = require('when'); -var util = require('util'); -var path = require('path'); -var ursa = require('ursa'); -var moment = require('moment'); - -var multipart = require('connect-multiparty'); -var multipartMiddleware = multipart(); - -/* - * TODO: modularize duplicate code - * TODO: implement proper session handling / user authentication - * TODO: add cors handler without losing :params support - * - */ - -var Api = { - loadViews: function (app) { - - //our middleware - app.param("coreid", Api.loadCore); - - - //core functions / variables - app.post('/v1/devices/:coreid/:func', Api.fn_call); - app.get('/v1/devices/:coreid/:var', Api.get_var); - - app.put('/v1/devices/:coreid', multipartMiddleware, Api.set_core_attributes); - app.get('/v1/devices/:coreid', Api.get_core_attributes);//ok customer - - //doesn't need per-core permissions, only shows owned cores. - app.get('/v1/devices', Api.list_devices);//ok customer - - app.post('/v1/provisioning/:coreid', Api.provision_core); - - app.delete('/v1/devices/:coreid', Api.release_device); - - app.post('/v1/devices', Api.claim_device); - app.post('/v1/device_claims', app.oauth.authenticate(), Api.get_claim_code); - - /*products*/ - app.get('/v1/products', app.oauth.authenticate(), Api.list_products); - app.get('/v1/products/:productIdOrSlug', app.oauth.authenticate(), Api.get_product); - app.post('/v1/products/:productIdOrSlug/device_claims', app.oauth.authenticate(), Api.get_product_claim_code); - app.delete('/v1/products/:productIdOrSlug/devices/:coreid', app.oauth.authenticate(), Api.release_product_device); - app.get('/v1/products/:productIdOrSlug/customers', app.oauth.authenticate(), Api.get_product_customers); - - app.post('/v1/products/:productIdOrSlug/devices', app.oauth.authenticate(), Api.add_product_device); - }, - - getSocketID: function (userID) { - return userID + "_" + global._socket_counter++; - }, - - getUserID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - if(req.app.locals.oauth.token.scope && req.app.locals.oauth.token.scope.indexOf("customer=") > -1) { - logger.log("Customer token"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - getCustomerID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - if(!req.app.locals.oauth.token.scope || req.app.locals.oauth.token.scope.indexOf("customer=") == -1) { - logger.log("User token"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - getUserOrCustomerID: function (req) { - if (!req.app.locals.oauth) { - logger.log("Token obj was empty"); - return false; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.app.locals.oauth.token.user; - }, - - hasDevice: function (coreID, userID) { - var userObj = global.roles.getUserByDevice(coreID); - //check core permission - if(userObj && userObj._id == userID) { - return true; - } else { - //logger.log("device Permission Denied"); - return false; - } - }, - - hasOrg: function (userID) { - var orgObj = global.roles.getOrgByUserid(userID); - //check user permission - if(orgObj) { - return true; - } else { - return false; - } - }, - - hasProduct: function (productIdOrSlug, userID) { - var orgObj = global.roles.getOrgByProductid(productIdOrSlug); - //check user permission - if(orgObj && orgObj.user_id == userID) { - return true; - } else { - return false; - } - }, - - list_devices: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - logger.log("ListDevices", { userID: userid }); - - //give me all the cores - - //var allCoreIDs = global.server.getAllCoreIDs(), - var userDevicesIDs = global.roles.devices[userid], - devices = [], - connected_promises = []; - - for (var index in userDevicesIDs) { - var coreid = userDevicesIDs[index]; - - if (!coreid) { - continue; - } - - var core = global.server.getCoreAttributes(coreid); - - var device = { - id: coreid, - name: core ? core.name : null, - last_app: core ? core.last_flashed_app_name : null, - product_id: core ? core.spark_product_id : null, - firmware_version: core ? core.product_firmware_version : null, - system_version: core ? core.spark_system_version : null, - last_heard: null - }; - - if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - devices.push(device); - connected_promises.push(Api.isDeviceOnline(userid, device.id)); - } - - logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); - - //switched 'done' to 'then' - threw an exception with 'done' here. - when.settle(connected_promises).then(function (descriptors) { - for (var i = 0; i < descriptors.length; i++) { - var desc = descriptors[i]; - devices[i].connected = ('rejected' !== desc.state); - devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; - } - - res.status(200).json(devices); - }); - }, - - get_core_attributes: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - socket = new CoreController(socketID); - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); - - var objReady = parallel([ - function () { - return when.resolve(global.server.getCoreAttributes(coreID)); - }, - function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); - }, - function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Ping" }, { cmd: "Pong" })); - } - ]); - - //whatever we get back... - when(objReady).done(function (results) { - try { - - if (!results || (results.length != 3)) { - logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); - res.status(404).json("Oops, I couldn't find that core"); - return; - } - - //we're expecting descResult to be an array: [ sender, {} ] - var doc = results[0], - descResult = results[1], - coreState = null, - descPingResult = results[2]; - - if (!doc || !doc.coreID) { - logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); - res.status(404).json("Oops, I couldn't find that core"); - return; - } - - if (util.isArray(descResult) && (descResult.length > 1)) { - coreState = descResult[1].state || {}; - } - if (!coreState) { - logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); - } - - var device = { - id: doc.coreID, - name: doc.name || null, - last_app: doc.last_flashed, - product_id: doc.spark_product_id || null, - firmware_version: doc.product_firmware_version || null, - system_version: doc.spark_system_version || null, - //connected: !!coreState, - connected: (descPingResult != "Request Timed Out") ? descPingResult[1].online : false, - last_heard: (descPingResult != "Request Timed Out") ? descPingResult[1].lastPing : null, - variables: (coreState) ? coreState.v : null, - functions: (coreState) ? coreState.f : null, - cc3000_patch_version: doc.cc3000_driver_version - }; - - if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - res.json(device); - } - catch (ex) { - logger.error("get_core_attributes merge error: " + ex); - res.status(500).json({ Error: "get_core_attributes error: " + ex }); - } - }, null); - - //get_core_attribs - end - }, - - set_core_attributes: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid) && !Api.hasOrg(userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - var promises = []; - - logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); - - var coreName = req.body ? req.body.name : null; - if (coreName != null) { - logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); - - global.server.setCoreAttribute(req.coreID, "name", coreName); - promises.push(when.resolve({ ok: true, name: coreName })); - } - - var hasFiles = req.files && req.files.file; - if (hasFiles) { - console.log("file"); - //oh hey, you want to flash firmware? - promises.push(Api.compile_and__or_flash_dfd(req)); - } - - var signal = req.body && req.body.signal; - if (signal) { - //get your hands up in the air! Or down. - promises.push(Api.core_signal_dfd(req)); - } - - var flashApp = req.body ? req.body.app : null; - if (flashApp) { - // It makes no sense to flash a known app and also - // either signal or flash a file sent with the request - if (!hasFiles && !signal) { - - // MUST sanitize app name here, before sending to Device Service - if (utilities.contains(settings.known_apps, flashApp)) { - promises.push(Api.flash_known_app_dfd(req)); - } - else { - promises.push(when.reject("Can't flash unknown app " + flashApp)); - } - } - } - - var app_id = req.body ? req.body.app_id : null; - if (app_id && !hasFiles && !signal && !flashApp) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_app_in_db_dfd(req)); - } - - var app_example_id = req.body ? req.body.app_example_id : null; - if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_example_app_in_db_dfd(req)); - } - - - if (promises.length >= 1) { - when.all(promises).done( - function (results) { - var aggregate = {}; - for (var i in results) { - for (var key in results[i]) { - aggregate[key] = results[i][key]; - } - } - res.json(aggregate); - }, - function (err) { - res.json({ ok: false, errors: [err] }); - } - ); - } - else { - logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); - res.json({error: "Nothing to do?"}); - } - }, - - isDeviceOnline: function (userID, coreID) { - var tmp = when.defer(); - - var socketID = Api.getSocketID(userID); - var socket = new CoreController(socketID); - - var failTimer = setTimeout(function () { - logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); - socket.close(); - tmp.reject("Device is not connected"); - }, settings.isCoreOnlineTimeout); - - - //setup listener for response back from the device service - socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); - - if (msg && msg.online) { - tmp.resolve(msg); - } - else { - tmp.reject(["Core isn't online", 404]); - } - - }, true); - - logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); - - //send it along to the device service - if (!socket.send(coreID, { cmd: "Ping" })) { - tmp.reject("Device is not connected"); - } - - return tmp.promise; - }, - - get_claim_code: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("GenerateClaimCode", { userID: userid }); - - var userDevicesIDs = global.roles.devices[userid]; - PasswordHasher.generateSalt(function (err, code) { - code = code.toString('base64'); - code = code.substring(0, 63); - - when(global.roles.addClaimCode(code, userid)).then( - function () { - res.json({ - claim_code: code, - device_ids: userDevicesIDs - }); - }, - function (err) { - res.json({ - ok: false, - errors: [ - err - ] - }); - } - ); - }); - }, - - claim_device: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - var coreid = req.body.id; - var core = global.server.getCoreAttributes(coreid); - - if(coreid) { - - if(core.claimCode) { - var userObj = global.roles.getUserByClaimCode(core.claimCode); - - if(user) { - when(global.roles.addDevice(coreid, userObj)).then( - function () { - var claimInfo = { - user_id : userObj._id, - id: coreid, - connected: false, - ok: true - } - - when(Api.isDeviceOnline(userid, coreid)) - .then( - function (desc) { - claimInfo.connected = ('rejected' !== desc.state); - - global.server.setCoreAttribute(coreid, "claimed", true); - res.json(claimInfo); - }, - function (err) { - res.status(404).json({ - ok: false, - errors: [ - "Device is not connected" - ] - }); - } - ); - }, - function (err) { - res.status(403).json({ - ok: false, - errors: [ - "That belongs to someone else. To request a transfer add ?request_transfer=true to the URL." - ] - }); - } - ); - } else { - res.status(404).json({ - ok: false, - errors: [ - {} - ] - }); - } - } else { - res.status(404).json({ - ok: false, - errors: [ - {} - ] - }); - } - } else { - res.status(404).json({ - ok: false, - errors: [ - "data.deviceID is empty" - ] - }); - } - }, - - release_device: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - when(global.roles.removeDevice(coreID, userid)).then( - function () { - global.server.setCoreAttribute(coreID, "claimed", false); - res.json({'ok' : true }); - }, function (err) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - } - ); - }, - - //called when the core send its claim code - linkDevice: function (coreid, claimCode, productid) { - var userObj = global.roles.getUserByClaimCode(claimCode); - if(userObj) { - logger.log("Linking Device...", { coreID: coreid }); - - if(userObj.org) { //if customer - //check if coreid is present in product devices - var productObj = global.roles.getProductByProductid(productid); - var index = utilities.indexOf(productObj.devices, coreid); - if (index > -1) { - for (var i = 0; i < global.roles.claim_codes[userObj._id].length; i++) { - var claimCodeObj = global.roles.claim_codes[userObj._id][i]; - //check if the claim code is valid for the product - if (claimCodeObj.code == claimCode && claimCodeObj.product_id != productid) { - logger.error("Claim code not valid for product", { claimCode: claimCode }); - return false; - } - }; - } else { - logger.error("Device not found for product"); - return false; - } - } - - when(global.roles.addDevice(coreid, userObj)).then( - function () { - global.server.setCoreAttribute(coreid, "claimed", true); - logger.log("Device linked", { coreID: coreid }); - }, - function (err) { - logger.error("Error in linking Device: "+err, { coreID: coreid }); - } - ); - } else { - logger.error("Claim code not valid", { claimCode: claimCode }); - } - }, - - safeMode: function (coreID, description) { - - logger.log("Device is in SAFE MODE", {coreID: coreID}); - - global.server.publishSpecialEvents('spark/status/safe-mode', description, coreID); - - //# spark/safe-mode-updater/updating - //{"name":"spark/safe-mode-updater/updating","data":"2","ttl":"60","published_at":"2016-01-01T14:41:0.000Z","coreid":"particle-internal"} - }, - - loadCore: function (req, res, next) { - req.coreID = req.params.coreid || req.body.id; - - //load core info! - req.coreInfo = { - "last_app": "", - "last_heard": new Date(), - "connected": false, - "deviceID": req.coreID - }; - - //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var gotCore = utilities.deferredAny([ - function () { - var core = global.server.getCoreAttributes(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - }, - function () { - var core = global.server.getCoreByName(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - } - ]); - - when(gotCore).then( - function (core) { - if (core) { - req.coreID = core.coreID || req.coreID; - req.coreInfo = { - last_handshake_at: core.last_handshake_at - }; - } - - next(); - }, - function (err) { - //s`okay. - next(); - }) - }, - - get_var: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req); - if(!userid) { - return next(); - } - - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - varName = req.params.var, - format = req.params.format; - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); - - - //send it along to the device service - //and listen for a response back from the device service - var socket = new CoreController(socketID); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "GetVar", name: varName }, - { cmd: "VarReturn", name: varName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then(function (arr) { - var msg = arr[1]; - if (msg.error) { - //at this point, either we didn't get a describe return, or that variable - //didn't exist, either way, 404 - return res.status(404).json({ - ok: false, - error: msg.error - }); - } - - //TODO: make me look like the spec. - msg.coreInfo = req.coreInfo; - msg.coreInfo.connected = true; - - if (format && (format == "raw")) { - return res.sendStatus("" + msg.result); - } - else { - return res.json(msg); - } - }, - function () { - res.status(408).json({error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - }, - - fn_call: function (req, res, next) { - var userid = Api.getUserOrCustomerID(req), - coreID = req.coreID, - funcName = req.params.func, - format = req.params.format; - - if(!userid) { - return next(); - } - - if(!Api.hasDevice(coreID, userid)) { - res.status(403).json({ - "error": "device Permission Denied", - "info": "I didn't recognize that device name or ID" - }); - return; - } - - logger.log("FunCall", { coreID: coreID, userid: userid.toString() }); - - var socketID = Api.getSocketID(userid); - var socket = new CoreController(socketID); - var core = socket.getCore(coreID); - - - var args = req.body; - delete args.access_token; - logger.log("FunCall - calling core ", { coreID: coreID, userid: userid.toString() }); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "CallFn", name: funcName, args: args }, - { cmd: "FnReturn", name: funcName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then( - function (arr) { - var sender = arr[0], msg = arr[1]; - - try { - //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); - if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { - res.status(404).json({ - ok: false, - error: "Function not found" - }); - } - else if (msg.error != null) { - res.status(400).json({ - ok: false, - error: msg.error - }); - } - else { - if (format && (format == "raw")) { - res.sendStatus("" + msg.result); - } - else { - res.json({ - id: core.coreID, - name: core.name || null, - last_app: core.last_flashed_app_name || null, - connected: true, - return_value: msg.result - }); - } - } - } - catch (ex) { - logger.error("FunCall handling resp error " + ex); - res.status(500).json({ - ok: false, - error: "Error while api was rendering response" - }); - } - }, - function () { - res.status(408).json({error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - - //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); - - // send the function call along to the device service - }, - - /** - * Ask the core to start / stop the "RaiseYourHand" signal - * @param req - */ - core_signal_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID, - showSignal = parseInt(req.body.signal); - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out, didn't hear back"}); - }, settings.coreSignalTimeout); - - //listen for a response back from the device service - socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, - function () { - clearTimeout(failTimer); - socket.close(); - - tmp.resolve({ - id: coreID, - connected: true, - signaling: showSignal === 1 - }); - }, true); - - - //send it along to the core via the device service - socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); - - return tmp.promise; - }, - - compile_and__or_flash_dfd: function (req) { - var allDone = when.defer(); - var userid = Api.getUserID(req), - coreID = req.coreID; - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - // - // Did they pass us a source file or a binary file? - // - var hasSourceFiles = false; - var sourceExts = [".cpp", ".c", ".h", ".ino" ]; - if (req.files) { - for (var name in req.files) { - if (!req.files.hasOwnProperty(name)) { - continue; - } - - var ext = utilities.getFilenameExt(req.files[name].path); - if (utilities.contains(sourceExts, ext)) { - hasSourceFiles = true; - break; - } - } - } - - - if (hasSourceFiles) { - //TODO: federate? - allDone.reject("Not yet implemented"); - } - else { - //they sent a binary, just flash it! - var flashDone = Api.flash_core_dfd(req); - - //pipe rejection / resolution of flash to response - utilities.pipeDeferred(flashDone, allDone); - } - - return allDone.promise; - }, - - - /** - * Flashing firmware to the core, binary file! - * @param req - * @returns {promise|*|Function|Promise|when.promise} - */ - flash_core_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - coreID = req.coreID; - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); - - var args = req.query; - delete args.coreid; - - if (req.files) { - console.log(req.files); - args.data = fs.readFileSync(req.files.file.path); - //args.data = fs.readFileSync(req.files.file.file); - } - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out."}); - }, settings.coreFlashTimeout); - - //listen for the first response back from the device service - socket.listenFor(coreID, { cmd: "Event", name: "Update" }, - function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - var response = { id: coreID, status: msg.message }; - if ("Update started" === msg.message) { - tmp.resolve(response); - } - else { - logger.error("flash_core_dfd rejected ", response); - tmp.reject(response); - } - - }, true); - - //send it along to the device service - socket.send(coreID, { cmd: "UFlash", args: args }); - - return tmp.promise; - }, - - provision_core: function (req, res, next) { - //if we're here, the user should be allowed to provision cores. - - var done = Api.provision_core_dfd(req); - when(done).then( - function (result) { - res.json(result); - }, - function (err) { - //different status code here? - res.status(400).json(err); - }); - }, - - provision_core_dfd: function (req) { - var result = when.defer(), - userid = Api.getUserID(req), - deviceID = req.body.deviceID, - publicKey = req.body.publicKey; - - if(!userid) { - return when.reject({ error: "No userID provided" }); - } - - if (!deviceID) { - return when.reject({ error: "No deviceID provided" }); - } - - try { - var keyObj = ursa.createPublicKey(publicKey); - if (!publicKey || (!ursa.isPublicKey(keyObj))) { - return when.reject({ error: "No key provided" }); - } - } - catch (ex) { - logger.error("error while parsing publicKey " + ex); - return when.reject({ error: "Key error " + ex }); - } - - - global.server.addCoreKey(deviceID, publicKey); - global.server.setCoreAttribute(deviceID, "registrar", userid); - global.server.setCoreAttribute(deviceID, "timestamp", new Date()); - result.resolve("Success!"); - - return result.promise; - }, - - //List products the currently authenticated user has access to. - list_products: function (req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("ListProducts", { userID: userid }); - - var orgObj = global.roles.getOrgByUserid(userid); - if(orgObj) { - var productObjs = global.roles.products[orgObj.slug]; - - //remove devices ?? - res.json({ products : productObjs }); - } else { - res.json({ products : [] }); - } - }, - - //Retrieve details for a product. - get_product: function( req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("GetProduct", { userID: userid }); - - var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - var productObj = global.roles.getProductByProductid(productid); - - res.json({ product : productObj }); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); - } - }, - - //Generate a device claim code for a customer, scoped for a specific product. - get_product_claim_code: function (req, res, next) { - var customerid = Api.getCustomerID(req); - if(!customerid) { - return next(); - } - - var productid = req.params.productIdOrSlug; - - logger.log("GenerateProductClaimCode", { customerID: customerid }); - - var productObj = global.roles.getProductByProductid(productid); - var productDevicesIDs = productObj.devices; - PasswordHasher.generateSalt(function (err, code) { - code = code.toString('base64'); - code = code.substring(0, 63); - - when(global.roles.addProductClaimCode(code, customerid, productObj.product_id)).then( - function () { - res.json({ - claim_code: code, - device_ids: productDevicesIDs - }); - }, - function (err) { - res.json({ - ok: false, - errors: [ - err - ] - }); - } - ); - }); - }, - - //Remove a device from a product and re-assign to a generic Particle product. This endpoint will unclaim the device if it is owned by a customer. - release_product_device: function (req, res, next) { - var coreID = req.coreID; - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - var productid = req.params.productIdOrSlug; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - when(global.roles.removeProductDevice(coreID, productid)).then( - function () { - global.server.setCoreAttribute(coreID, "claimed", false); - res.json({ ok:true }); - }, function (err) { - res.status(400).json({ - "code": 400, - "ok": false, - "info": "Device not found for this product" - }); - } - ); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); - } - }, - - //List Customers for a product. - get_product_customers: function( req, res, next) { - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - logger.log("GetProductCustomer", { userID: userid }); - - var productid = req.params.productIdOrSlug; - var productObj = global.roles.getProductByProductid(productid); - var productDevices = productObj.devices; - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - var customerObjs = []; - var userObjIds = []; - var devices = []; - for (var k = 0; k < productDevices.length; k++) { - var deviceid = productDevices[k]; - var userObj = global.roles.getUserByDevice(deviceid); - if(userObj && userObj.org) { //if customer - devices.push(deviceid); - var index = utilities.indexOf(userObjIds, userObj._id); - if (index == -1) { - userObjIds.push(userObj._id); - customerObjs.push({ - id: userObj._id, - email: userObj.email, - devices: userObj.devices - }); - } - } - } - res.json({ customers : customerObjs, devices : devices }); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found' ] }); - } - }, - - add_product_device: function (req, res, next) { - var coreID = req.body.id; - if(!coreID) { - res.status(400).json({ ok: false, errors: [ 'id is required.' ] }); - } - var userid = Api.getUserID(req); - if(!userid) { - return next(); - } - - var productid = req.params.productIdOrSlug; - logger.log("AddingProductDevice", { productID: productid }); - - var orgObj = global.roles.getOrgByProductid(productid); - if(orgObj && orgObj.user_id == userid) { - when(global.roles.addProductDevice(coreID, productid)).then( - function () { - res.json({ ok:true }); - }, function (err) { - res.status(400).json({ - "code": 400, - "ok": false, - "info": "Device already present for that product" - }); - } - ); - } else { - res.status(404).json({ ok: false, errors: [ 'Product not found.' ] }); - } - }, - - _: null -}; - -exports = module.exports = global.api = Api; diff --git a/lib/OAuth2ServerModel.js b/lib/OAuth2ServerModel.js index 47cf3e7b..5e973a02 100644 --- a/lib/OAuth2ServerModel.js +++ b/lib/OAuth2ServerModel.js @@ -22,45 +22,42 @@ var when = require('when'); var OAuth2ServerModel = function (options) { - this.options = options; + this.options = options; }; OAuth2ServerModel.prototype = { - getAccessToken: function (bearerToken) { - return when(roles.getTokenInfoByAccessToken(bearerToken)); - }, + getAccessToken: function (bearerToken, callback) { + var token = roles.getTokenInfoByToken(bearerToken); + callback(null, token); + }, - getClient: function(clientId, clientSecret) { - return when(roles.getClient(clientId, clientSecret)); - }, - - getUserFromClient: function(client) { - return when(roles.getUserByClient(client.client_id)); - }, - - getRefreshToken: function (bearerToken) { - return when(roles.getTokenInfoByRefreshToken(bearerToken)); - }, - - revokeToken: function (bearerToken) { - return when(roles.revokeToken(bearerToken)); - }, + getClient: function (clientId, clientSecret, callback) { + return callback(null, { client_id: clientId }); + }, - saveToken: function (token, client, user) { - return when(roles.addAccessToken(token, client, user)); - }, - - validateScope: function(user, client, scope) { - return scope; - }, + grantTypeAllowed: function (clientId, grantType, callback) { + return callback(null, 'password' === grantType); + }, - getUser: function (username, password) { - if (username && username.toLowerCase) { - username = username.toLowerCase(); - } + saveAccessToken: function (accessToken, clientId, userId, expires, callback) { + when(roles.addAccessToken(accessToken, clientId, userId, expires)) + .ensure(callback); + }, - return when(roles.validateLogin(username, password)); - } + getUser: function (username, password, callback) { + if (username && username.toLowerCase) { + username = username.toLowerCase(); + } + + when(roles.validateLogin(username, password)) + .then( + function (user) { + callback(null, { id: user._id }); + }, + function (err) { + callback(err, null); + }); + } }; module.exports = OAuth2ServerModel; diff --git a/main.js b/main.js index 6eabcdaa..6d583fb3 100644 --- a/main.js +++ b/main.js @@ -19,70 +19,60 @@ var fs = require('fs'); var http = require('http'); var express = require('express'); -var bodyParser = require('body-parser'); -var morgan = require('morgan'); + var settings = require('./settings.js'); var utilities = require("./lib/utilities.js"); var logger = require('./lib/logger.js'); -//var OAuthServer = require('node-oauth2-server'); -var oauthserver = require('express-oauth-server'); - +var OAuthServer = require('node-oauth2-server'); var OAuth2ServerModel = require('./lib/OAuth2ServerModel'); var AccessTokenViews = require('./lib/AccessTokenViews.js'); -var CustomerViews = require('./lib/CustomerViews.js'); global._socket_counter = 1; +var oauth = OAuthServer({ + model: new OAuth2ServerModel({ }), + allow: { + "post": ['/v1/users'], + "get": ['/server/health', '/v1/access_tokens'], + "delete": ['/v1/access_tokens/([0-9a-f]{40})'] + }, + grants: ['password'], + accessTokenLifetime: 7776000 //90 days +}); + var set_cors_headers = function (req, res, next) { - if ('OPTIONS' === req.method) { - res.set({ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', - 'Access-Control-Max-Age': 300 - }); - return res.sendStatus(204); - } - else { - res.set({'Access-Control-Allow-Origin': '*'}); - next(); - } + if ('OPTIONS' === req.method) { + res.set({ + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', + 'Access-Control-Max-Age': 300 + }); + return res.send(204); + } + else { + res.set({'Access-Control-Allow-Origin': '*'}); + next(); + } }; //TODO: something better here process.on('uncaughtException', function (ex) { - var details = ''; - try { details = JSON.stringify(ex); } catch (ex2) { } + var details = ''; + try { details = JSON.stringify(ex); } catch (ex2) { } - logger.error('Caught exception: ' + ex + details); + logger.error('Caught exception: ' + ex + details); }); var app = express(); - -app.oauth = new oauthserver({ - model: new OAuth2ServerModel({}), - allowBearerTokensInQueryString:true, - accessTokenLifetime: 7776000 //90 days -}); - -app.set('json spaces', 2); -app.use(morgan('combined')); -app.use(bodyParser.urlencoded({ extended: true })); -app.use(bodyParser.json()); +app.use(express.logger()); +app.use(express.bodyParser()); app.use(set_cors_headers); - -app.post('/oauth/token', app.oauth.token()); - -//app.use(app.oauth.token()); -app.all('/v1/devices*', app.oauth.authenticate()); -app.all('/v1/provisioning*', app.oauth.authenticate()); -app.all('/v1/events*', app.oauth.authenticate()); -//app.all('/v1/products', app.oauth.authenticate()); //TODO remove customer creation -//app.use(oauth.handler()); -//app.use(oauth.errorHandler()); +app.use(oauth.handler()); +app.use(oauth.errorHandler()); var UserCreator = require('./lib/UserCreator.js'); app.post('/v1/users', UserCreator.getMiddleware()); @@ -90,21 +80,21 @@ app.post('/v1/users', UserCreator.getMiddleware()); var api = require('./views/api_v1.js'); var eventsV1 = require('./views/EventViews001.js'); var tokenViews = new AccessTokenViews({ }); -var customerViews = new CustomerViews({ }); eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); -customerViews.loadViews(app); + + app.use(function (req, res, next) { - res.status(404).send({ ok: false, error: "Not Found" }); + return res.send(404); }); -var node_port = process.env.NODE_PORT || '9000'; +var node_port = process.env.NODE_PORT || '8080'; node_port = parseInt(node_port); console.log("Starting server, listening on " + node_port); @@ -113,7 +103,7 @@ http.createServer(app).listen(node_port); var DeviceServer = require("spark-protocol").DeviceServer; var server = new DeviceServer({ - coreKeysDir: settings.coreKeysDir + coreKeysDir: settings.coreKeysDir }); global.server = server; server.start(); @@ -121,5 +111,5 @@ server.start(); var ips = utilities.getIPAddresses(); for(var i=0;i Date: Thu, 24 Nov 2016 13:24:35 -0800 Subject: [PATCH 052/504] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bab8c944..c9562163 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Quick Install ``` git clone https://github.com/spark/spark-server.git -cd spark-server/js +cd spark-server/ npm install node main.js ``` From 408a5f3ce1e33a13071e93c9bd874fd4f063b52a Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 24 Nov 2016 13:26:56 -0800 Subject: [PATCH 053/504] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9562163..5c443364 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,14 @@ particle config profile_name apiUrl "http://DOMAIN_OR_IP" For the local cloud, the port number 8080 needs to be added behind: http://domain_or_ip:8080 -This will create a new profile to point to your server and switching back to the spark cloud is simply particle config particle and other profiles would be particle config profile_name +This will create a new profile to point to your server and switching back to the spark cloud is simply: +``` +particle config particle +``` +and other profiles would be: +``` +particle config profile_name +``` 4.) We will now point over to the local cloud using particle config profile_name From 13a81877001a1c64bdb8be582f4bf88deaf7aadd Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 24 Nov 2016 13:27:22 -0800 Subject: [PATCH 054/504] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c443364..d02829c3 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,10 @@ and other profiles would be: particle config profile_name ``` -4.) We will now point over to the local cloud using particle config profile_name +4.) We will now point over to the local cloud using +``` +particle config profile_name +``` 5.) On a separate CMD from the one running the server, type From 6c09b8226e5b56af5cdb04f32537626edc75306f Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 24 Nov 2016 13:55:57 -0800 Subject: [PATCH 055/504] Update README.md --- README.md | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d02829c3..f3a07e08 100644 --- a/README.md +++ b/README.md @@ -28,41 +28,34 @@ node main.js How do I get started? ===================== -1.) Run the server with: +1) Run the server with: ``` node main.js ``` -2.) Watch for your IP address, you'll see something like: +2) Watch for your IP address, you'll see something like: ``` Your server IP address is: 192.168.1.10 ``` -3.) We will now create a new server profile on Particle-CLI using the command: +3) We will now create a new server profile on Particle-CLI using the command: ``` particle config profile_name apiUrl "http://DOMAIN_OR_IP" ``` -For the local cloud, the port number 8080 needs to be added behind: http://domain_or_ip:8080 +For the local cloud, the port number 8080 needs to be added behind: `http://domain_or_ip:8080`. It is important to also have the `http://` otherwise it won't work. -This will create a new profile to point to your server and switching back to the spark cloud is simply: -``` -particle config particle -``` -and other profiles would be: -``` -particle config profile_name -``` +This will create a new profile to point to your server and switching back to the spark cloud is simply `particle config particle` and other profiles would be `particle config profile_name` -4.) We will now point over to the local cloud using +4) We will now point over to the local cloud using ``` particle config profile_name ``` -5.) On a separate CMD from the one running the server, type +5) On a separate CMD from the one running the server, type ``` particle setup @@ -72,28 +65,23 @@ This will create an account on the local cloud Perform CTRL + C once you logon with Particle-CLI asking you to send Wifi-credentials etc... -6.) On Command-line, cd to particle-server and place your core in DFU mode [flashing yellow] +6) Put your core into listening mode, and run `spark identify` to get your core id. You'll need this id later -7.) Create and provision access on your local cloud with the keys doctor: +7) `mkdir ..\temp` and `cd ..\temp` - A bunch of keys will be generated in the next steps. -``` - particle keys doctor your_core_id -``` - -8.) Change server keys to local cloud key + IP Address +8) Change server keys to local cloud key + IP Address ``` -particle keys server default_key.pub.pem IP_ADDRESS +particle keys server ..\spark-server\default_key.pub.pem IP_ADDRESS ``` -9.) Go to cores_key directory to place core public key inside +9) Create and provision access on your local cloud with the keys doctor: ``` -cd core_keys -place core in DFU-mode -particle keys save INPUT_DEVICE_ID_HERE + particle keys doctor your_core_id ``` + What kind of project is this? ====================================== From acc90dc346b5d8631c2a9cd7757b331785829a88 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 25 Nov 2016 13:43:04 +0200 Subject: [PATCH 056/504] move sources to /src folder --- {lib => src/lib}/AccessTokenViews.js | 0 {lib => src/lib}/CoreController.js | 0 {lib => src/lib}/OAuth2ServerModel.js | 0 {lib => src/lib}/PasswordHasher.js | 0 {lib => src/lib}/RolesController.js | 0 {lib => src/lib}/UserCreator.js | 0 {lib => src/lib}/logger.js | 0 {lib => src/lib}/utilities.js | 0 main.js => src/main.js | 0 settings.js => src/settings.js | 0 {views => src/views}/EventViews001.js | 0 {views => src/views}/api_v1.js | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename {lib => src/lib}/AccessTokenViews.js (100%) rename {lib => src/lib}/CoreController.js (100%) rename {lib => src/lib}/OAuth2ServerModel.js (100%) rename {lib => src/lib}/PasswordHasher.js (100%) rename {lib => src/lib}/RolesController.js (100%) rename {lib => src/lib}/UserCreator.js (100%) rename {lib => src/lib}/logger.js (100%) rename {lib => src/lib}/utilities.js (100%) rename main.js => src/main.js (100%) rename settings.js => src/settings.js (100%) rename {views => src/views}/EventViews001.js (100%) rename {views => src/views}/api_v1.js (100%) diff --git a/lib/AccessTokenViews.js b/src/lib/AccessTokenViews.js similarity index 100% rename from lib/AccessTokenViews.js rename to src/lib/AccessTokenViews.js diff --git a/lib/CoreController.js b/src/lib/CoreController.js similarity index 100% rename from lib/CoreController.js rename to src/lib/CoreController.js diff --git a/lib/OAuth2ServerModel.js b/src/lib/OAuth2ServerModel.js similarity index 100% rename from lib/OAuth2ServerModel.js rename to src/lib/OAuth2ServerModel.js diff --git a/lib/PasswordHasher.js b/src/lib/PasswordHasher.js similarity index 100% rename from lib/PasswordHasher.js rename to src/lib/PasswordHasher.js diff --git a/lib/RolesController.js b/src/lib/RolesController.js similarity index 100% rename from lib/RolesController.js rename to src/lib/RolesController.js diff --git a/lib/UserCreator.js b/src/lib/UserCreator.js similarity index 100% rename from lib/UserCreator.js rename to src/lib/UserCreator.js diff --git a/lib/logger.js b/src/lib/logger.js similarity index 100% rename from lib/logger.js rename to src/lib/logger.js diff --git a/lib/utilities.js b/src/lib/utilities.js similarity index 100% rename from lib/utilities.js rename to src/lib/utilities.js diff --git a/main.js b/src/main.js similarity index 100% rename from main.js rename to src/main.js diff --git a/settings.js b/src/settings.js similarity index 100% rename from settings.js rename to src/settings.js diff --git a/views/EventViews001.js b/src/views/EventViews001.js similarity index 100% rename from views/EventViews001.js rename to src/views/EventViews001.js diff --git a/views/api_v1.js b/src/views/api_v1.js similarity index 100% rename from views/api_v1.js rename to src/views/api_v1.js From a8ed06f34da7664c5af906b0e08e7819cb3e265f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 25 Nov 2016 13:43:45 +0200 Subject: [PATCH 057/504] change gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 24f2f826..f358f104 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# ide files +.idea + # Logs logs *.log @@ -30,3 +33,6 @@ users *.der *.pem *.pub.pem + +# build folder +build \ No newline at end of file From 348e5172c044c3716bb102aa97f4fd9a56d9fb9b Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 25 Nov 2016 13:44:52 +0200 Subject: [PATCH 058/504] setup babel, flow, nodemon, start/build scripts for the project --- .babelrc | 4 ++++ .flowconfig | 7 +++++++ .gitignore | 2 +- package.json | 42 ++++++++++++++++++++++++++---------------- 4 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 .babelrc create mode 100644 .flowconfig diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..32f65e30 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "plugins": ["transform-flow-strip-types"], + "presets": ["latest"] +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 00000000..4a58bdcd --- /dev/null +++ b/.flowconfig @@ -0,0 +1,7 @@ +[ignore] + +[include] + +[libs] + +[options] diff --git a/.gitignore b/.gitignore index f358f104..4bd37115 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,4 @@ users *.pub.pem # build folder -build \ No newline at end of file +build diff --git a/package.json b/package.json index 8e618576..98dcdb5c 100644 --- a/package.json +++ b/package.json @@ -13,21 +13,6 @@ "email": "david@spark.io", "url": "https://www.spark.io/" }, - "dependencies": { - "express": "~3.4.4", - "hogan-express": "~0.5.1", - "moment": "*", - "node-oauth2-server": "~1.5.3", - "request": "*", - "spark-protocol": "../spark-protocol", - "ursa": "*", - "when": "*", - "xtend": "*" - }, - "scripts": { - "start": "node main.js" - }, - "main": "main.js", "contributors": [ { "name": "Kenneth Lim", @@ -39,5 +24,30 @@ "email": "emily@spark.io", "url": "https://github.com/emilyrose" } - ] + ], + "scripts": { + "build": "babel ./src --out-dir ./build", + "build:clean": "rimraf ./build", + "prebuild": "npm run build:clean", + "start": "nodemon --exec babel-node ./src/main.js --watch src", + "start:prod": "npm run build && node ./build/main.js" + }, + "dependencies": { + "express": "~3.4.4", + "hogan-express": "~0.5.1", + "moment": "*", + "node-oauth2-server": "~1.5.3", + "request": "*", + "spark-protocol": "../spark-protocol", + "ursa": "*", + "when": "*", + "xtend": "*" + }, + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-plugin-transform-flow-strip-types": "^6.18.0", + "babel-preset-latest": "^6.16.0", + "nodemon": "^1.11.0", + "rimraf": "^2.5.4" + } } From b529902c62535755f44f64fc8f379249920da21b Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 25 Nov 2016 16:56:24 +0200 Subject: [PATCH 059/504] add watching for spark-protocol changes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98dcdb5c..eeb27a31 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib", "start:prod": "npm run build && node ./build/main.js" }, "dependencies": { From 0c8ee1dba7fc0b368921bffb115849494275ccc6 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 25 Nov 2016 08:05:47 -0800 Subject: [PATCH 060/504] Fixed issue with nodemon restarting. --- package.json | 2 +- src/settings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index eeb27a31..04f83719 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users", "start:prod": "npm run build && node ./build/main.js" }, "dependencies": { diff --git a/src/settings.js b/src/settings.js index aa2896d3..159ba6a9 100644 --- a/src/settings.js +++ b/src/settings.js @@ -31,4 +31,4 @@ module.exports = { maxHooksPerUser: 20, maxHooksPerDevice: 10, -}; \ No newline at end of file +}; From 1632cdfcc0a559da83bed64045c31608f159f0fd Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 25 Nov 2016 18:34:25 +0200 Subject: [PATCH 061/504] add editorConfig --- .editorconfig | 7 +++++++ src/main.js | 18 +++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..d4eed840 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = false +indent_style = space +indent_size = 2 diff --git a/src/main.js b/src/main.js index 6d583fb3..fc7240f4 100644 --- a/src/main.js +++ b/src/main.js @@ -43,15 +43,15 @@ var oauth = OAuthServer({ }); var set_cors_headers = function (req, res, next) { - if ('OPTIONS' === req.method) { - res.set({ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', - 'Access-Control-Max-Age': 300 - }); - return res.send(204); - } + if ('OPTIONS' === req.method) { + res.set({ + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', + 'Access-Control-Max-Age': 300 + }); + return res.send(204); + } else { res.set({'Access-Control-Allow-Origin': '*'}); next(); From 7e8e2464f19cec086566d3a162640b2d7a9035b6 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 25 Nov 2016 08:43:27 -0800 Subject: [PATCH 062/504] Working on adding webhooks. --- src/lib/webhooks/WebhookUtils.js | 24 ++++++++++++++++++ src/main.js | 4 ++- src/views/Webhooks.js | 43 ++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/lib/webhooks/WebhookUtils.js create mode 100644 src/views/Webhooks.js diff --git a/src/lib/webhooks/WebhookUtils.js b/src/lib/webhooks/WebhookUtils.js new file mode 100644 index 00000000..2dbff6b8 --- /dev/null +++ b/src/lib/webhooks/WebhookUtils.js @@ -0,0 +1,24 @@ +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +*/ + +const settings = require('../../settings.js'); + +class WebhookUtils { +} + +module.exports = new WebhookUtils(); diff --git a/src/main.js b/src/main.js index 6d583fb3..0f980518 100644 --- a/src/main.js +++ b/src/main.js @@ -29,6 +29,8 @@ var OAuthServer = require('node-oauth2-server'); var OAuth2ServerModel = require('./lib/OAuth2ServerModel'); var AccessTokenViews = require('./lib/AccessTokenViews.js'); +const Webhook = require('./views/Webhooks'); + global._socket_counter = 1; var oauth = OAuthServer({ @@ -85,7 +87,7 @@ var tokenViews = new AccessTokenViews({ }); eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); - +const webhookViews = new Webhook(app); diff --git a/src/views/Webhooks.js b/src/views/Webhooks.js new file mode 100644 index 00000000..0a7832b0 --- /dev/null +++ b/src/views/Webhooks.js @@ -0,0 +1,43 @@ +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +*/ + +const ROUTE_BASE = '/v1/webhooks'; + +class Webhooks { + constructor(app) { + app.get(ROUTE_BASE, this.get); + } + + get(request, response) { + // console.log(JSON.stringify(request)); + + response.json( + [ + { + "id": "12345", + "url": "https://samplesite.com", + "event": "hello", + "created_at": "2016-04-28T17:06:33.123Z", + "requestType": "POST", + }, + ], + ); + } +} + +module.exports = Webhooks; From fe833695190da98301b0e6676307b05070bc493c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 25 Nov 2016 18:34:25 +0200 Subject: [PATCH 063/504] add editorConfig --- .editorconfig | 7 +++++++ src/main.js | 18 +++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..d4eed840 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = false +indent_style = space +indent_size = 2 diff --git a/src/main.js b/src/main.js index 0f980518..6b6bfa4e 100644 --- a/src/main.js +++ b/src/main.js @@ -45,15 +45,15 @@ var oauth = OAuthServer({ }); var set_cors_headers = function (req, res, next) { - if ('OPTIONS' === req.method) { - res.set({ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', - 'Access-Control-Max-Age': 300 - }); - return res.send(204); - } + if ('OPTIONS' === req.method) { + res.set({ + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', + 'Access-Control-Max-Age': 300 + }); + return res.send(204); + } else { res.set({'Access-Control-Allow-Origin': '*'}); next(); From 7e29fcc58fdf1ed4500fc7518e12880174eae265 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 25 Nov 2016 23:37:23 +0200 Subject: [PATCH 064/504] update express, use morgan as logger --- package.json | 4 +++- src/main.js | 17 +++++++++-------- src/views/api_v1.js | 8 ++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 04f83719..e63524f3 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ "start:prod": "npm run build && node ./build/main.js" }, "dependencies": { - "express": "~3.4.4", + "body-parser": "^1.15.2", + "express": "^4.14.0", "hogan-express": "~0.5.1", "moment": "*", + "morgan": "^1.7.0", "node-oauth2-server": "~1.5.3", "request": "*", "spark-protocol": "../spark-protocol", diff --git a/src/main.js b/src/main.js index 6b6bfa4e..b5b85d3a 100644 --- a/src/main.js +++ b/src/main.js @@ -16,10 +16,10 @@ * You can download the source here: https://github.com/spark/spark-server */ -var fs = require('fs'); -var http = require('http'); -var express = require('express'); - +import http from 'http'; +import express from 'express'; +import morgan from 'morgan'; +import bodyParser from 'body-parser' var settings = require('./settings.js'); var utilities = require("./lib/utilities.js"); @@ -69,9 +69,10 @@ process.on('uncaughtException', function (ex) { }); -var app = express(); -app.use(express.logger()); -app.use(express.bodyParser()); +const app = express(); +app.use(morgan('combined')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); app.use(set_cors_headers); app.use(oauth.handler()); app.use(oauth.errorHandler()); @@ -92,7 +93,7 @@ const webhookViews = new Webhook(app); app.use(function (req, res, next) { - return res.send(404); + return res.sendStatus(404); }); diff --git a/src/views/api_v1.js b/src/views/api_v1.js index 53e66de7..fee26576 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -122,7 +122,7 @@ var Api = { devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; } - res.json(200, devices); + res.status(200).json(devices); }); }, @@ -327,7 +327,7 @@ var Api = { loadCore: function (req, res, next) { - req.coreID = req.param('coreid') || req.body.id; + req.coreID = req.params.coreid || req.body.id; //load core info! req.coreInfo = { @@ -381,8 +381,8 @@ var Api = { var userid = Api.getUserID(req); var socketID = Api.getSocketID(userid), coreID = req.coreID, - varName = req.param('var'), - format = req.param('format'); + varName = req.params.var, + format = req.params.format; logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); From 33e526c7ec81779a5402b09448bb8ba04c410a72 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sat, 26 Nov 2016 01:14:09 +0200 Subject: [PATCH 065/504] update main.js to es6 style --- src/main.js | 115 +++++++++++++++++++++++++--------------------------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/src/main.js b/src/main.js index b5b85d3a..34fbbf45 100644 --- a/src/main.js +++ b/src/main.js @@ -16,43 +16,62 @@ * You can download the source here: https://github.com/spark/spark-server */ -import http from 'http'; +import bodyParser from 'body-parser'; import express from 'express'; +import http from 'http'; import morgan from 'morgan'; -import bodyParser from 'body-parser' +import OAuthServer from 'node-oauth2-server'; +import { DeviceServer } from 'spark-protocol'; +import settings from './settings'; -var settings = require('./settings.js'); -var utilities = require("./lib/utilities.js"); -var logger = require('./lib/logger.js'); +import utilities from './lib/utilities'; +import logger from './lib/logger'; +import OAuth2ServerModel from './lib/OAuth2ServerModel'; +import AccessTokenViews from './lib/AccessTokenViews'; +import UserCreator from './lib/UserCreator.js'; -var OAuthServer = require('node-oauth2-server'); -var OAuth2ServerModel = require('./lib/OAuth2ServerModel'); -var AccessTokenViews = require('./lib/AccessTokenViews.js'); +import api from './views/api_v1.js'; +import eventsV1 from './views/EventViews001.js'; +import Webhook from './views/Webhooks'; -const Webhook = require('./views/Webhooks'); +const NODE_PORT = process.env.NODE_PORT || 8080; +// TODO wny do we need this? (Anton Puko) global._socket_counter = 1; -var oauth = OAuthServer({ - model: new OAuth2ServerModel({ }), - allow: { - "post": ['/v1/users'], - "get": ['/server/health', '/v1/access_tokens'], - "delete": ['/v1/access_tokens/([0-9a-f]{40})'] - }, +//TODO: something better here +process.on('uncaughtException', (exception) => { + let details = ''; + try { + details = JSON.stringify(exception); + } catch (stringifyException) { + logger.error('Caught exception: ' + stringifyException); + } + logger.error('Caught exception: ' + exception + details); +}); + +const app = express(); + +const oauth = OAuthServer({ + allow: { + "delete": ['/v1/access_tokens/([0-9a-f]{40})'], + "get": ['/server/health', '/v1/access_tokens'], + "post": ['/v1/users'], + }, + accessTokenLifetime: 7776000, //90 days grants: ['password'], - accessTokenLifetime: 7776000 //90 days + model: new OAuth2ServerModel({}), }); -var set_cors_headers = function (req, res, next) { +const setCORSHeaders = (req, res, next) => { if ('OPTIONS' === req.method) { res.set({ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', - 'Access-Control-Max-Age': 300 + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Max-Age': 300, }); - return res.send(204); + return res.sendStatus(204); } else { res.set({'Access-Control-Allow-Origin': '*'}); @@ -60,59 +79,37 @@ var set_cors_headers = function (req, res, next) { } }; -//TODO: something better here -process.on('uncaughtException', function (ex) { - var details = ''; - try { details = JSON.stringify(ex); } catch (ex2) { } - - logger.error('Caught exception: ' + ex + details); -}); - - -const app = express(); app.use(morgan('combined')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); -app.use(set_cors_headers); +app.use(setCORSHeaders); app.use(oauth.handler()); app.use(oauth.errorHandler()); -var UserCreator = require('./lib/UserCreator.js'); -app.post('/v1/users', UserCreator.getMiddleware()); - -var api = require('./views/api_v1.js'); -var eventsV1 = require('./views/EventViews001.js'); -var tokenViews = new AccessTokenViews({ }); +app.post('/v1/users', UserCreator.getMiddleware()); +const tokenViews = new AccessTokenViews({}); eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); const webhookViews = new Webhook(app); +app.use((req, res, next) => res.sendStatus(404)); -app.use(function (req, res, next) { - return res.sendStatus(404); -}); - - -var node_port = process.env.NODE_PORT || '8080'; -node_port = parseInt(node_port); +console.log("Starting server, listening on " + NODE_PORT); +http.createServer(app).listen(NODE_PORT); -console.log("Starting server, listening on " + node_port); -http.createServer(app).listen(node_port); - - -var DeviceServer = require("spark-protocol").DeviceServer; -var server = new DeviceServer({ - coreKeysDir: settings.coreKeysDir +const deviceServer = new DeviceServer({ + coreKeysDir: settings.coreKeysDir, }); -global.server = server; -server.start(); + +// TODO wny do we need next line? (Anton Puko) +global.server = deviceServer; +deviceServer.start(); -var ips = utilities.getIPAddresses(); -for(var i=0;i + console.log(`Your server IP address is: ${ip}`), +); From f94495efb8ff3e4b0ab827387d713f768493cf27 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sat, 26 Nov 2016 02:38:23 +0200 Subject: [PATCH 066/504] remove hogan-express --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index e63524f3..9b70e588 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "dependencies": { "body-parser": "^1.15.2", "express": "^4.14.0", - "hogan-express": "~0.5.1", "moment": "*", "morgan": "^1.7.0", "node-oauth2-server": "~1.5.3", From 93cef6a7be8658b95941c076526d4a276492cc0e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sat, 26 Nov 2016 03:01:12 +0200 Subject: [PATCH 067/504] rename req,res in main.js --- src/main.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.js b/src/main.js index 34fbbf45..ff30bb3f 100644 --- a/src/main.js +++ b/src/main.js @@ -63,18 +63,18 @@ const oauth = OAuthServer({ model: new OAuth2ServerModel({}), }); -const setCORSHeaders = (req, res, next) => { - if ('OPTIONS' === req.method) { - res.set({ +const setCORSHeaders = (request, response, next) => { + if ('OPTIONS' === request.method) { + response.set({ 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Origin': '*', 'Access-Control-Max-Age': 300, }); - return res.sendStatus(204); + return response.sendStatus(204); } else { - res.set({'Access-Control-Allow-Origin': '*'}); + response.set({'Access-Control-Allow-Origin': '*'}); next(); } }; @@ -95,7 +95,7 @@ api.loadViews(app); tokenViews.loadViews(app); const webhookViews = new Webhook(app); -app.use((req, res, next) => res.sendStatus(404)); +app.use((request, response, next) => response.sendStatus(404)); console.log("Starting server, listening on " + NODE_PORT); From 147bbe5df8c267817ef5982bd335e9b7cd0b391e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sat, 26 Nov 2016 03:08:19 +0200 Subject: [PATCH 068/504] clean settings.js --- src/lib/CoreController.js | 2 +- src/lib/RolesController.js | 2 +- src/settings.js | 20 +++++++++----------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/lib/CoreController.js b/src/lib/CoreController.js index 85aa1a66..dfa0521b 100644 --- a/src/lib/CoreController.js +++ b/src/lib/CoreController.js @@ -22,7 +22,7 @@ var extend = require('xtend'); var EventEmitter = require('events').EventEmitter; var logger = require('./logger.js'); -var settings = require("../settings"); +import settings from '../settings'; var utilities = require("./utilities.js"); diff --git a/src/lib/RolesController.js b/src/lib/RolesController.js index 0d6c8e54..fe5215b0 100644 --- a/src/lib/RolesController.js +++ b/src/lib/RolesController.js @@ -23,7 +23,7 @@ var sequence = require('when/sequence'); var pipeline = require('when/pipeline'); var PasswordHasher = require('./PasswordHasher.js'); var roles = require('./RolesController.js'); -var settings = require('../settings.js'); +import settings from '../settings'; var logger = require('./logger.js'); diff --git a/src/settings.js b/src/settings.js index 159ba6a9..7773f7b8 100644 --- a/src/settings.js +++ b/src/settings.js @@ -15,20 +15,18 @@ * * You can download the source here: https://github.com/spark/spark-server */ +// @flow -var path = require('path'); - -module.exports = { - baseUrl: "http://localhost", - userDataDir: path.join(__dirname, "users"), - coreKeysDir: path.join(__dirname, "core_keys"), +import path from 'path'; +export default { + baseUrl: 'http://localhost', + coreFlashTimeout: 90000, + coreKeysDir: path.join(__dirname, 'core_keys'), coreRequestTimeout: 30000, - isCoreOnlineTimeout: 2000, - coreSignalTimeout: 30000, - coreFlashTimeout: 90000, - - maxHooksPerUser: 20, + isCoreOnlineTimeout: 2000, maxHooksPerDevice: 10, + maxHooksPerUser: 20, + userDataDir: path.join(__dirname, 'users'), }; From ee65f0a382de5d2176a1ce9e363d464d5ea28584 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 26 Nov 2016 13:31:52 -0800 Subject: [PATCH 069/504] Working on webhooks controller --- .babelrc | 9 +++-- package.json | 5 +++ src/lib/FileRepositoryBase.js | 0 src/lib/controllers/ViewBase.js | 17 +++++++++ src/lib/controllers/Webhooks.js | 39 ++++++++++++++++++++ src/lib/webhooks/WebhookFileRepository.js | 0 src/views/Webhooks.js | 43 ----------------------- 7 files changed, 68 insertions(+), 45 deletions(-) create mode 100644 src/lib/FileRepositoryBase.js create mode 100644 src/lib/controllers/ViewBase.js create mode 100644 src/lib/controllers/Webhooks.js create mode 100644 src/lib/webhooks/WebhookFileRepository.js delete mode 100644 src/views/Webhooks.js diff --git a/.babelrc b/.babelrc index 32f65e30..e01b0599 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,9 @@ { - "plugins": ["transform-flow-strip-types"], - "presets": ["latest"] + "plugins": [ + "transform-class-properties", + "transform-es2015-destructuring", + "transform-es2015-spread", + "transform-flow-strip-types", + ], + "presets": ["es2015", "stage-0", "latest"] } diff --git a/package.json b/package.json index 04f83719..e77e36b5 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,13 @@ }, "devDependencies": { "babel-cli": "^6.18.0", + "babel-plugin-transform-class-properties": "^6.19.0", + "babel-plugin-transform-es2015-destructuring": "^6.19.0", + "babel-plugin-transform-es2015-spread": "^6.8.0", "babel-plugin-transform-flow-strip-types": "^6.18.0", + "babel-preset-es2015": "^6.18.0", "babel-preset-latest": "^6.16.0", + "babel-preset-stage-0": "^6.16.0", "nodemon": "^1.11.0", "rimraf": "^2.5.4" } diff --git a/src/lib/FileRepositoryBase.js b/src/lib/FileRepositoryBase.js new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/controllers/ViewBase.js b/src/lib/controllers/ViewBase.js new file mode 100644 index 00000000..ed943a67 --- /dev/null +++ b/src/lib/controllers/ViewBase.js @@ -0,0 +1,17 @@ +class Controller { + bad(message) { + return { + data: {message}, + status: 400, + }; + } + + ok(output) { + return { + data: output, + status: 200, + }; + } +} + +module.exports = ViewBase; diff --git a/src/lib/controllers/Webhooks.js b/src/lib/controllers/Webhooks.js new file mode 100644 index 00000000..100069b2 --- /dev/null +++ b/src/lib/controllers/Webhooks.js @@ -0,0 +1,39 @@ +const ViewBase = require('./Controller'); + +const ROUTE_BASE = '/v1/webhooks'; + +class WebhookController extends Controller { + get(model) { + return this.ok([ + { + "id": "12345", + "url": "https://samplesite.com", + "event": "hello", + "created_at": "2016-04-28T17:06:33.123Z", + "requestType": "POST", + }, + { + "id": "11111", + "url": "https://samplesite.com", + "event": "hello", + "created_at": "2016-04-28T17:06:33.123Z", + "requestType": "POST", + }, + ]); + } + + post(model) { + const webhookToSave = { + ...model, + created_at: new Date(), + }; + console.log(); + console.log('foo'); + console.log(model); + console.log('bar'); + console.log(); + return this.ok(); + } +} + +module.exports = WebhookController; diff --git a/src/lib/webhooks/WebhookFileRepository.js b/src/lib/webhooks/WebhookFileRepository.js new file mode 100644 index 00000000..e69de29b diff --git a/src/views/Webhooks.js b/src/views/Webhooks.js deleted file mode 100644 index 0a7832b0..00000000 --- a/src/views/Webhooks.js +++ /dev/null @@ -1,43 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -const ROUTE_BASE = '/v1/webhooks'; - -class Webhooks { - constructor(app) { - app.get(ROUTE_BASE, this.get); - } - - get(request, response) { - // console.log(JSON.stringify(request)); - - response.json( - [ - { - "id": "12345", - "url": "https://samplesite.com", - "event": "hello", - "created_at": "2016-04-28T17:06:33.123Z", - "requestType": "POST", - }, - ], - ); - } -} - -module.exports = Webhooks; From 54655c017d1142decd60eadae456bc308294c212 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 26 Nov 2016 14:07:49 -0800 Subject: [PATCH 070/504] Working on setting up the controllers. --- .babelrc | 3 ++- package.json | 3 +++ src/lib/RouteConfig.js | 5 +++++ src/lib/controllers/{ViewBase.js => Controller.js} | 4 +--- .../{Webhooks.js => WebhookController.js} | 13 +++++++++---- src/lib/decorators/httpVerb.js | 8 ++++++++ src/lib/decorators/route.js | 6 ++++++ src/lib/decorators/types.js | 11 +++++++++++ src/main.js | 4 ++-- src/settings.js | 2 +- 10 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 src/lib/RouteConfig.js rename src/lib/controllers/{ViewBase.js => Controller.js} (77%) rename src/lib/controllers/{Webhooks.js => WebhookController.js} (73%) create mode 100644 src/lib/decorators/httpVerb.js create mode 100644 src/lib/decorators/route.js create mode 100644 src/lib/decorators/types.js diff --git a/.babelrc b/.babelrc index e01b0599..72aebb68 100644 --- a/.babelrc +++ b/.babelrc @@ -1,9 +1,10 @@ { "plugins": [ "transform-class-properties", + "transform-decorators-legacy", "transform-es2015-destructuring", "transform-es2015-spread", "transform-flow-strip-types", ], - "presets": ["es2015", "stage-0", "latest"] + "presets": ["es2015", "latest", "stage-0", "stage-1"] } diff --git a/package.json b/package.json index 78f31fc1..970b51cd 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,15 @@ "devDependencies": { "babel-cli": "^6.18.0", "babel-plugin-transform-class-properties": "^6.19.0", + "babel-plugin-transform-decorators": "^6.13.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-es2015-destructuring": "^6.19.0", "babel-plugin-transform-es2015-spread": "^6.8.0", "babel-plugin-transform-flow-strip-types": "^6.18.0", "babel-preset-es2015": "^6.18.0", "babel-preset-latest": "^6.16.0", "babel-preset-stage-0": "^6.16.0", + "babel-preset-stage-1": "^6.16.0", "nodemon": "^1.11.0", "rimraf": "^2.5.4" } diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js new file mode 100644 index 00000000..e03956cb --- /dev/null +++ b/src/lib/RouteConfig.js @@ -0,0 +1,5 @@ +import type Controller from './controllers/Controller' + +export default (app: Object, controllers: Array): void => { + +}; diff --git a/src/lib/controllers/ViewBase.js b/src/lib/controllers/Controller.js similarity index 77% rename from src/lib/controllers/ViewBase.js rename to src/lib/controllers/Controller.js index ed943a67..e0b9aa38 100644 --- a/src/lib/controllers/ViewBase.js +++ b/src/lib/controllers/Controller.js @@ -1,4 +1,4 @@ -class Controller { +export default class Controller { bad(message) { return { data: {message}, @@ -13,5 +13,3 @@ class Controller { }; } } - -module.exports = ViewBase; diff --git a/src/lib/controllers/Webhooks.js b/src/lib/controllers/WebhookController.js similarity index 73% rename from src/lib/controllers/Webhooks.js rename to src/lib/controllers/WebhookController.js index 100069b2..d665d876 100644 --- a/src/lib/controllers/Webhooks.js +++ b/src/lib/controllers/WebhookController.js @@ -1,8 +1,11 @@ -const ViewBase = require('./Controller'); - -const ROUTE_BASE = '/v1/webhooks'; +import Controller from './Controller'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; class WebhookController extends Controller { + + @httpVerb('get'); + @route('/v1/webhooks'); get(model) { return this.ok([ { @@ -22,6 +25,8 @@ class WebhookController extends Controller { ]); } + @httpVerb('post'); + @route('/v1/webhooks'); post(model) { const webhookToSave = { ...model, @@ -36,4 +41,4 @@ class WebhookController extends Controller { } } -module.exports = WebhookController; +export default WebhookController; diff --git a/src/lib/decorators/httpVerb.js b/src/lib/decorators/httpVerb.js new file mode 100644 index 00000000..186c5180 --- /dev/null +++ b/src/lib/decorators/httpVerb.js @@ -0,0 +1,8 @@ +import type {Decorator, HttpVerb} from './types'; + +export default ( + httpVerb: HttpVerb, +): Decorator => (target, name, descriptor): Object => { + descriptor.httpVerb = httpVerb; + return descriptor; +}; diff --git a/src/lib/decorators/route.js b/src/lib/decorators/route.js new file mode 100644 index 00000000..18ae0d64 --- /dev/null +++ b/src/lib/decorators/route.js @@ -0,0 +1,6 @@ +export default ( + route: string, +): Decorator => (target, name, descriptor): Object => { + descriptor.route = route; + return descriptor; +}; diff --git a/src/lib/decorators/types.js b/src/lib/decorators/types.js new file mode 100644 index 00000000..998d50a0 --- /dev/null +++ b/src/lib/decorators/types.js @@ -0,0 +1,11 @@ +export type Decorator = ( + target: Object, + name: string, + descriptor: Object, +) => Object; + +export type HttpVerb = + 'delete' | + 'get' | + 'post' | + 'put'; diff --git a/src/main.js b/src/main.js index ff30bb3f..dba9baef 100644 --- a/src/main.js +++ b/src/main.js @@ -32,7 +32,7 @@ import UserCreator from './lib/UserCreator.js'; import api from './views/api_v1.js'; import eventsV1 from './views/EventViews001.js'; -import Webhook from './views/Webhooks'; +import WebhookController from './lib/controllers/WebhookController'; const NODE_PORT = process.env.NODE_PORT || 8080; @@ -93,7 +93,7 @@ const tokenViews = new AccessTokenViews({}); eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); -const webhookViews = new Webhook(app); +const webhookViews = new WebhookController(); app.use((request, response, next) => response.sendStatus(404)); diff --git a/src/settings.js b/src/settings.js index 7773f7b8..8220701e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -19,7 +19,7 @@ import path from 'path'; -export default { +export default { baseUrl: 'http://localhost', coreFlashTimeout: 90000, coreKeysDir: path.join(__dirname, 'core_keys'), From d3027bd54dfdc0a0bef7bfe9d3fc6e955dd8d15a Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 26 Nov 2016 16:30:42 -0800 Subject: [PATCH 071/504] Finished webhook controller. --- .gitignore | 1 + package.json | 2 +- src/lib/FileRepositoryBase.js | 0 src/lib/controllers/WebhookController.js | 63 ++++++++++++--------- src/lib/controllers/types.js | 8 +++ src/lib/repository/FileRepositoryBase.js | 45 +++++++++++++++ src/lib/repository/WebhookFileRepository.js | 31 ++++++++++ src/lib/uuid.js | 8 +++ src/main.js | 2 +- src/settings.js | 4 ++ src/types.js | 14 +++++ 11 files changed, 149 insertions(+), 29 deletions(-) delete mode 100644 src/lib/FileRepositoryBase.js create mode 100644 src/lib/controllers/types.js create mode 100644 src/lib/repository/FileRepositoryBase.js create mode 100644 src/lib/repository/WebhookFileRepository.js create mode 100644 src/lib/uuid.js create mode 100644 src/types.js diff --git a/.gitignore b/.gitignore index 4bd37115..35789fae 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ node_modules # Spark generated files/directories which contain secrets core_keys users +webhooks *.der *.pem *.pub.pem diff --git a/package.json b/package.json index 970b51cd..63026828 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", "start:prod": "npm run build && node ./build/main.js" }, "dependencies": { diff --git a/src/lib/FileRepositoryBase.js b/src/lib/FileRepositoryBase.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index ba436f78..60dc1846 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -1,41 +1,50 @@ +import type {Webhook} from '../../types'; +import type {WebhookRepositoryType} from './types'; + +import settings from '../../settings'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; class WebhookController extends Controller { + _webhookRepository: RepositoryType; + + constructor(webhookRepository: RepositoryType) { + super(); + + this._webhookRepository = webhookRepository; + } + @httpVerb('get'); @route('/v1/webhooks'); - get(model) { - return this.ok([ - { - "id": "12345", - "url": "https://samplesite.com", - "event": "hello", - "created_at": "2016-04-28T17:06:33.123Z", - "requestType": "POST", - }, - { - "id": "11111", - "url": "https://samplesite.com", - "event": "hello", - "created_at": "2016-04-28T17:06:33.123Z", - "requestType": "POST", - }, - ]); + get() { + return this.ok(this._webhookRepository.getAll()); + } + + @httpVerb('get'); + @route('/v1/webhooks/:webhookId'); + getByWebhookId({webhookId}: {webhookId: string}) { + return this.ok(this._webhookRepository.getById(webhookId)); } @httpVerb('post'); @route('/v1/webhooks'); - post(model) { - const webhookToSave = { - ...model, - created_at: new Date(), - }; - console.log(); - console.log('foo'); - console.log(model); - console.log('bar'); - console.log(); + post(model: Webhook) { + const newWebhook = this._webhookRepository.create(model); + return this.ok({ + created_at: newWebhook.created_at, + event: newWebhook.event, + hookUrl: settings.baseUrl + '/v1/webhooks/' + newWebhook.id, + id: newWebhook.id, + ok: true, + url: newWebhook.url, + }); + } + + @httpVerb('delete'); + @route('/v1/webhooks/:webhookId'); + delete({webhookId}: {webhookId: string}) { + this._webhookRepository.delete(webhookId); return this.ok(); } } diff --git a/src/lib/controllers/types.js b/src/lib/controllers/types.js new file mode 100644 index 00000000..ca6cf58c --- /dev/null +++ b/src/lib/controllers/types.js @@ -0,0 +1,8 @@ +import type {Webhook} from '../../types'; + +export type RepositoryType = { + create(model: TModel) => TModel, + delete(id: string) => void, + getAll() => Array, + getById(id: string) => TModel, +}; diff --git a/src/lib/repository/FileRepositoryBase.js b/src/lib/repository/FileRepositoryBase.js new file mode 100644 index 00000000..7b10ca01 --- /dev/null +++ b/src/lib/repository/FileRepositoryBase.js @@ -0,0 +1,45 @@ +import fs from 'fs'; +import path from 'path'; + +class FileRepositoryBase { + _path: string; + + constructor(path) { + this._path = path; + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } + } + + createFile(fileName: string, data: Object): void { + console.log(fileName); + const filePath = path.join(this._path, fileName); + if (fs.existsSync(filePath)) { + return; + } + + fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); + } + + deleteFile(fileName: string): void { + const filePath = path.join(this._path, fileName); + if (!fs.existsSync(filePath)) { + return; + } + + fs.unlink(filePath); + } + + getAllData(): Array { + return fs.readdirSync(this._path).map( + fileName => JSON.parse(fs.readFileSync(path.join(this._path, fileName))), + ); + } + + getFile(fileName): TModel { + const filePath = path.join(this._path, fileName); + return JSON.parse(fs.readFileSync(filePath)); + } +} + +export default FileRepositoryBase; diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js new file mode 100644 index 00000000..a75a5289 --- /dev/null +++ b/src/lib/repository/WebhookFileRepository.js @@ -0,0 +1,31 @@ +import type {Webhook} from '../../types'; + +import FileRepositoryBase from './FileRepositoryBase'; +import uuid from '../uuid'; + +class WebhookFileRepository extends FileRepositoryBase { + create(model: Webhook): Webhook { + const modelToSave = { + ...model, + created_at: new Date(), + id: uuid(), + }; + + this.createFile(modelToSave.id + '.json', modelToSave); + return modelToSave; + } + + delete(id: string): void { + this.deleteFile(id + '.json'); + } + + getAll(): Array { + return this.getAllData(); + } + + getById(id: string): Webhook { + return this.getFile(id + '.json'); + } +} + +export default WebhookFileRepository; diff --git a/src/lib/uuid.js b/src/lib/uuid.js new file mode 100644 index 00000000..7e1fcb3d --- /dev/null +++ b/src/lib/uuid.js @@ -0,0 +1,8 @@ +const s4 = (): string => + Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + +export default (): string => { + return (s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4()).toLowerCase(); +} diff --git a/src/main.js b/src/main.js index 6e475c5c..91482fd3 100644 --- a/src/main.js +++ b/src/main.js @@ -97,7 +97,7 @@ eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); routeConfig(app, [ - new WebhookController(), + new WebhookController(settings.webhookRepository), ]); app.use((request, response, next) => response.sendStatus(404)); diff --git a/src/settings.js b/src/settings.js index 8220701e..641f5412 100644 --- a/src/settings.js +++ b/src/settings.js @@ -18,6 +18,7 @@ // @flow import path from 'path'; +import WebhookFileRepository from './lib/repository/WebhookFileRepository'; export default { baseUrl: 'http://localhost', @@ -29,4 +30,7 @@ export default { maxHooksPerDevice: 10, maxHooksPerUser: 20, userDataDir: path.join(__dirname, 'users'), + webhookRepository: new WebhookFileRepository( + path.join(__dirname, 'webhooks'), + ), }; diff --git a/src/types.js b/src/types.js new file mode 100644 index 00000000..f9132867 --- /dev/null +++ b/src/types.js @@ -0,0 +1,14 @@ +export type Webhook = { + deviceID: string, + event: string, + errorResponseTopic: string, + id: string, + json: {[key: string]: Object}, + mydevices: boolean, + productIdOrSlug: ?string, + rejectUnauthorized: boolean, + requestType: string, + responseTemplate: ?string, + responseTopic: string, + url: string, +}; From e5ffd5297e625631fae16cdca39d00d92332e467 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 26 Nov 2016 17:54:29 -0800 Subject: [PATCH 072/504] Minor cleanup --- src/lib/controllers/WebhookController.js | 7 +++---- src/lib/controllers/types.js | 8 -------- src/lib/repository/WebhookFileRepository.js | 3 +++ src/types.js | 7 +++++++ 4 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 src/lib/controllers/types.js diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index 60dc1846..138e091e 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -1,5 +1,4 @@ -import type {Webhook} from '../../types'; -import type {WebhookRepositoryType} from './types'; +import type {Repository, Webhook} from '../../types'; import settings from '../../settings'; import Controller from './Controller'; @@ -7,9 +6,9 @@ import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; class WebhookController extends Controller { - _webhookRepository: RepositoryType; + _webhookRepository: Repository; - constructor(webhookRepository: RepositoryType) { + constructor(webhookRepository: Repository) { super(); this._webhookRepository = webhookRepository; diff --git a/src/lib/controllers/types.js b/src/lib/controllers/types.js deleted file mode 100644 index ca6cf58c..00000000 --- a/src/lib/controllers/types.js +++ /dev/null @@ -1,8 +0,0 @@ -import type {Webhook} from '../../types'; - -export type RepositoryType = { - create(model: TModel) => TModel, - delete(id: string) => void, - getAll() => Array, - getById(id: string) => TModel, -}; diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index a75a5289..d47aa495 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -8,6 +8,9 @@ class WebhookFileRepository extends FileRepositoryBase { const modelToSave = { ...model, created_at: new Date(), + // TODO: Add another repository for fetching users. This should be + // injected on every request so we can easily get the current user + created_by: null, // user id id: uuid(), }; diff --git a/src/types.js b/src/types.js index f9132867..e5d6aac7 100644 --- a/src/types.js +++ b/src/types.js @@ -12,3 +12,10 @@ export type Webhook = { responseTopic: string, url: string, }; + +export type Repository = { + create(model: TModel) => TModel, + delete(id: string) => void, + getAll() => Array, + getById(id: string) => TModel, +}; From 84c7af07a4d60c3a5aa884950b9770fd16761957 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 26 Nov 2016 17:58:05 -0800 Subject: [PATCH 073/504] Use protected functions instead of public functions on the file repository base. --- src/lib/repository/FileRepositoryBase.js | 8 ++++---- src/lib/repository/WebhookFileRepository.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/repository/FileRepositoryBase.js b/src/lib/repository/FileRepositoryBase.js index 7b10ca01..52eeace7 100644 --- a/src/lib/repository/FileRepositoryBase.js +++ b/src/lib/repository/FileRepositoryBase.js @@ -11,7 +11,7 @@ class FileRepositoryBase { } } - createFile(fileName: string, data: Object): void { + __createFile(fileName: string, data: Object): void { console.log(fileName); const filePath = path.join(this._path, fileName); if (fs.existsSync(filePath)) { @@ -21,7 +21,7 @@ class FileRepositoryBase { fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); } - deleteFile(fileName: string): void { + __deleteFile(fileName: string): void { const filePath = path.join(this._path, fileName); if (!fs.existsSync(filePath)) { return; @@ -30,13 +30,13 @@ class FileRepositoryBase { fs.unlink(filePath); } - getAllData(): Array { + __getAllData(): Array { return fs.readdirSync(this._path).map( fileName => JSON.parse(fs.readFileSync(path.join(this._path, fileName))), ); } - getFile(fileName): TModel { + __getFile(fileName): TModel { const filePath = path.join(this._path, fileName); return JSON.parse(fs.readFileSync(filePath)); } diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index d47aa495..1ba5f055 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -14,20 +14,20 @@ class WebhookFileRepository extends FileRepositoryBase { id: uuid(), }; - this.createFile(modelToSave.id + '.json', modelToSave); + this.__createFile(modelToSave.id + '.json', modelToSave); return modelToSave; } delete(id: string): void { - this.deleteFile(id + '.json'); + this.__deleteFile(id + '.json'); } getAll(): Array { - return this.getAllData(); + return this.__getAllData(); } getById(id: string): Webhook { - return this.getFile(id + '.json'); + return this.__getFile(id + '.json'); } } From a5293f014b9eadf10a67ab52cf55100cfa6ea96c Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 26 Nov 2016 21:19:38 -0800 Subject: [PATCH 074/504] Working on migrating all the shitty server code + events. --- src/lib/DeviceConnection.js | 17 +++++++ src/lib/controllers/EventsController.js | 5 ++ src/lib/repository/DeviceFileRepository.js | 46 +++++++++++++++++++ .../{FileRepositoryBase.js => FileManager.js} | 25 ++++++---- src/lib/repository/WebhookFileRepository.js | 18 +++++--- src/lib/webhooks/WebhookFileRepository.js | 0 src/lib/webhooks/WebhookUtils.js | 24 ---------- src/settings.js | 4 +- src/types.js | 12 ++++- 9 files changed, 109 insertions(+), 42 deletions(-) create mode 100644 src/lib/DeviceConnection.js create mode 100644 src/lib/controllers/EventsController.js create mode 100644 src/lib/repository/DeviceFileRepository.js rename src/lib/repository/{FileRepositoryBase.js => FileManager.js} (54%) delete mode 100644 src/lib/webhooks/WebhookFileRepository.js delete mode 100644 src/lib/webhooks/WebhookUtils.js diff --git a/src/lib/DeviceConnection.js b/src/lib/DeviceConnection.js new file mode 100644 index 00000000..8f3a3d13 --- /dev/null +++ b/src/lib/DeviceConnection.js @@ -0,0 +1,17 @@ +import {EventEmitter} from 'events'; + +let CONNECTION_COUNTER = 0; + +class DeviceConnection { + _socketId: number; + + constructor() { + this._socketId = CONNECTION_COUNTER++; + } + + getDeviceById(coreId: string): Device { + + } +} + +export default DeviceConnection; diff --git a/src/lib/controllers/EventsController.js b/src/lib/controllers/EventsController.js new file mode 100644 index 00000000..8d9cce1f --- /dev/null +++ b/src/lib/controllers/EventsController.js @@ -0,0 +1,5 @@ +class EventsController extends Controller { + +} + +export default EventsController; diff --git a/src/lib/repository/DeviceFileRepository.js b/src/lib/repository/DeviceFileRepository.js new file mode 100644 index 00000000..94c0b673 --- /dev/null +++ b/src/lib/repository/DeviceFileRepository.js @@ -0,0 +1,46 @@ +import type {Device} from '../../types'; + +import FileManager from './FileManager'; +import uuid from '../uuid'; + +class DeviceFileRepository { + _fileManager: FileManager; + + constructor(path: string) { + this._fileManager = new FileManager(path); + } + + create(id: string, model: Device): Device { + const modelToSave = { + ...model, + timestamp: new Date(), + }; + + this._fileManager.createFile(id + '.json', modelToSave); + return modelToSave; + } + + update(id: string, model: Device): Device { + const modelToSave = { + ...model, + timestamp: new Date(), + }; + + this._fileManager.writeFile(id + '.json', model); + return modelToSave; + } + + delete(id: string): void { + this._fileManager.deleteFile(id + '.json'); + } + + getAll(): Array { + return this._fileManager.getAllData(); + } + + getById(id: string): Device { + return this._fileManager.getFile(id + '.json'); + } +} + +export default WebhookFileRepository; diff --git a/src/lib/repository/FileRepositoryBase.js b/src/lib/repository/FileManager.js similarity index 54% rename from src/lib/repository/FileRepositoryBase.js rename to src/lib/repository/FileManager.js index 52eeace7..487ccb63 100644 --- a/src/lib/repository/FileRepositoryBase.js +++ b/src/lib/repository/FileManager.js @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; -class FileRepositoryBase { +class FileManager { _path: string; constructor(path) { @@ -11,17 +11,15 @@ class FileRepositoryBase { } } - __createFile(fileName: string, data: Object): void { - console.log(fileName); - const filePath = path.join(this._path, fileName); - if (fs.existsSync(filePath)) { + createFile(fileName: string, data: TModel): void { + if (fs.existsSync(path.join(this._path, fileName))) { return; } - fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); + this.writeFile(fileName, data); } - __deleteFile(fileName: string): void { + deleteFile(fileName: string): void { const filePath = path.join(this._path, fileName); if (!fs.existsSync(filePath)) { return; @@ -30,16 +28,23 @@ class FileRepositoryBase { fs.unlink(filePath); } - __getAllData(): Array { + getAllData(): Array { return fs.readdirSync(this._path).map( fileName => JSON.parse(fs.readFileSync(path.join(this._path, fileName))), ); } - __getFile(fileName): TModel { + getFile(fileName): TModel { const filePath = path.join(this._path, fileName); return JSON.parse(fs.readFileSync(filePath)); } + + writeFile(fileName: string, data: TModel): void { + fs.writeFileSync( + path.join(this._path, fileName), + JSON.stringify(data, null, 2), + ); + } } -export default FileRepositoryBase; +export default FileManager; diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 1ba5f055..941eeec2 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,9 +1,15 @@ import type {Webhook} from '../../types'; -import FileRepositoryBase from './FileRepositoryBase'; +import FileManager from './FileManager'; import uuid from '../uuid'; -class WebhookFileRepository extends FileRepositoryBase { +class WebhookFileRepository { + _fileManager: FileManager; + + constructor(path: string) { + this._fileManager = new FileManager(path); + } + create(model: Webhook): Webhook { const modelToSave = { ...model, @@ -14,20 +20,20 @@ class WebhookFileRepository extends FileRepositoryBase { id: uuid(), }; - this.__createFile(modelToSave.id + '.json', modelToSave); + this._fileManager.createFile(modelToSave.id + '.json', modelToSave); return modelToSave; } delete(id: string): void { - this.__deleteFile(id + '.json'); + this._fileManager.deleteFile(id + '.json'); } getAll(): Array { - return this.__getAllData(); + return this._fileManager.getAllData(); } getById(id: string): Webhook { - return this.__getFile(id + '.json'); + return this._fileManager.getFile(id + '.json'); } } diff --git a/src/lib/webhooks/WebhookFileRepository.js b/src/lib/webhooks/WebhookFileRepository.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/lib/webhooks/WebhookUtils.js b/src/lib/webhooks/WebhookUtils.js deleted file mode 100644 index 2dbff6b8..00000000 --- a/src/lib/webhooks/WebhookUtils.js +++ /dev/null @@ -1,24 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -const settings = require('../../settings.js'); - -class WebhookUtils { -} - -module.exports = new WebhookUtils(); diff --git a/src/settings.js b/src/settings.js index 641f5412..0074b8b3 100644 --- a/src/settings.js +++ b/src/settings.js @@ -14,8 +14,10 @@ * along with this program. If not, see . * * You can download the source here: https://github.com/spark/spark-server +* +* @flow +* */ -// @flow import path from 'path'; import WebhookFileRepository from './lib/repository/WebhookFileRepository'; diff --git a/src/types.js b/src/types.js index e5d6aac7..3bfea41f 100644 --- a/src/types.js +++ b/src/types.js @@ -13,9 +13,19 @@ export type Webhook = { url: string, }; +export type Device = { + deviceId: string, + ip: string, + particleProductId: number, + productFirmwareVersion: number, + registrar: string, + timestamp: Date, +}; + export type Repository = { - create(model: TModel) => TModel, + create(id: string, model: TModel) => TModel, delete(id: string) => void, getAll() => Array, getById(id: string) => TModel, + update(id: string, model: TModel) => TModel, }; From db6e8a68ec7eb89bbc4424de21a5df7cbd1242b9 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sun, 27 Nov 2016 20:25:06 +0200 Subject: [PATCH 075/504] add moment and express libdef --- flow-typed/npm/express_v4.x.x.js | 185 ++++++++++++++++++++++++ flow-typed/npm/moment_v2.x.x.js | 233 +++++++++++++++++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 flow-typed/npm/express_v4.x.x.js create mode 100644 flow-typed/npm/moment_v2.x.x.js diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js new file mode 100644 index 00000000..23ccfb3c --- /dev/null +++ b/flow-typed/npm/express_v4.x.x.js @@ -0,0 +1,185 @@ +// flow-typed signature: 54f4a1579bf43031e95db3d78777be00 +// flow-typed version: aac2114da6/express_v4.x.x/flow_>=v0.25.x + +// @flow +import type { Server } from 'http'; + +declare type express$RouterOptions = { + caseSensitive?: boolean, + mergeParams?: boolean, + strict?: boolean +}; + +declare class express$RequestResponseBase { + app: express$Application; + get(field: string): string | void; +} + +declare class express$Request extends http$IncomingMessage mixins express$RequestResponseBase { + baseUrl: string; + body: mixed; + cookies: {[cookie: string]: string}; + fresh: boolean; + hostname: boolean; + ip: string; + ips: Array; + method: string; + originalUrl: string; + params: {[param: string]: string}; + path: string; + protocol: 'https' | 'http'; + query: {[name: string]: string}; + route: string; + secure: boolean; + signedCookies: {[signedCookie: string]: string}; + stale: boolean; + subdomains: Array; + xhr: boolean; + accepts(types: string): string | false; + acceptsCharsets(...charsets: Array): string | false; + acceptsEncodings(...encoding: Array): string | false; + acceptsLanguages(...lang: Array): string | false; + header(field: string): string | void; + is(type: string): boolean; + param(name: string, defaultValue?: string): string | void; +} + +declare type express$CookieOptions = { + domain?: string, + encode?: (value: string) => string, + expires?: Date, + httpOnly?: boolean, + maxAge?: string, + path?: string, + secure?: boolean, + signed?: boolean +}; + +declare type express$RenderCallback = (err: Error | null, html?: string) => mixed; + +declare type express$SendFileOptions = { + maxAge?: number, + root?: string, + lastModified?: boolean, + headers?: {[name: string]: string}, + dotfiles?: 'allow' | 'deny' | 'ignore' +}; + +declare class express$Response extends http$ClientRequest mixins express$RequestResponseBase { + headersSent: boolean; + locals: {[name: string]: mixed}; + append(field: string, value?: string): this; + attachment(filename?: string): this; + cookie(name: string, value: string, options?: express$CookieOptions): this; + clearCookie(name: string, options?: express$CookieOptions): this; + download(path: string, filename?: string, callback?: (err?: ?Error) => void): this; + format(typesObject: {[type: string]: Function}): this; + json(body?: mixed): this; + jsonp(body?: mixed): this; + links(links: {[name: string]: string}): this; + location(path: string): this; + redirect(url: string, ...args: Array): this; + redirect(status: number, url: string, ...args: Array): this; + render(view: string, locals?: {[name: string]: mixed}, callback?: express$RenderCallback): this; + send(body?: mixed): this; + sendFile(path: string, options?: express$SendFileOptions, callback?: (err?: ?Error) => mixed): this; + sendStatus(statusCode: number): this; + set(field: string, value?: string): this; + status(statusCode: number): this; + type(type: string): this; + vary(field: string): this; +} + +declare type express$NextFunction = (err?: ?Error) => mixed; +declare type express$Middleware = + ((req: express$Request, res: express$Response, next: express$NextFunction) => mixed) | + ((error: ?Error, req: express$Request, res: express$Response, next: express$NextFunction) => mixed); +declare interface express$RouteMethodType { + (middleware: express$Middleware): T; + (...middleware: Array): T; + (path: string|RegExp|string[], ...middleware: Array): T; +} +declare interface express$RouterMethodType { + (middleware: express$Middleware): T; + (...middleware: Array): T; + (path: string|RegExp|string[], ...middleware: Array): T; + (path: string, router: express$Router): T; +} +declare class express$Route { + all: express$RouteMethodType; + get: express$RouteMethodType; + post: express$RouteMethodType; + put: express$RouteMethodType; + head: express$RouteMethodType; + delete: express$RouteMethodType; + options: express$RouteMethodType; + trace: express$RouteMethodType; + copy: express$RouteMethodType; + lock: express$RouteMethodType; + mkcol: express$RouteMethodType; + move: express$RouteMethodType; + purge: express$RouteMethodType; + propfind: express$RouteMethodType; + proppatch: express$RouteMethodType; + unlock: express$RouteMethodType; + report: express$RouteMethodType; + mkactivity: express$RouteMethodType; + checkout: express$RouteMethodType; + merge: express$RouteMethodType; + + // @TODO Missing 'm-search' but get flow illegal name error. + + notify: express$RouteMethodType; + subscribe: express$RouteMethodType; + unsubscribe: express$RouteMethodType; + patch: express$RouteMethodType; + search: express$RouteMethodType; + connect: express$RouteMethodType; +} + +declare class express$Router extends express$Route { + constructor(options?: express$RouterOptions): void; + use: express$RouterMethodType; + route(path: string): express$Route; + static (): express$Router; +} + +declare class express$Application extends express$Router mixins events$EventEmitter { + constructor(): void; + locals: {[name: string]: mixed}; + mountpath: string; + listen(port: number, hostname?: string, backlog?: number, callback?: (err?: ?Error) => mixed): Server; + listen(port: number, hostname?: string, callback?: (err?: ?Error) => mixed): Server; + listen(port: number, callback?: (err?: ?Error) => mixed): Server; + listen(path: string, callback?: (err?: ?Error) => mixed): Server; + listen(handle: Object, callback?: (err?: ?Error) => mixed): Server; + disable(name: string): void; + disabled(name: string): boolean; + enable(name: string): void; + enabled(name: string): boolean; + engine(name: string, callback: Function): void; + /** + * Mixed will not be taken as a value option. Issue around using the GET http method name and the get for settings. + */ + // get(name: string): mixed; + set(name: string, value: mixed): mixed; + render(name: string, optionsOrFunction: {[name: string]: mixed}, callback: express$RenderCallback): void; +} + +declare module 'express' { + declare function serveStatic(root: string, options?: Object): express$Middleware; + + declare type RouterOptions = express$RouterOptions; + declare type CookieOptions = express$CookieOptions; + declare type Middleware = express$Middleware; + declare type NextFunction = express$NextFunction; + declare type $Response = express$Response; + declare type $Request = express$Request; + declare type $Application = express$Application; + + declare module.exports: { + (): express$Application, // If you try to call like a function, it will use this signature + static: serveStatic, // `static` property on the function + Router: typeof express$Router, // `Router` property on the function + }; +} diff --git a/flow-typed/npm/moment_v2.x.x.js b/flow-typed/npm/moment_v2.x.x.js new file mode 100644 index 00000000..86751112 --- /dev/null +++ b/flow-typed/npm/moment_v2.x.x.js @@ -0,0 +1,233 @@ +// flow-typed signature: 8b6a390c8f84a79f68a5ba41f3665db0 +// flow-typed version: 25c06ef4a8/moment_v2.x.x/flow_>=v0.28.x + +type moment$MomentOptions = { + y?: number|string, + year?: number|string, + years?: number|string, + M?: number|string, + month?: number|string, + months?: number|string, + d?: number|string, + day?: number|string, + days?: number|string, + date?: number|string, + h?: number|string, + hour?: number|string, + hours?: number|string, + m?: number|string, + minute?: number|string, + minutes?: number|string, + s?: number|string, + second?: number|string, + seconds?: number|string, + ms?: number|string, + millisecond?: number|string, + milliseconds?: number|string, +}; + +type moment$MomentObject = { + years: number, + months: number, + date: number, + hours: number, + minutes: number, + seconds: number, + milliseconds: number, +}; + +type moment$MomentCreationData = { + input: string, + format: string, + locale: Object, + isUTC: bool, + strict: bool, +}; + +type moment$CalendarFormats = { + sameDay?: string, + nextDay?: string, + nextWeek?: string, + lastDay?: string, + lastWeek?: string, + sameElse?: string, +}; + +declare class moment$LocaleData { + months(moment: moment$Moment): string; + monthsShort(moment: moment$Moment): string; + monthsParse(month: string): number; + weekdays(moment: moment$Moment): string; + weekdaysShort(moment: moment$Moment): string; + weekdaysMin(moment: moment$Moment): string; + weekdaysParse(weekDay: string): number; + longDateFormat(dateFormat: string): string; + isPM(date: string): bool; + meridiem(hours: number, minutes: number, isLower: bool): string; + calendar(key: 'sameDay'|'nextDay'|'lastDay'|'nextWeek'|'prevWeek'|'sameElse', moment: moment$Moment): string; + relativeTime(number: number, withoutSuffix: bool, key: 's'|'m'|'mm'|'h'|'hh'|'d'|'dd'|'M'|'MM'|'y'|'yy', isFuture: bool): string; + pastFuture(diff: any, relTime: string): string; + ordinal(number: number): string; + preparse(str: string): any; + postformat(str: string): any; + week(moment: moment$Moment): string; + invalidDate(): string; + firstDayOfWeek(): number; + firstDayOfYear(): number; +} +declare class moment$MomentDuration { + humanize(suffix?: bool): string; + milliseconds(): number; + asMilliseconds(): number; + seconds(): number; + asSeconds(): number; + minutes(): number; + asMinutes(): number; + hours(): number; + asHours(): number; + days(): number; + asDays(): number; + months(): number; + asMonths(): number; + years(): number; + asYears(): number; + add(value: number|moment$MomentDuration|Object, unit?: string): this; + subtract(value: number|moment$MomentDuration|Object, unit?: string): this; + as(unit: string): number; + get(unit: string): number; + toJSON(): string; +} +declare class moment$Moment { + static ISO_8601: string; + static (string?: string, format?: string|Array, locale?: string, strict?: bool): moment$Moment; + static (initDate: ?Object|number|Date|Array|moment$Moment|string): moment$Moment; + static unix(seconds: number): moment$Moment; + static utc(): moment$Moment; + static utc(number: number|Array): moment$Moment; + static utc(str: string, str2?: string|Array, str3?: string): moment$Moment; + static utc(moment: moment$Moment): moment$Moment; + static utc(date: Date): moment$Moment; + static parseZone(rawDate: string): moment$Moment; + isValid(): bool; + invalidAt(): 0|1|2|3|4|5|6; + creationData(): moment$MomentCreationData; + millisecond(number: number): this; + milliseconds(number: number): this; + millisecond(): number; + milliseconds(): number; + second(number: number): this; + seconds(number: number): this; + second(): number; + seconds(): number; + minute(number: number): this; + minutes(number: number): this; + minute(): number; + minutes(): number; + hour(number: number): this; + hours(number: number): this; + hour(): number; + hours(): number; + date(number: number): this; + dates(number: number): this; + date(): number; + dates(): number; + day(day: number|string): this; + days(day: number|string): this; + day(): number; + days(): number; + weekday(number: number): this; + weekday(): number; + isoWeekday(number: number): this; + isoWeekday(): number; + dayOfYear(number: number): this; + dayOfYear(): number; + week(number: number): this; + weeks(number: number): this; + week(): number; + weeks(): number; + isoWeek(number: number): this; + isoWeeks(number: number): this; + isoWeek(): number; + isoWeeks(): number; + month(number: number): this; + months(number: number): this; + month(): number; + months(): number; + quarter(number: number): this; + quarter(): number; + year(number: number): this; + years(number: number): this; + year(): number; + years(): number; + weekYear(number: number): this; + weekYear(): number; + isoWeekYear(number: number): this; + isoWeekYear(): number; + weeksInYear(): number; + isoWeeksInYear(): number; + get(string: string): number; + set(unit: string, value: number): this; + set(options: { [unit: string]: number }): this; + static max(...dates: Array): moment$Moment; + static max(dates: Array): moment$Moment; + static min(...dates: Array): moment$Moment; + static min(dates: Array): moment$Moment; + add(value: number|moment$MomentDuration|moment$Moment|Object, unit?: string): this; + subtract(value: number|moment$MomentDuration|moment$Moment|string, unit?: string): this; + startOf(unit: string): this; + endOf(unit: string): this; + local(): this; + utc(): this; + utcOffset(offset: number|string): void; + utcOffset(): number|string; + format(format?: string): string; + fromNow(removeSuffix?: bool): string; + from(value: moment$Moment|string|number|Date|Array, removePrefix?: bool): string; + toNow(removePrefix?: bool): string; + to(value: moment$Moment|string|number|Date|Array, removePrefix?: bool): string; + calendar(refTime?: any, formats?: moment$CalendarFormats): string; + diff(date: moment$Moment|string|number|Date|Array, format?: string, floating?: bool): number; + valueOf(): number; + unix(): number; + daysInMonth(): number; + toDate(): Date; + toArray(): Array; + toJSON(): string; + toISOString(): string; + toObject(): moment$MomentObject; + isBefore(date: moment$Moment|string|number|Date|Array): bool; + isSame(date: moment$Moment|string|number|Date|Array): bool; + isAfter(date: moment$Moment|string|number|Date|Array): bool; + isSameOrBefore(date: moment$Moment|string|number|Date|Array): bool; + isSameOrAfter(date: moment$Moment|string|number|Date|Array): bool; + isBetween(date: moment$Moment|string|number|Date|Array): bool; + isDST(): bool; + isDSTShifted(): bool; + isLeapYear(): bool; + clone(): moment$Moment; + static isMoment(obj: any): bool; + static isDatE(obj: any): bool; + static locale(locale: string, localeData?: Object): string; + static locale(locales: Array): string; + locale(locale: string, customization?: Object|null): moment$Moment; + locale(): string; + static months(): Array; + static monthsShort(): Array; + static weekdays(): Array; + static weekdaysShort(): Array; + static weekdaysMin(): Array; + static months(): string; + static monthsShort(): string; + static weekdays(): string; + static weekdaysShort(): string; + static weekdaysMin(): string; + static localeData(key?: string): moment$LocaleData; + static duration(value: number|Object|string, unit?: string): moment$MomentDuration; + static isDuration(obj: any): bool; + static normalizeUnits(unit: string): string; + static invalid(object: any): moment$Moment; +} + +declare module 'moment' { + declare module.exports: Class; +} From adeded0c4de4b524f942b5218be62256ceb0709f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sun, 27 Nov 2016 20:43:18 +0200 Subject: [PATCH 076/504] add ignore Decorators option to flowConfig --- .flowconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.flowconfig b/.flowconfig index 4a58bdcd..ae2d22fd 100644 --- a/.flowconfig +++ b/.flowconfig @@ -5,3 +5,4 @@ [libs] [options] +esproposal.decorators=ignore \ No newline at end of file From 4d43d4911b36a6c9d4d3720766689c346fe387a1 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sun, 27 Nov 2016 20:44:10 +0200 Subject: [PATCH 077/504] remove semicolons from decorators --- src/lib/controllers/WebhookController.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index 138e091e..85bf8ca4 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -1,3 +1,5 @@ +// @flow + import type {Repository, Webhook} from '../../types'; import settings from '../../settings'; @@ -14,20 +16,20 @@ class WebhookController extends Controller { this._webhookRepository = webhookRepository; } - @httpVerb('get'); - @route('/v1/webhooks'); + @httpVerb('get') + @route('/v1/webhooks') get() { return this.ok(this._webhookRepository.getAll()); } - @httpVerb('get'); - @route('/v1/webhooks/:webhookId'); + @httpVerb('get') + @route('/v1/webhooks/:webhookId') getByWebhookId({webhookId}: {webhookId: string}) { return this.ok(this._webhookRepository.getById(webhookId)); } - @httpVerb('post'); - @route('/v1/webhooks'); + @httpVerb('post') + @route('/v1/webhooks') post(model: Webhook) { const newWebhook = this._webhookRepository.create(model); return this.ok({ @@ -40,8 +42,8 @@ class WebhookController extends Controller { }); } - @httpVerb('delete'); - @route('/v1/webhooks/:webhookId'); + @httpVerb('delete') + @route('/v1/webhooks/:webhookId') delete({webhookId}: {webhookId: string}) { this._webhookRepository.delete(webhookId); return this.ok(); From ad61b31e770b8fddf86082977be2dab84076f00c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sun, 27 Nov 2016 20:52:59 +0200 Subject: [PATCH 078/504] fix routeConfig path in main.js --- src/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index 91482fd3..1add654f 100644 --- a/src/main.js +++ b/src/main.js @@ -34,7 +34,7 @@ import api from './views/api_v1.js'; import eventsV1 from './views/EventViews001.js'; // Routing -import routeConfig from './lib/routeConfig'; +import routeConfig from './lib/RouteConfig'; import WebhookController from './lib/controllers/WebhookController'; const NODE_PORT = process.env.NODE_PORT || 8080; From d7f1785cf2fb352a258ac94ab5f50b467fd74ac7 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sun, 27 Nov 2016 20:55:48 +0200 Subject: [PATCH 079/504] fix formatting --- src/lib/RouteConfig.js | 2 +- src/lib/controllers/WebhookController.js | 6 +++--- src/lib/decorators/httpVerb.js | 2 +- src/lib/repository/DeviceFileRepository.js | 2 +- src/lib/repository/WebhookFileRepository.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 2abb7151..819d6aa8 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -1,4 +1,4 @@ -import type {$Application} from 'express'; +import type { $Application } from 'express'; import type Controller from './controllers/Controller' diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index 85bf8ca4..a8200af7 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -1,6 +1,6 @@ // @flow -import type {Repository, Webhook} from '../../types'; +import type { Repository, Webhook } from '../../types'; import settings from '../../settings'; import Controller from './Controller'; @@ -24,7 +24,7 @@ class WebhookController extends Controller { @httpVerb('get') @route('/v1/webhooks/:webhookId') - getByWebhookId({webhookId}: {webhookId: string}) { + getByWebhookId({ webhookId }: { webhookId: string }) { return this.ok(this._webhookRepository.getById(webhookId)); } @@ -44,7 +44,7 @@ class WebhookController extends Controller { @httpVerb('delete') @route('/v1/webhooks/:webhookId') - delete({webhookId}: {webhookId: string}) { + delete({ webhookId }: { webhookId: string }) { this._webhookRepository.delete(webhookId); return this.ok(); } diff --git a/src/lib/decorators/httpVerb.js b/src/lib/decorators/httpVerb.js index 642f12e9..ef16cf62 100644 --- a/src/lib/decorators/httpVerb.js +++ b/src/lib/decorators/httpVerb.js @@ -1,4 +1,4 @@ -import type {Decorator, HttpVerb} from './types'; +import type { Decorator, HttpVerb } from './types'; export default ( httpVerb: HttpVerb, diff --git a/src/lib/repository/DeviceFileRepository.js b/src/lib/repository/DeviceFileRepository.js index 94c0b673..822e527c 100644 --- a/src/lib/repository/DeviceFileRepository.js +++ b/src/lib/repository/DeviceFileRepository.js @@ -1,4 +1,4 @@ -import type {Device} from '../../types'; +import type { Device } from '../../types'; import FileManager from './FileManager'; import uuid from '../uuid'; diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 941eeec2..1c9be267 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,4 +1,4 @@ -import type {Webhook} from '../../types'; +import type { Webhook } from '../../types'; import FileManager from './FileManager'; import uuid from '../uuid'; From b523f08804a09ac2af867849d8d5748916c63bec Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sun, 27 Nov 2016 20:56:27 +0200 Subject: [PATCH 080/504] fix repo type formatting --- src/types.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types.js b/src/types.js index 3bfea41f..c6e997cf 100644 --- a/src/types.js +++ b/src/types.js @@ -23,9 +23,9 @@ export type Device = { }; export type Repository = { - create(id: string, model: TModel) => TModel, - delete(id: string) => void, - getAll() => Array, - getById(id: string) => TModel, - update(id: string, model: TModel) => TModel, + create: (id: string, model: TModel) => TModel, + delete: (id: string) => void, + getAll: () => Array, + getById: (id: string) => TModel, + update: (id: string, model: TModel) => TModel, }; From fd7de566a3ecd5a34f197566befb2bbc26932517 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sun, 27 Nov 2016 20:59:20 +0200 Subject: [PATCH 081/504] add blank endline in flowconfig --- .flowconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flowconfig b/.flowconfig index ae2d22fd..32087dfc 100644 --- a/.flowconfig +++ b/.flowconfig @@ -5,4 +5,4 @@ [libs] [options] -esproposal.decorators=ignore \ No newline at end of file +esproposal.decorators=ignore From b8ac5a3a658d3365c977c27d859ecd78f624cfa7 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 27 Nov 2016 11:13:01 -0800 Subject: [PATCH 082/504] Moving repositories to spark-protocol --- src/lib/repository/DeviceFileRepository.js | 46 ------------------- src/lib/repository/FileManager.js | 50 --------------------- src/lib/repository/WebhookFileRepository.js | 2 +- src/lib/uuid.js | 8 ---- src/main.js | 3 ++ src/types.js | 10 ++--- 6 files changed, 9 insertions(+), 110 deletions(-) delete mode 100644 src/lib/repository/DeviceFileRepository.js delete mode 100644 src/lib/repository/FileManager.js delete mode 100644 src/lib/uuid.js diff --git a/src/lib/repository/DeviceFileRepository.js b/src/lib/repository/DeviceFileRepository.js deleted file mode 100644 index 94c0b673..00000000 --- a/src/lib/repository/DeviceFileRepository.js +++ /dev/null @@ -1,46 +0,0 @@ -import type {Device} from '../../types'; - -import FileManager from './FileManager'; -import uuid from '../uuid'; - -class DeviceFileRepository { - _fileManager: FileManager; - - constructor(path: string) { - this._fileManager = new FileManager(path); - } - - create(id: string, model: Device): Device { - const modelToSave = { - ...model, - timestamp: new Date(), - }; - - this._fileManager.createFile(id + '.json', modelToSave); - return modelToSave; - } - - update(id: string, model: Device): Device { - const modelToSave = { - ...model, - timestamp: new Date(), - }; - - this._fileManager.writeFile(id + '.json', model); - return modelToSave; - } - - delete(id: string): void { - this._fileManager.deleteFile(id + '.json'); - } - - getAll(): Array { - return this._fileManager.getAllData(); - } - - getById(id: string): Device { - return this._fileManager.getFile(id + '.json'); - } -} - -export default WebhookFileRepository; diff --git a/src/lib/repository/FileManager.js b/src/lib/repository/FileManager.js deleted file mode 100644 index 487ccb63..00000000 --- a/src/lib/repository/FileManager.js +++ /dev/null @@ -1,50 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -class FileManager { - _path: string; - - constructor(path) { - this._path = path; - if (!fs.existsSync(path)) { - fs.mkdirSync(path); - } - } - - createFile(fileName: string, data: TModel): void { - if (fs.existsSync(path.join(this._path, fileName))) { - return; - } - - this.writeFile(fileName, data); - } - - deleteFile(fileName: string): void { - const filePath = path.join(this._path, fileName); - if (!fs.existsSync(filePath)) { - return; - } - - fs.unlink(filePath); - } - - getAllData(): Array { - return fs.readdirSync(this._path).map( - fileName => JSON.parse(fs.readFileSync(path.join(this._path, fileName))), - ); - } - - getFile(fileName): TModel { - const filePath = path.join(this._path, fileName); - return JSON.parse(fs.readFileSync(filePath)); - } - - writeFile(fileName: string, data: TModel): void { - fs.writeFileSync( - path.join(this._path, fileName), - JSON.stringify(data, null, 2), - ); - } -} - -export default FileManager; diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 941eeec2..c71f7d3b 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,6 +1,6 @@ import type {Webhook} from '../../types'; -import FileManager from './FileManager'; +import {FileManager} from 'spark-protocol'; import uuid from '../uuid'; class WebhookFileRepository { diff --git a/src/lib/uuid.js b/src/lib/uuid.js deleted file mode 100644 index 7e1fcb3d..00000000 --- a/src/lib/uuid.js +++ /dev/null @@ -1,8 +0,0 @@ -const s4 = (): string => - Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - -export default (): string => { - return (s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4()).toLowerCase(); -} diff --git a/src/main.js b/src/main.js index 91482fd3..c79e4b60 100644 --- a/src/main.js +++ b/src/main.js @@ -14,6 +14,9 @@ * along with this program. If not, see . * * You can download the source here: https://github.com/spark/spark-server +* +* @flow +* */ import bodyParser from 'body-parser'; diff --git a/src/types.js b/src/types.js index 3bfea41f..c6e997cf 100644 --- a/src/types.js +++ b/src/types.js @@ -23,9 +23,9 @@ export type Device = { }; export type Repository = { - create(id: string, model: TModel) => TModel, - delete(id: string) => void, - getAll() => Array, - getById(id: string) => TModel, - update(id: string, model: TModel) => TModel, + create: (id: string, model: TModel) => TModel, + delete: (id: string) => void, + getAll: () => Array, + getById: (id: string) => TModel, + update: (id: string, model: TModel) => TModel, }; From 5d688cfafdfd08f8d6a64a61369fb52c6718b8d8 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 00:51:20 +0200 Subject: [PATCH 083/504] add eslint --- .eslintignore | 11 +++++++++++ .eslintrc | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++++++ 3 files changed, 71 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..a3346445 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,11 @@ +flow-typed + +# Don't check auto-generated stuff +coverage +build +node_modules + +# Cruft +.DS_Store +npm-debug.log +.idea diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..c7b19c5c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,54 @@ +{ + "extends": "airbnb-base", + "parser": "babel-eslint", + "plugins": [ + "flowtype", + "sorting" + ], + "rules": { + "eol-last": 2, + "flowtype/define-flow-type": 1, + "flowtype/require-parameter-type": 1, + "flowtype/require-valid-file-annotation": [ + 2, + "always" + ], + "flowtype/require-return-type": [ + 1, + "always", + { + "annotateUndefined": "never" + } + ], + "flowtype/space-after-type-colon": [ + 1, + "always" + ], + "flowtype/space-before-type-colon": [ + 1, + "never" + ], + "flowtype/use-flow-type": 1, + "import/imports-first": 0, + "import/newline-after-import": 0, + "import/no-duplicates": 0, + "import/no-extraneous-dependencies": 0, + "import/no-named-as-default": 0, + "import/no-unresolved": 2, + "import/prefer-default-export": 0, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "no-mixed-operators": 0, + "no-console": 1, + "newline-per-chained-call": 0, + "object-curly-spacing": 2, + "semi": 2, + "sorting/sort-object-props": [ + 2, + { + "ignoreCase": true, + "ignoreMethods": false + } + ] + } +} diff --git a/package.json b/package.json index 63026828..dba68b7a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "scripts": { "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", + "lint": "eslint -- .", "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", "start:prod": "npm run build && node ./build/main.js" @@ -46,6 +47,7 @@ }, "devDependencies": { "babel-cli": "^6.18.0", + "babel-eslint": "^7.1.1", "babel-plugin-transform-class-properties": "^6.19.0", "babel-plugin-transform-decorators": "^6.13.0", "babel-plugin-transform-decorators-legacy": "^1.3.4", @@ -56,6 +58,10 @@ "babel-preset-latest": "^6.16.0", "babel-preset-stage-0": "^6.16.0", "babel-preset-stage-1": "^6.16.0", + "eslint": "^3.11.0", + "eslint-config-airbnb-base": "^10.0.1", + "eslint-plugin-flowtype": "^2.28.2", + "eslint-plugin-import": "^2.2.0", "nodemon": "^1.11.0", "rimraf": "^2.5.4" } From c090f944f9cf270353d25bdeebecbbc6c176a7b6 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 01:05:29 +0200 Subject: [PATCH 084/504] annotateMain --- src/main.js | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/main.js b/src/main.js index 1d8af738..dd249916 100644 --- a/src/main.js +++ b/src/main.js @@ -19,9 +19,15 @@ * */ +import type { + $Request, + $Response, + Middleware, + NextFunction, +} from 'express'; + import bodyParser from 'body-parser'; import express from 'express'; -import http from 'http'; import morgan from 'morgan'; import OAuthServer from 'node-oauth2-server'; import { DeviceServer } from 'spark-protocol'; @@ -46,31 +52,35 @@ const NODE_PORT = process.env.NODE_PORT || 8080; global._socket_counter = 1; //TODO: something better here -process.on('uncaughtException', (exception) => { +process.on('uncaughtException', (exception: Error) => { let details = ''; try { details = JSON.stringify(exception); } catch (stringifyException) { - logger.error('Caught exception: ' + stringifyException); + logger.error(`Caught exception: ${stringifyException}`); } - logger.error('Caught exception: ' + exception + details); + logger.error(`Caught exception: ${exception.toString()} ${details}`); }); const app = express(); const oauth = OAuthServer({ allow: { - "delete": ['/v1/access_tokens/([0-9a-f]{40})'], - "get": ['/server/health', '/v1/access_tokens'], - "post": ['/v1/users'], + 'delete': ['/v1/access_tokens/([0-9a-f]{40})'], + 'get': ['/server/health', '/v1/access_tokens'], + 'post': ['/v1/users'], }, - accessTokenLifetime: 7776000, //90 days + accessTokenLifetime: 7776000, // 90 days grants: ['password'], model: new OAuth2ServerModel({}), }); -const setCORSHeaders = (request, response, next) => { - if ('OPTIONS' === request.method) { +const setCORSHeaders: Middleware = ( + request: $Request, + response: $Response, + next: NextFunction, +): mixed => { + if (request.method === 'OPTIONS') { response.set({ 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', @@ -79,10 +89,10 @@ const setCORSHeaders = (request, response, next) => { }); return response.sendStatus(204); } - else { - response.set({'Access-Control-Allow-Origin': '*'}); - next(); - } + else { + response.set({ 'Access-Control-Allow-Origin': '*' }); + next(); + } }; app.use(morgan('combined')); @@ -103,11 +113,17 @@ routeConfig(app, [ new WebhookController(settings.webhookRepository), ]); -app.use((request, response, next) => response.sendStatus(404)); +const noRouteMiddleware: Middleware = ( + request: $Request, + response: $Response, + next: NextFunction, +): mixed => response.sendStatus(404); + +app.use(noRouteMiddleware); console.log("Starting server, listening on " + NODE_PORT); -http.createServer(app).listen(NODE_PORT); +app.listen(NODE_PORT); const deviceServer = new DeviceServer({ coreKeysDir: settings.coreKeysDir, From 6816e57e8c9567f5097fe35cad053fa750afa805 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 27 Nov 2016 18:38:24 -0800 Subject: [PATCH 085/504] Working on passing in parameters in RouteConfig --- src/lib/RouteConfig.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 819d6aa8..05cfcbc2 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -2,6 +2,17 @@ import type { $Application } from 'express'; import type Controller from './controllers/Controller' +const getFunctionArgumentNames = (func): Array => { + // First match everything inside the function argument parens. + const arguments = func.toString().match(/function\s.*?\(([^)]*)\)/)[1]; + + // Split the arguments string into an array comma delimited. + return arguments.split(',').map(argument => { + // Ensure no inline comments are parsed and trim the whitespace. + return argument.replace(/\/\*.*\*\//, '').trim(); + }).filter(argument => !!argument); +}; + export default (app: $Application, controllers: Array): void => { controllers.map(controller => { Object.getOwnPropertyNames( @@ -13,13 +24,18 @@ export default (app: $Application, controllers: Array): void => { return; } + const argumentNames = getFunctionArgumentNames(mappedFunction); app[httpVerb](route, (request, response) => { - const result = mappedFunction.call( + const values = argumentNames + .map(argument => request.params[argument]) + .filter(value => typeof value !== undefined); + console.log(values); + const result = mappedFunction.apply( controller, - { - ...request.params, - ...request.body, - }, + [ + ...values, + request.body, + ] ); response.status(result.status).json(result.data); From 33822c189914cf9f5adc4a9a8bc1cb5ec15069c9 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 27 Nov 2016 19:03:29 -0800 Subject: [PATCH 086/504] Routes now inject parameters into functions instead of merging the request params/body. --- flow-typed/npm/express_v4.x.x.js | 2 +- src/lib/RouteConfig.js | 4 ++-- src/lib/controllers/WebhookController.js | 4 ++-- src/lib/repository/WebhookFileRepository.js | 3 +-- src/main.js | 23 ++++++++++++++++++--- src/settings.js | 11 ++++++++++ 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 23ccfb3c..0c907c2c 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -84,7 +84,7 @@ declare class express$Response extends http$ClientRequest mixins express$Request send(body?: mixed): this; sendFile(path: string, options?: express$SendFileOptions, callback?: (err?: ?Error) => mixed): this; sendStatus(statusCode: number): this; - set(field: string, value?: string): this; + set(field: string | {[field: string]: ?string}, value?: string): this; status(statusCode: number): this; type(type: string): this; vary(field: string): this; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 05cfcbc2..22c431dc 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -4,10 +4,10 @@ import type Controller from './controllers/Controller' const getFunctionArgumentNames = (func): Array => { // First match everything inside the function argument parens. - const arguments = func.toString().match(/function\s.*?\(([^)]*)\)/)[1]; + const args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1]; // Split the arguments string into an array comma delimited. - return arguments.split(',').map(argument => { + return args.split(',').map(argument => { // Ensure no inline comments are parsed and trim the whitespace. return argument.replace(/\/\*.*\*\//, '').trim(); }).filter(argument => !!argument); diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index a8200af7..4199830d 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -24,7 +24,7 @@ class WebhookController extends Controller { @httpVerb('get') @route('/v1/webhooks/:webhookId') - getByWebhookId({ webhookId }: { webhookId: string }) { + getByWebhookId(webhookId: string) { return this.ok(this._webhookRepository.getById(webhookId)); } @@ -44,7 +44,7 @@ class WebhookController extends Controller { @httpVerb('delete') @route('/v1/webhooks/:webhookId') - delete({ webhookId }: { webhookId: string }) { + delete(webhookId: string) { this._webhookRepository.delete(webhookId); return this.ok(); } diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 3439ce79..069e89b1 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,7 +1,6 @@ import type { Webhook } from '../../types'; -import {FileManager} from 'spark-protocol'; -import uuid from '../uuid'; +import {FileManager, uuid} from 'spark-protocol'; class WebhookFileRepository { _fileManager: FileManager; diff --git a/src/main.js b/src/main.js index dd249916..e20be22a 100644 --- a/src/main.js +++ b/src/main.js @@ -29,6 +29,7 @@ import type { import bodyParser from 'body-parser'; import express from 'express'; import morgan from 'morgan'; +import path from 'path'; import OAuthServer from 'node-oauth2-server'; import { DeviceServer } from 'spark-protocol'; import settings from './settings'; @@ -46,6 +47,11 @@ import eventsV1 from './views/EventViews001.js'; import routeConfig from './lib/RouteConfig'; import WebhookController from './lib/controllers/WebhookController'; +import { + DeviceFileRepository, + ServerConfigFileRepository, +} from 'spark-protocol'; + const NODE_PORT = process.env.NODE_PORT || 8080; // TODO wny do we need this? (Anton Puko) @@ -82,10 +88,11 @@ const setCORSHeaders: Middleware = ( ): mixed => { if (request.method === 'OPTIONS') { response.set({ - 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', + 'Access-Control-Allow-Headers': + 'X-Requested-With, Content-Type, Accept, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Origin': '*', - 'Access-Control-Max-Age': 300, + 'Access-Control-Max-Age': '300', }); return response.sendStatus(204); } @@ -126,7 +133,17 @@ console.log("Starting server, listening on " + NODE_PORT); app.listen(NODE_PORT); const deviceServer = new DeviceServer({ - coreKeysDir: settings.coreKeysDir, + deviceAttributeRepository: new DeviceFileRepository( + path.join(__dirname, 'device_keys'), + ), + host: settings.HOST, + port: settings.PORT, + serverConfigRepository: new ServerConfigFileRepository( + settings.serverKeyFile, + ), + serverKeyFile: settings.serverKeyFile, + serverKeyPassFile: settings.serverKeyPassFile, + serverKeyPassEnvVar: settings.serverKeyPassEnvVar, }); // TODO wny do we need next line? (Anton Puko) diff --git a/src/settings.js b/src/settings.js index 0074b8b3..4cc6da9a 100644 --- a/src/settings.js +++ b/src/settings.js @@ -35,4 +35,15 @@ export default { webhookRepository: new WebhookFileRepository( path.join(__dirname, 'webhooks'), ), + + /** + * Your server crypto keys! + */ + cryptoSalt: 'aes-128-cbc', + serverKeyFile: "default_key.pem", + serverKeyPassFile: null, + serverKeyPassEnvVar: null, + + PORT: 5683, + HOST: "localhost", }; From e7b40ad10c46fbe08ae57bd2f876fef0da4089a2 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 14:38:37 +0200 Subject: [PATCH 087/504] add missing eslint plugin --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index dba68b7a..d551fe49 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "eslint-config-airbnb-base": "^10.0.1", "eslint-plugin-flowtype": "^2.28.2", "eslint-plugin-import": "^2.2.0", + "eslint-plugin-sorting": "^0.3.0", "nodemon": "^1.11.0", "rimraf": "^2.5.4" } From 827f4e9de00757ab02042aa6b0df6040e7626c85 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 14:40:01 +0200 Subject: [PATCH 088/504] add no-duplicate-imports 0 rule --- .eslintrc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.eslintrc b/.eslintrc index c7b19c5c..12a30c96 100644 --- a/.eslintrc +++ b/.eslintrc @@ -36,11 +36,12 @@ "import/no-named-as-default": 0, "import/no-unresolved": 2, "import/prefer-default-export": 0, + "newline-per-chained-call": 0, + "no-console": 1, + "no-duplicate-imports": 0, + "no-mixed-operators": 0, "no-underscore-dangle": 0, "no-use-before-define": 0, - "no-mixed-operators": 0, - "no-console": 1, - "newline-per-chained-call": 0, "object-curly-spacing": 2, "semi": 2, "sorting/sort-object-props": [ From 9bd3d9a0d57bd454b8196d5c9cf0485cbe8d10c3 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 14:43:56 +0200 Subject: [PATCH 089/504] import first rule fix --- .eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 12a30c96..b6410f31 100644 --- a/.eslintrc +++ b/.eslintrc @@ -29,7 +29,7 @@ "never" ], "flowtype/use-flow-type": 1, - "import/imports-first": 0, + "import/first": 0, "import/newline-after-import": 0, "import/no-duplicates": 0, "import/no-extraneous-dependencies": 0, From 26e41277ef509320accf557548905bed16f5535a Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 14:50:07 +0200 Subject: [PATCH 090/504] fix flow config --- .flowconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.flowconfig b/.flowconfig index 32087dfc..46902fec 100644 --- a/.flowconfig +++ b/.flowconfig @@ -6,3 +6,4 @@ [options] esproposal.decorators=ignore +esproposal.class_instance_fields=enable \ No newline at end of file From 546a7ac3b04fe9a95617725235a699ad9337f161 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 14:55:15 +0200 Subject: [PATCH 091/504] fix some eslint errors in main.js --- src/main.js | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/main.js b/src/main.js index e20be22a..ca95204b 100644 --- a/src/main.js +++ b/src/main.js @@ -38,10 +38,10 @@ import utilities from './lib/utilities'; import logger from './lib/logger'; import OAuth2ServerModel from './lib/OAuth2ServerModel'; import AccessTokenViews from './lib/AccessTokenViews'; -import UserCreator from './lib/UserCreator.js'; +import UserCreator from './lib/UserCreator'; -import api from './views/api_v1.js'; -import eventsV1 from './views/EventViews001.js'; +import api from './views/api_v1'; +import eventsV1 from './views/EventViews001'; // Routing import routeConfig from './lib/RouteConfig'; @@ -52,12 +52,12 @@ import { ServerConfigFileRepository, } from 'spark-protocol'; -const NODE_PORT = process.env.NODE_PORT || 8080; +const NODE_PORT = process.env.NODE_PORT || 8080; // TODO wny do we need this? (Anton Puko) global._socket_counter = 1; -//TODO: something better here +// TODO: something better here process.on('uncaughtException', (exception: Error) => { let details = ''; try { @@ -71,17 +71,17 @@ process.on('uncaughtException', (exception: Error) => { const app = express(); const oauth = OAuthServer({ + accessTokenLifetime: 7776000, // 90 days allow: { - 'delete': ['/v1/access_tokens/([0-9a-f]{40})'], - 'get': ['/server/health', '/v1/access_tokens'], - 'post': ['/v1/users'], + delete: ['/v1/access_tokens/([0-9a-f]{40})'], + get: ['/server/health', '/v1/access_tokens'], + post: ['/v1/users'], }, - accessTokenLifetime: 7776000, // 90 days - grants: ['password'], - model: new OAuth2ServerModel({}), + grants: ['password'], + model: new OAuth2ServerModel({}), }); -const setCORSHeaders: Middleware = ( +const setCORSHeaders: Middleware = ( request: $Request, response: $Response, next: NextFunction, @@ -96,10 +96,8 @@ const setCORSHeaders: Middleware = ( }); return response.sendStatus(204); } - else { - response.set({ 'Access-Control-Allow-Origin': '*' }); - next(); - } + response.set({ 'Access-Control-Allow-Origin': '*' }); + return next(); }; app.use(morgan('combined')); @@ -123,13 +121,12 @@ routeConfig(app, [ const noRouteMiddleware: Middleware = ( request: $Request, response: $Response, - next: NextFunction, ): mixed => response.sendStatus(404); app.use(noRouteMiddleware); -console.log("Starting server, listening on " + NODE_PORT); +console.log(`Starting server, listening on ${NODE_PORT}`); app.listen(NODE_PORT); const deviceServer = new DeviceServer({ @@ -142,8 +139,8 @@ const deviceServer = new DeviceServer({ settings.serverKeyFile, ), serverKeyFile: settings.serverKeyFile, - serverKeyPassFile: settings.serverKeyPassFile, serverKeyPassEnvVar: settings.serverKeyPassEnvVar, + serverKeyPassFile: settings.serverKeyPassFile, }); // TODO wny do we need next line? (Anton Puko) @@ -151,6 +148,6 @@ global.server = deviceServer; deviceServer.start(); -utilities.getIPAddresses().forEach((ip) => +utilities.getIPAddresses().forEach((ip: string): void => console.log(`Your server IP address is: ${ip}`), ); From 190fca75de41ef443f09dca5d203b6438d729ba9 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 14:55:46 +0200 Subject: [PATCH 092/504] fix no-console rule --- .eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index b6410f31..9e755d04 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,7 +37,7 @@ "import/no-unresolved": 2, "import/prefer-default-export": 0, "newline-per-chained-call": 0, - "no-console": 1, + "no-console": 0, "no-duplicate-imports": 0, "no-mixed-operators": 0, "no-underscore-dangle": 0, From 2ea42eae67869e25ac1e46d143b359bd6a39ae74 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 28 Nov 2016 07:56:23 -0800 Subject: [PATCH 093/504] Cleaning up main --- src/main.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main.js b/src/main.js index e20be22a..41928fc7 100644 --- a/src/main.js +++ b/src/main.js @@ -48,11 +48,11 @@ import routeConfig from './lib/RouteConfig'; import WebhookController from './lib/controllers/WebhookController'; import { - DeviceFileRepository, + DeviceAttributeFileRepository, ServerConfigFileRepository, } from 'spark-protocol'; -const NODE_PORT = process.env.NODE_PORT || 8080; +const NODE_PORT = process.env.NODE_PORT || 8080; // TODO wny do we need this? (Anton Puko) global._socket_counter = 1; @@ -65,7 +65,7 @@ process.on('uncaughtException', (exception: Error) => { } catch (stringifyException) { logger.error(`Caught exception: ${stringifyException}`); } - logger.error(`Caught exception: ${exception.toString()} ${details}`); + logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); const app = express(); @@ -133,8 +133,9 @@ console.log("Starting server, listening on " + NODE_PORT); app.listen(NODE_PORT); const deviceServer = new DeviceServer({ - deviceAttributeRepository: new DeviceFileRepository( - path.join(__dirname, 'device_keys'), + coreKeysDir: settings.coreKeysDir, + deviceAttributeRepository: new DeviceAttributeFileRepository( + settings.coreKeysDir, ), host: settings.HOST, port: settings.PORT, From cbc6825161a06a00b850bb24125b264428b60b4a Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 18:50:53 +0200 Subject: [PATCH 094/504] fix routeConfig, add some annotations --- src/lib/RouteConfig.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 22c431dc..331e3f2d 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -1,41 +1,41 @@ -import type { $Application } from 'express'; +// @flow -import type Controller from './controllers/Controller' +import type { $Application, $Request, $Response } from 'express'; +import type Controller from './controllers/Controller'; -const getFunctionArgumentNames = (func): Array => { +const getFunctionArgumentNames = (func: Function): Array => { // First match everything inside the function argument parens. const args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1]; // Split the arguments string into an array comma delimited. - return args.split(',').map(argument => { + return args.split(',').map((argument: string): string => // Ensure no inline comments are parsed and trim the whitespace. - return argument.replace(/\/\*.*\*\//, '').trim(); - }).filter(argument => !!argument); + argument.replace(/\/\*.*\*\//, '').trim(), + ).filter((argument: string): boolean => !!argument); }; -export default (app: $Application, controllers: Array): void => { - controllers.map(controller => { +export default (app: $Application, controllers: Array) => { + controllers.forEach((controller: Controller) => { Object.getOwnPropertyNames( Object.getPrototypeOf(controller), - ).map(functionName => { + ).forEach((functionName: string) => { const mappedFunction = controller[functionName]; - const {httpVerb, route} = mappedFunction; + const { httpVerb, route } = mappedFunction; if (!httpVerb) { return; } const argumentNames = getFunctionArgumentNames(mappedFunction); - app[httpVerb](route, (request, response) => { + + app[httpVerb](route, (request: $Request, response: $Response) => { const values = argumentNames - .map(argument => request.params[argument]) - .filter(value => typeof value !== undefined); - console.log(values); - const result = mappedFunction.apply( + .map((argument: string): string => request.params[argument]) + .filter((value: ?Object): boolean => value !== undefined); + + const result = mappedFunction.call( controller, - [ - ...values, - request.body, - ] + ...values, + request.body, ); response.status(result.status).json(result.data); From 4d2ef92e9479cede12a0ef92da5718080567f672 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 15:43:13 +0200 Subject: [PATCH 095/504] add express-oauth-server package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index d551fe49..736fe219 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "eslint-plugin-flowtype": "^2.28.2", "eslint-plugin-import": "^2.2.0", "eslint-plugin-sorting": "^0.3.0", + "express-oauth-server": "^2.0.0-b1", "nodemon": "^1.11.0", "rimraf": "^2.5.4" } From 4dd898591e3c8a36ce5b46c5d7d4c3b9fa26e839 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 28 Nov 2016 16:23:35 +0200 Subject: [PATCH 096/504] refactor password hasher --- src/lib/PasswordHasher.js | 43 +++++++++++++++++++++++++------------- src/lib/RolesController.js | 2 +- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/lib/PasswordHasher.js b/src/lib/PasswordHasher.js index 2960d38e..b76555d5 100644 --- a/src/lib/PasswordHasher.js +++ b/src/lib/PasswordHasher.js @@ -14,24 +14,37 @@ * along with this program. If not, see . * * You can download the source here: https://github.com/spark/spark-server +* +* @flow +* */ -var crypto = require('crypto'); - -var PasswordHasher = function () { +import crypto from 'crypto'; -}; +const HASH_DIGEST = 'sha1'; +const HASH_ITERATIONS = 30000; +const KEY_LENGTH = 64; -PasswordHasher.generateSalt = function (callback) { - return crypto.randomBytes(64, callback); -}; +class PasswordHasher { + static generateSalt(callback: (error: ?Error, buffer: Buffer) => void) { + crypto.randomBytes(64, callback); + } -PasswordHasher.hash = function (password, salt, callback) { - password = password.toString('base64'); - salt = salt.toString('base64'); - return crypto.pbkdf2(password, salt, 30000, 64, function (err, derivedKey) { - return callback(err, derivedKey.toString('base64')); - }); -}; + static hash( + password: Buffer, + salt: Buffer, + callback: (error: ?Error, key: string) => void, + ) { + crypto.pbkdf2( + password.toString('base64'), + salt.toString('base64'), + HASH_ITERATIONS, + KEY_LENGTH, + HASH_DIGEST, + (error: ?Error, key: Buffer): void => + callback(error, key.toString('base64')), + ); + } +} -module.exports = PasswordHasher; +export default PasswordHasher; diff --git a/src/lib/RolesController.js b/src/lib/RolesController.js index fe5215b0..72a31d2e 100644 --- a/src/lib/RolesController.js +++ b/src/lib/RolesController.js @@ -21,7 +21,7 @@ var path = require('path'); var when = require('when'); var sequence = require('when/sequence'); var pipeline = require('when/pipeline'); -var PasswordHasher = require('./PasswordHasher.js'); +import PasswordHasher from './PasswordHasher'; var roles = require('./RolesController.js'); import settings from '../settings'; var logger = require('./logger.js'); From 8214284322ebdb964fad147289a0da052849744e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 00:39:41 +0200 Subject: [PATCH 097/504] fix: move express-oauth-server from devDeps to deps --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 736fe219..a0ee2cd0 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,9 @@ "dependencies": { "body-parser": "^1.15.2", "express": "^4.14.0", + "express-oauth-server": "^2.0.0-b1", "moment": "*", "morgan": "^1.7.0", - "node-oauth2-server": "~1.5.3", "request": "*", "spark-protocol": "../spark-protocol", "ursa": "*", @@ -63,7 +63,6 @@ "eslint-plugin-flowtype": "^2.28.2", "eslint-plugin-import": "^2.2.0", "eslint-plugin-sorting": "^0.3.0", - "express-oauth-server": "^2.0.0-b1", "nodemon": "^1.11.0", "rimraf": "^2.5.4" } From 03b25b24cac7c2f0d8657260b24bd11eb17d8dbc Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 00:40:48 +0200 Subject: [PATCH 098/504] implement usersFileRepository --- src/lib/repository/UsersFIleRepository.js | 77 +++++++++++++++++++++++ src/settings.js | 4 ++ 2 files changed, 81 insertions(+) create mode 100644 src/lib/repository/UsersFIleRepository.js diff --git a/src/lib/repository/UsersFIleRepository.js b/src/lib/repository/UsersFIleRepository.js new file mode 100644 index 00000000..f2df49f0 --- /dev/null +++ b/src/lib/repository/UsersFIleRepository.js @@ -0,0 +1,77 @@ +// @flow + +import type { TokenObject, User, UserCredentials } from '../../types'; + +import { FileManager, uuid } from 'spark-protocol'; +import PasswordHasher from '../PasswordHasher'; + +class UsersFileRepository { + _fileManager: FileManager; + + constructor(path: string) { + this._fileManager = new FileManager(path); + } + + create = async (userCredentials: UserCredentials): User => { + const { username, password } = userCredentials; + + const salt = await PasswordHasher.generateSalt(); + const passwordHash = await PasswordHasher.hash(password, salt); + + const modelToSave = { + accessTokens: [], + created_at: new Date(), + created_by: null, + id: uuid(), + passwordHash, + salt, + username, + }; + // todo make async call of fileManager when implement async inside fileManager + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + return modelToSave; + }; + + getAll(): Array { + return this._fileManager.getAllData(); + } + + getById(id: string): User { + return this._fileManager.getFile(id + '.json'); + } + + getByUsername(username: string) { + return this.getAll().find((user: User) => user.username === username); + } + + async validateLogin(username: string, password: string) { + try { + const user = this.getByUsername(username); + if (!user) { + throw new Error('user doesn\'t exist'); + } + + const hash = await PasswordHasher.hash(password, user.salt); + if (hash !== user.passwordHash) { + throw new Error('wrong password'); + } + + return user; + } catch (error) { + return error; + } + } + + saveAccessToken(userId: string, tokenObject: TokenObject) { + const user = this.getById(userId); + const userToSave = { + ...user, + accessTokens: [...user.accessTokens, tokenObject], + }; + + // todo make async call of fileManager when implement async inside fileManager + this._fileManager.writeFile(`${userId}.json`, userToSave); + } +} + +export default UsersFileRepository; diff --git a/src/settings.js b/src/settings.js index 4cc6da9a..d7a1767d 100644 --- a/src/settings.js +++ b/src/settings.js @@ -21,6 +21,7 @@ import path from 'path'; import WebhookFileRepository from './lib/repository/WebhookFileRepository'; +import UsersFileRepository from './lib/repository/UsersFIleRepository'; export default { baseUrl: 'http://localhost', @@ -35,6 +36,9 @@ export default { webhookRepository: new WebhookFileRepository( path.join(__dirname, 'webhooks'), ), + usersRepository: new UsersFileRepository( + path.join(__dirname, 'users'), + ), /** * Your server crypto keys! From 7ec77f3681c5811b6bc897901522b1d8154cbdfc Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 00:41:18 +0200 Subject: [PATCH 099/504] add Promise wrappers to PasswordHasher --- src/lib/PasswordHasher.js | 45 ++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/lib/PasswordHasher.js b/src/lib/PasswordHasher.js index b76555d5..769424e0 100644 --- a/src/lib/PasswordHasher.js +++ b/src/lib/PasswordHasher.js @@ -26,24 +26,39 @@ const HASH_ITERATIONS = 30000; const KEY_LENGTH = 64; class PasswordHasher { - static generateSalt(callback: (error: ?Error, buffer: Buffer) => void) { - crypto.randomBytes(64, callback); + static generateSalt(size: number = 64): Promise<*> { + // todo better annotate promise resolve, reject + return new Promise((resolve: Function, reject: Function) => { + crypto.randomBytes(size, (error: ?Error, buffer: Buffer) => { + if (error) { + reject(error); + return; + } + resolve(buffer.toString('base64')); + }); + }); } static hash( - password: Buffer, - salt: Buffer, - callback: (error: ?Error, key: string) => void, - ) { - crypto.pbkdf2( - password.toString('base64'), - salt.toString('base64'), - HASH_ITERATIONS, - KEY_LENGTH, - HASH_DIGEST, - (error: ?Error, key: Buffer): void => - callback(error, key.toString('base64')), - ); + password: string, + salt: string, + ): Promise<*> { + // todo better annotate promise resolve, reject + return new Promise((resolve: Function, reject: Function) => { + crypto.pbkdf2( + password, + salt, + HASH_ITERATIONS, + KEY_LENGTH, + HASH_DIGEST, + (error: ?Error, key: Buffer) => { + if (error) { + reject(error); + } + resolve(key.toString('base64')); + }, + ); + }); } } From 2037a99a2dc48ce5cecd39381f27bc4c16907dc7 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 00:43:14 +0200 Subject: [PATCH 100/504] implement new OAuthModel, add types for user, client, token --- src/lib/OAuth2ServerModel.js | 64 ------------------------------------ src/lib/OAuthModel.js | 41 +++++++++++++++++++++++ src/main.js | 23 +++++-------- src/oauthClients.json | 5 +++ src/types.js | 30 +++++++++++++++++ 5 files changed, 85 insertions(+), 78 deletions(-) create mode 100644 src/lib/OAuthModel.js create mode 100644 src/oauthClients.json diff --git a/src/lib/OAuth2ServerModel.js b/src/lib/OAuth2ServerModel.js index 5e973a02..e69de29b 100644 --- a/src/lib/OAuth2ServerModel.js +++ b/src/lib/OAuth2ServerModel.js @@ -1,64 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var PasswordHasher = require('./PasswordHasher'); -var roles = require('./RolesController.js'); -var when = require('when'); - - -var OAuth2ServerModel = function (options) { - this.options = options; -}; -OAuth2ServerModel.prototype = { - - getAccessToken: function (bearerToken, callback) { - var token = roles.getTokenInfoByToken(bearerToken); - callback(null, token); - }, - - getClient: function (clientId, clientSecret, callback) { - return callback(null, { client_id: clientId }); - }, - - grantTypeAllowed: function (clientId, grantType, callback) { - return callback(null, 'password' === grantType); - }, - - saveAccessToken: function (accessToken, clientId, userId, expires, callback) { - when(roles.addAccessToken(accessToken, clientId, userId, expires)) - .ensure(callback); - }, - - getUser: function (username, password, callback) { - if (username && username.toLowerCase) { - username = username.toLowerCase(); - } - - when(roles.validateLogin(username, password)) - .then( - function (user) { - callback(null, { id: user._id }); - }, - function (err) { - callback(err, null); - }); - } -}; - -module.exports = OAuth2ServerModel; - diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js new file mode 100644 index 00000000..12425c6d --- /dev/null +++ b/src/lib/OAuthModel.js @@ -0,0 +1,41 @@ +// @flow + +import type { Client, Repository, User } from '../types'; + +import ouathClients from '../oauthClients.json'; + +class OauthModel { + _usersRepository: Repository; + + constructor(usersRepository: Repository) { + this._usersRepository = usersRepository; + } + + getAccessToken(bearerToken) { + // todo implement getAccessToken + return true; + } + + getClient = (clientId: string, clientSecret: string): Client => + ouathClients.find((client: Client): Client => + client.clientId === clientId && client.clientSecret === clientSecret, + ); + + getUser = async (username: string, password: string): User => { + return await this._usersRepository.validateLogin(username, password); + }; + + saveToken = (tokenObject, client, user) => { + this._usersRepository.saveAccessToken(user.id, tokenObject); + return { + accessToken: tokenObject.accessToken, + client, + user, + }; + }; + + // todo figure out this function + validateScope = (user: User, scope) => scope; +} + +export default OauthModel; diff --git a/src/main.js b/src/main.js index ca95204b..ec27405c 100644 --- a/src/main.js +++ b/src/main.js @@ -30,21 +30,21 @@ import bodyParser from 'body-parser'; import express from 'express'; import morgan from 'morgan'; import path from 'path'; -import OAuthServer from 'node-oauth2-server'; +import OAuthServer from 'express-oauth-server'; import { DeviceServer } from 'spark-protocol'; import settings from './settings'; import utilities from './lib/utilities'; import logger from './lib/logger'; -import OAuth2ServerModel from './lib/OAuth2ServerModel'; +import OAuthModel from './lib/OAuthModel'; import AccessTokenViews from './lib/AccessTokenViews'; -import UserCreator from './lib/UserCreator'; import api from './views/api_v1'; import eventsV1 from './views/EventViews001'; // Routing import routeConfig from './lib/RouteConfig'; +import UsersController from './lib/controllers/UsersController'; import WebhookController from './lib/controllers/WebhookController'; import { @@ -70,15 +70,9 @@ process.on('uncaughtException', (exception: Error) => { const app = express(); -const oauth = OAuthServer({ +const oauth = new OAuthServer({ accessTokenLifetime: 7776000, // 90 days - allow: { - delete: ['/v1/access_tokens/([0-9a-f]{40})'], - get: ['/server/health', '/v1/access_tokens'], - post: ['/v1/users'], - }, - grants: ['password'], - model: new OAuth2ServerModel({}), + model: new OAuthModel(settings.usersRepository), }); const setCORSHeaders: Middleware = ( @@ -104,17 +98,18 @@ app.use(morgan('combined')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(setCORSHeaders); -app.use(oauth.handler()); -app.use(oauth.errorHandler()); + +// todo temporary for login +app.post('/oauth/token', oauth.token()); -app.post('/v1/users', UserCreator.getMiddleware()); const tokenViews = new AccessTokenViews({}); eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); routeConfig(app, [ + new UsersController(settings.usersRepository), new WebhookController(settings.webhookRepository), ]); diff --git a/src/oauthClients.json b/src/oauthClients.json new file mode 100644 index 00000000..72cd0afb --- /dev/null +++ b/src/oauthClients.json @@ -0,0 +1,5 @@ +[{ + "clientId": "CLI2", + "clientSecret": "client_secret_here", + "grants": ["password"] +}] diff --git a/src/types.js b/src/types.js index c6e997cf..f1893e50 100644 --- a/src/types.js +++ b/src/types.js @@ -13,6 +13,13 @@ export type Webhook = { url: string, }; + +export type Client = { + clientId: string, + clientSecret: string, + grants: Array, +}; + export type Device = { deviceId: string, ip: string, @@ -22,6 +29,29 @@ export type Device = { timestamp: Date, }; +export type GrantType = 'password' | 'refresh_token'; + +export type TokenObject = { + accessToken: string + accessTokenExpiresAt: Date, + refreshToken: string, + refreshTokenExpiresAt: Date, + scope: Object, +}; + +export type User = { + accessTokens: Array, + created_at: Date, + id: string, + passwordHash: string, + username: string, +}; + +export type UserCredentials = { + username: string, + password: string, +}; + export type Repository = { create: (id: string, model: TModel) => TModel, delete: (id: string) => void, From 49c742796a669e64a44a1d3ac8165e88e5a18053 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 00:43:37 +0200 Subject: [PATCH 101/504] implement UsersController --- src/lib/UserCreator.js | 59 -------------------------- src/lib/controllers/UsersController.js | 25 +++++++++++ 2 files changed, 25 insertions(+), 59 deletions(-) create mode 100644 src/lib/controllers/UsersController.js diff --git a/src/lib/UserCreator.js b/src/lib/UserCreator.js index a6234c37..e69de29b 100644 --- a/src/lib/UserCreator.js +++ b/src/lib/UserCreator.js @@ -1,59 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -*/ - - -var roles = require('./RolesController.js'); -var when = require('when'); - - -var UserCreator = function (options) { - this.options = options; -}; - -UserCreator.prototype = { - create: function (username, password, callback) { - username = username.toLowerCase(); - - roles.createUser(username, password) - .then(callback, callback); - }, - - - getMiddleware: function () { - var that = this; - return function (req, res) { - if ((null != req.body.username) && (null != req.body.password)) { - var username = req.body.username.toLowerCase(); - - return that.create(username, req.body.password, function (err) { - if (err) { - return res.json({ ok: false, errors: [err] }); - } - else { - return res.json({ ok: true }); - } - }); - } - else { - return res.json({ ok: false, errors: ['username and password required'] }); - } - }; - } -}; - -module.exports = new UserCreator(); - diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js new file mode 100644 index 00000000..a5581f65 --- /dev/null +++ b/src/lib/controllers/UsersController.js @@ -0,0 +1,25 @@ +// @flow + +import type { Repository, User, UserCredentials } from '../../types'; + +import Controller from './Controller'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; + +class UsersController extends Controller { + _usersRepository: Repository; + + constructor(usersRepository: Repository) { + super(); + this._usersRepository = usersRepository; + } + + @httpVerb('post') + @route('/v1/users') + async createUser(userCredentials: UserCredentials) { + const newUser = await this._usersRepository.create(userCredentials); + return this.ok(newUser); + } +} + +export default UsersController; From d05b0197ff0326018ae50d4e2c1ed729de837705 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 00:44:10 +0200 Subject: [PATCH 102/504] fix async response in RouteConfig --- src/lib/RouteConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 331e3f2d..8a59aaab 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -27,12 +27,12 @@ export default (app: $Application, controllers: Array) => { const argumentNames = getFunctionArgumentNames(mappedFunction); - app[httpVerb](route, (request: $Request, response: $Response) => { + app[httpVerb](route, async (request: $Request, response: $Response) => { const values = argumentNames .map((argument: string): string => request.params[argument]) .filter((value: ?Object): boolean => value !== undefined); - const result = mappedFunction.call( + const result = await mappedFunction.call( controller, ...values, request.body, From 85c2d0008163f30169eb20fb04e0b41ad1b00e67 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 01:21:15 +0200 Subject: [PATCH 103/504] fix webhookFileRepository methods to arrow-functions --- src/lib/repository/WebhookFileRepository.js | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 069e89b1..9d8c262c 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,6 +1,8 @@ +// @flow + import type { Webhook } from '../../types'; -import {FileManager, uuid} from 'spark-protocol'; +import { FileManager, uuid } from 'spark-protocol'; class WebhookFileRepository { _fileManager: FileManager; @@ -9,7 +11,7 @@ class WebhookFileRepository { this._fileManager = new FileManager(path); } - create(model: Webhook): Webhook { + create = (model: Webhook): Webhook => { const modelToSave = { ...model, created_at: new Date(), @@ -19,21 +21,18 @@ class WebhookFileRepository { id: uuid(), }; - this._fileManager.createFile(modelToSave.id + '.json', modelToSave); + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); return modelToSave; - } + }; - delete(id: string): void { - this._fileManager.deleteFile(id + '.json'); - } + delete = (id: string): void => + this._fileManager.deleteFile(`${id}.json`); - getAll(): Array { - return this._fileManager.getAllData(); - } + getAll = (): Array => + this._fileManager.getAllData(); - getById(id: string): Webhook { - return this._fileManager.getFile(id + '.json'); - } + getById = (id: string): Webhook => + this._fileManager.getFile(`${id}.json`); } export default WebhookFileRepository; From ef684c84cb30229348ff5294fb9de9de6f6a6f1a Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 01:43:46 +0200 Subject: [PATCH 104/504] fix nits --- src/lib/PasswordHasher.js | 1 + src/lib/RouteConfig.js | 18 +++++++++++------- ...IleRepository.js => UsersFileRepository.js} | 3 +-- src/types.js | 7 +++++-- 4 files changed, 18 insertions(+), 11 deletions(-) rename src/lib/repository/{UsersFIleRepository.js => UsersFileRepository.js} (91%) diff --git a/src/lib/PasswordHasher.js b/src/lib/PasswordHasher.js index 769424e0..ee7804d9 100644 --- a/src/lib/PasswordHasher.js +++ b/src/lib/PasswordHasher.js @@ -54,6 +54,7 @@ class PasswordHasher { (error: ?Error, key: Buffer) => { if (error) { reject(error); + return; } resolve(key.toString('base64')); }, diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 8a59aaab..d4ab3fdd 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -32,13 +32,17 @@ export default (app: $Application, controllers: Array) => { .map((argument: string): string => request.params[argument]) .filter((value: ?Object): boolean => value !== undefined); - const result = await mappedFunction.call( - controller, - ...values, - request.body, - ); - - response.status(result.status).json(result.data); + try { + const result = await mappedFunction.call( + controller, + ...values, + request.body, + ); + + response.status(result.status).json(result.data); + } catch (error) { + console.log(error); + } }); }); }); diff --git a/src/lib/repository/UsersFIleRepository.js b/src/lib/repository/UsersFileRepository.js similarity index 91% rename from src/lib/repository/UsersFIleRepository.js rename to src/lib/repository/UsersFileRepository.js index f2df49f0..bb70f643 100644 --- a/src/lib/repository/UsersFIleRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -27,7 +27,7 @@ class UsersFileRepository { salt, username, }; - // todo make async call of fileManager when implement async inside fileManager + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); return modelToSave; }; @@ -69,7 +69,6 @@ class UsersFileRepository { accessTokens: [...user.accessTokens, tokenObject], }; - // todo make async call of fileManager when implement async inside fileManager this._fileManager.writeFile(`${userId}.json`, userToSave); } } diff --git a/src/types.js b/src/types.js index f1893e50..2ba232c7 100644 --- a/src/types.js +++ b/src/types.js @@ -29,14 +29,17 @@ export type Device = { timestamp: Date, }; -export type GrantType = 'password' | 'refresh_token'; +export type GrantType = + 'bearer_token'| + 'password'| + 'refresh_token'; export type TokenObject = { accessToken: string accessTokenExpiresAt: Date, refreshToken: string, refreshTokenExpiresAt: Date, - scope: Object, + scope: string, }; export type User = { From 9209aed724b3fa5b0054b97ed1c13439abf7f92e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 12:51:03 +0200 Subject: [PATCH 105/504] fix typo in name --- src/lib/OAuth2ServerModel.js | 0 src/lib/UserCreator.js | 0 src/settings.js | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/lib/OAuth2ServerModel.js delete mode 100644 src/lib/UserCreator.js diff --git a/src/lib/OAuth2ServerModel.js b/src/lib/OAuth2ServerModel.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/lib/UserCreator.js b/src/lib/UserCreator.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/settings.js b/src/settings.js index d7a1767d..148a7672 100644 --- a/src/settings.js +++ b/src/settings.js @@ -21,7 +21,7 @@ import path from 'path'; import WebhookFileRepository from './lib/repository/WebhookFileRepository'; -import UsersFileRepository from './lib/repository/UsersFIleRepository'; +import UsersFileRepository from './lib/repository/UsersFileRepository'; export default { baseUrl: 'http://localhost', From 8e2f7da02c294a81118d69693ec63b93b39864d9 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 1 Dec 2016 00:35:26 +0200 Subject: [PATCH 106/504] implement getAccessToken() in outhModel --- src/lib/OAuthModel.js | 19 +++++++++++++++---- src/lib/repository/UsersFileRepository.js | 9 +++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index 12425c6d..b34ec9e6 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -11,10 +11,21 @@ class OauthModel { this._usersRepository = usersRepository; } - getAccessToken(bearerToken) { - // todo implement getAccessToken - return true; - } + getAccessToken = (bearerToken: string) => { + const user = this._usersRepository.getByAccessToken(bearerToken); + if (!user) { + return false; + } + + const userTokenObject = user.accessTokens.find((tokenObject) => + tokenObject.accessToken === bearerToken, + ); + + return { + accessToken: userTokenObject.accessToken, + user: user.id, + }; + }; getClient = (clientId: string, clientSecret: string): Client => ouathClients.find((client: Client): Client => diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UsersFileRepository.js index bb70f643..fce6b474 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -5,6 +5,7 @@ import type { TokenObject, User, UserCredentials } from '../../types'; import { FileManager, uuid } from 'spark-protocol'; import PasswordHasher from '../PasswordHasher'; +// todo change class methods style to arrow functions. class UsersFileRepository { _fileManager: FileManager; @@ -62,6 +63,14 @@ class UsersFileRepository { } } + getByAccessToken = (accessToken: string): ?User => + this.getAll().find((user: User): User => + user.accessTokens.some((tokenObject: TokenObject): boolean => + tokenObject.accessToken === accessToken, + ), + ); + + saveAccessToken(userId: string, tokenObject: TokenObject) { const user = this.getById(userId); const userToSave = { From fbafc34df5594def3197bcdc8e232998fbe47ec0 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 1 Dec 2016 00:36:51 +0200 Subject: [PATCH 107/504] move oauth middleware to RouteConfig, inject authenticate() middleware in controllers routes by condition --- src/lib/RouteConfig.js | 77 +++++++++++++++++++++++++++++++----------- src/main.js | 11 ------ src/settings.js | 1 + 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index d4ab3fdd..32b7f04a 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -1,8 +1,18 @@ // @flow -import type { $Application, $Request, $Response } from 'express'; +import type { + $Application, + $Request, + $Response, + Middleware, + NextFunction, +} from 'express'; import type Controller from './controllers/Controller'; +import OAuthModel from './OAuthModel'; +import OAuthServer from 'express-oauth-server'; +import settings from '../settings'; + const getFunctionArgumentNames = (func: Function): Array => { // First match everything inside the function argument parens. const args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1]; @@ -14,36 +24,65 @@ const getFunctionArgumentNames = (func: Function): Array => { ).filter((argument: string): boolean => !!argument); }; +// TODO fix flow errors, come up with better name; +const maybe = (middleware: Middleware, condition: boolean): Middleware => + (request: $Request, response: $Response, next: NextFunction) => { + console.log('im in maybe'); + if (condition) { + console.log('here'); + middleware(request, response, next); + } else { + console.log('here1'); + next(); + } + }; + + export default (app: $Application, controllers: Array) => { + // TODO figure out, may be I need to add oauth to app.oauth or app.locals.oauth + // to be able to inject user object in request. + const oauth = new OAuthServer({ + accessTokenLifetime: settings.accessTokenLifetime, + allowBearerTokensInQueryString: true, + model: new OAuthModel(settings.usersRepository), + }); + + app.post('/oauth/token', oauth.token()); + controllers.forEach((controller: Controller) => { Object.getOwnPropertyNames( Object.getPrototypeOf(controller), ).forEach((functionName: string) => { const mappedFunction = controller[functionName]; - const { httpVerb, route } = mappedFunction; + const { httpVerb, route, anonymous } = mappedFunction; if (!httpVerb) { return; } + const argumentNames = getFunctionArgumentNames(mappedFunction); - app[httpVerb](route, async (request: $Request, response: $Response) => { - const values = argumentNames - .map((argument: string): string => request.params[argument]) - .filter((value: ?Object): boolean => value !== undefined); - - try { - const result = await mappedFunction.call( - controller, - ...values, - request.body, - ); - - response.status(result.status).json(result.data); - } catch (error) { - console.log(error); - } - }); + app[httpVerb]( + route, + maybe(oauth.authenticate(), !anonymous), + async (request: $Request, response: $Response) => { + const values = argumentNames + .map((argument: string): string => request.params[argument]) + .filter((value: ?Object): boolean => value !== undefined); + + try { + const result = await mappedFunction.call( + controller, + ...values, + request.body, + ); + + response.status(result.status).json(result.data); + } catch (error) { + console.log(error); + } + }, + ); }); }); }; diff --git a/src/main.js b/src/main.js index ec27405c..cf058e31 100644 --- a/src/main.js +++ b/src/main.js @@ -30,13 +30,11 @@ import bodyParser from 'body-parser'; import express from 'express'; import morgan from 'morgan'; import path from 'path'; -import OAuthServer from 'express-oauth-server'; import { DeviceServer } from 'spark-protocol'; import settings from './settings'; import utilities from './lib/utilities'; import logger from './lib/logger'; -import OAuthModel from './lib/OAuthModel'; import AccessTokenViews from './lib/AccessTokenViews'; import api from './views/api_v1'; @@ -70,11 +68,6 @@ process.on('uncaughtException', (exception: Error) => { const app = express(); -const oauth = new OAuthServer({ - accessTokenLifetime: 7776000, // 90 days - model: new OAuthModel(settings.usersRepository), -}); - const setCORSHeaders: Middleware = ( request: $Request, response: $Response, @@ -99,10 +92,6 @@ app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(setCORSHeaders); -// todo temporary for login -app.post('/oauth/token', oauth.token()); - - const tokenViews = new AccessTokenViews({}); eventsV1.loadViews(app); diff --git a/src/settings.js b/src/settings.js index 148a7672..6e75b6c2 100644 --- a/src/settings.js +++ b/src/settings.js @@ -24,6 +24,7 @@ import WebhookFileRepository from './lib/repository/WebhookFileRepository'; import UsersFileRepository from './lib/repository/UsersFileRepository'; export default { + accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, coreKeysDir: path.join(__dirname, 'core_keys'), From 1bd8ede05afae7f57425a0c1c9fb0a09ee0d544e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 1 Dec 2016 00:37:33 +0200 Subject: [PATCH 108/504] implement anonymous decorator, use it in UsersController --- src/lib/controllers/UsersController.js | 2 ++ src/lib/decorators/anonymous.js | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/lib/decorators/anonymous.js diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index a5581f65..c4cc65cb 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -3,6 +3,7 @@ import type { Repository, User, UserCredentials } from '../../types'; import Controller from './Controller'; +import anonymous from '../decorators/anonymous'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; @@ -16,6 +17,7 @@ class UsersController extends Controller { @httpVerb('post') @route('/v1/users') + @anonymous() async createUser(userCredentials: UserCredentials) { const newUser = await this._usersRepository.create(userCredentials); return this.ok(newUser); diff --git a/src/lib/decorators/anonymous.js b/src/lib/decorators/anonymous.js new file mode 100644 index 00000000..baae5fc9 --- /dev/null +++ b/src/lib/decorators/anonymous.js @@ -0,0 +1,10 @@ +// @flow + +import type { Decorator } from './types'; + +export default (): Decorator => + (target, name, descriptor): Object => { + // eslint-disable-next-line no-param-reassign + target[name].anonymous = true; + return descriptor; + }; From 3d245d1d66006b4d937bd62cc47677096fc77f66 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 30 Nov 2016 20:56:26 -0800 Subject: [PATCH 109/504] Noted where the events actually get written in the EventViews001 --- src/views/EventViews001.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/EventViews001.js b/src/views/EventViews001.js index 7c263d65..1f9ff84e 100644 --- a/src/views/EventViews001.js +++ b/src/views/EventViews001.js @@ -52,7 +52,6 @@ var EventsApi = { pipeEvents: function (socket, req, res, filterCoreId) { var userid = Api.getUserID(req); - /* Start SSE */ @@ -72,7 +71,7 @@ var EventsApi = { var keepAlive = function() { if (((new Date()) - _lastMessage) >= 9000) { _lastMessage = new Date(); - res.write("\n"); + res.write("asdf\n"); checkSocket(); } }; @@ -150,6 +149,8 @@ var EventsApi = { //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. name = (name) ? name.toString().replace(userid + "/", "") : null; + + // NOTE: THIS IS THE ACTUAL DATA THAT GETS SENT TO THE CORE!!! var obj = { data: data ? data.toString() : null, ttl: ttl ? ttl.toString() : null, @@ -254,6 +255,7 @@ var EventsApi = { var is_public = (!private_str || (private_str == "") || (private_str == "false")); var socket = new CoreController(socketID); + console.log('EventViews001 - send_and_event'); var success = socket.sendEvent(is_public, eventName, userid, From 302ec552c307f25876c120c7d2ce09bba1cfa2fa Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 28 Nov 2016 07:56:23 -0800 Subject: [PATCH 110/504] Cleaning up main --- src/main.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.js b/src/main.js index cf058e31..e86d81e4 100644 --- a/src/main.js +++ b/src/main.js @@ -46,7 +46,7 @@ import UsersController from './lib/controllers/UsersController'; import WebhookController from './lib/controllers/WebhookController'; import { - DeviceFileRepository, + DeviceAttributeFileRepository, ServerConfigFileRepository, } from 'spark-protocol'; @@ -63,7 +63,7 @@ process.on('uncaughtException', (exception: Error) => { } catch (stringifyException) { logger.error(`Caught exception: ${stringifyException}`); } - logger.error(`Caught exception: ${exception.toString()} ${details}`); + logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); const app = express(); @@ -114,8 +114,9 @@ console.log(`Starting server, listening on ${NODE_PORT}`); app.listen(NODE_PORT); const deviceServer = new DeviceServer({ - deviceAttributeRepository: new DeviceFileRepository( - path.join(__dirname, 'device_keys'), + coreKeysDir: settings.coreKeysDir, + deviceAttributeRepository: new DeviceAttributeFileRepository( + settings.coreKeysDir, ), host: settings.HOST, port: settings.PORT, From 94e57466cc741cc791e181e97e54f74091aac70c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 30 Nov 2016 01:21:15 +0200 Subject: [PATCH 111/504] fix webhookFileRepository methods to arrow-functions --- src/lib/repository/WebhookFileRepository.js | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 069e89b1..9d8c262c 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,6 +1,8 @@ +// @flow + import type { Webhook } from '../../types'; -import {FileManager, uuid} from 'spark-protocol'; +import { FileManager, uuid } from 'spark-protocol'; class WebhookFileRepository { _fileManager: FileManager; @@ -9,7 +11,7 @@ class WebhookFileRepository { this._fileManager = new FileManager(path); } - create(model: Webhook): Webhook { + create = (model: Webhook): Webhook => { const modelToSave = { ...model, created_at: new Date(), @@ -19,21 +21,18 @@ class WebhookFileRepository { id: uuid(), }; - this._fileManager.createFile(modelToSave.id + '.json', modelToSave); + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); return modelToSave; - } + }; - delete(id: string): void { - this._fileManager.deleteFile(id + '.json'); - } + delete = (id: string): void => + this._fileManager.deleteFile(`${id}.json`); - getAll(): Array { - return this._fileManager.getAllData(); - } + getAll = (): Array => + this._fileManager.getAllData(); - getById(id: string): Webhook { - return this._fileManager.getFile(id + '.json'); - } + getById = (id: string): Webhook => + this._fileManager.getFile(`${id}.json`); } export default WebhookFileRepository; From 213f2406e4dce21ca40c2c416df494974384e933 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 30 Nov 2016 20:56:26 -0800 Subject: [PATCH 112/504] Noted where the events actually get written in the EventViews001 --- src/views/EventViews001.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/EventViews001.js b/src/views/EventViews001.js index 7c263d65..1f9ff84e 100644 --- a/src/views/EventViews001.js +++ b/src/views/EventViews001.js @@ -52,7 +52,6 @@ var EventsApi = { pipeEvents: function (socket, req, res, filterCoreId) { var userid = Api.getUserID(req); - /* Start SSE */ @@ -72,7 +71,7 @@ var EventsApi = { var keepAlive = function() { if (((new Date()) - _lastMessage) >= 9000) { _lastMessage = new Date(); - res.write("\n"); + res.write("asdf\n"); checkSocket(); } }; @@ -150,6 +149,8 @@ var EventsApi = { //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. name = (name) ? name.toString().replace(userid + "/", "") : null; + + // NOTE: THIS IS THE ACTUAL DATA THAT GETS SENT TO THE CORE!!! var obj = { data: data ? data.toString() : null, ttl: ttl ? ttl.toString() : null, @@ -254,6 +255,7 @@ var EventsApi = { var is_public = (!private_str || (private_str == "") || (private_str == "false")); var socket = new CoreController(socketID); + console.log('EventViews001 - send_and_event'); var success = socket.sendEvent(is_public, eventName, userid, From 05d11757aeef397bfb2ba90fa067d59421724212 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 1 Dec 2016 00:41:56 +0200 Subject: [PATCH 113/504] fix: remove console.logs() --- src/lib/RouteConfig.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 32b7f04a..33562770 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -27,12 +27,9 @@ const getFunctionArgumentNames = (func: Function): Array => { // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => (request: $Request, response: $Response, next: NextFunction) => { - console.log('im in maybe'); if (condition) { - console.log('here'); middleware(request, response, next); } else { - console.log('here1'); next(); } }; From cecccf338f67e359f39ff7347a369567287dc8b9 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 2 Dec 2016 00:37:25 +0200 Subject: [PATCH 114/504] inject User in protected request, bind request,response,user to controllerContext. --- src/lib/OAuthModel.js | 2 +- src/lib/RouteConfig.js | 20 ++++++++++++++++++-- src/settings.js | 43 +++++++++++++++++++++--------------------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index b34ec9e6..01956a61 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -23,7 +23,7 @@ class OauthModel { return { accessToken: userTokenObject.accessToken, - user: user.id, + user, }; }; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 33562770..3dec2fb5 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -34,6 +34,16 @@ const maybe = (middleware: Middleware, condition: boolean): Middleware => } }; +const injectUserMiddleware = (): Middleware => + (request: $Request, response: $Response, next: NextFunction) => { + const oauthInfo = response.locals.oauth; + if (oauthInfo) { + // eslint-disable-next-line no-param-reassign + request.user = oauthInfo.token.user; + } + next(); + }; + export default (app: $Application, controllers: Array) => { // TODO figure out, may be I need to add oauth to app.oauth or app.locals.oauth @@ -44,7 +54,7 @@ export default (app: $Application, controllers: Array) => { model: new OAuthModel(settings.usersRepository), }); - app.post('/oauth/token', oauth.token()); + app.post(settings.loginRoute, oauth.token()); controllers.forEach((controller: Controller) => { Object.getOwnPropertyNames( @@ -62,14 +72,20 @@ export default (app: $Application, controllers: Array) => { app[httpVerb]( route, maybe(oauth.authenticate(), !anonymous), + injectUserMiddleware(), async (request: $Request, response: $Response) => { const values = argumentNames .map((argument: string): string => request.params[argument]) .filter((value: ?Object): boolean => value !== undefined); + const controllerContext = Object.create(controller); + controllerContext.request = request; + controllerContext.response = response; + controllerContext.user = request.user; + try { const result = await mappedFunction.call( - controller, + controllerContext, ...values, request.body, ); diff --git a/src/settings.js b/src/settings.js index 6e75b6c2..4f36202d 100644 --- a/src/settings.js +++ b/src/settings.js @@ -25,30 +25,31 @@ import UsersFileRepository from './lib/repository/UsersFileRepository'; export default { accessTokenLifetime: 7776000, // 90 days, - baseUrl: 'http://localhost', - coreFlashTimeout: 90000, - coreKeysDir: path.join(__dirname, 'core_keys'), - coreRequestTimeout: 30000, - coreSignalTimeout: 30000, - isCoreOnlineTimeout: 2000, - maxHooksPerDevice: 10, - maxHooksPerUser: 20, - userDataDir: path.join(__dirname, 'users'), - webhookRepository: new WebhookFileRepository( - path.join(__dirname, 'webhooks'), - ), + baseUrl: 'http://localhost', + coreFlashTimeout: 90000, + coreKeysDir: path.join(__dirname, 'core_keys'), + coreRequestTimeout: 30000, + coreSignalTimeout: 30000, + isCoreOnlineTimeout: 2000, + maxHooksPerDevice: 10, + maxHooksPerUser: 20, + userDataDir: path.join(__dirname, 'users'), + loginRoute: '/oauth/token', + webhookRepository: new WebhookFileRepository( + path.join(__dirname, 'webhooks'), + ), usersRepository: new UsersFileRepository( path.join(__dirname, 'users'), ), - /** - * Your server crypto keys! - */ - cryptoSalt: 'aes-128-cbc', - serverKeyFile: "default_key.pem", - serverKeyPassFile: null, - serverKeyPassEnvVar: null, + /** + * Your server crypto keys! + */ + cryptoSalt: 'aes-128-cbc', + serverKeyFile: "default_key.pem", + serverKeyPassFile: null, + serverKeyPassEnvVar: null, - PORT: 5683, - HOST: "localhost", + PORT: 5683, + HOST: "localhost", }; From 1acbf45bcceb5a6d19807fce62f6c5c8cf9f3b12 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 2 Dec 2016 03:31:38 +0200 Subject: [PATCH 115/504] parse route.path for getting url params in args --- src/lib/RouteConfig.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 3dec2fb5..88a48256 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -13,17 +13,6 @@ import OAuthModel from './OAuthModel'; import OAuthServer from 'express-oauth-server'; import settings from '../settings'; -const getFunctionArgumentNames = (func: Function): Array => { - // First match everything inside the function argument parens. - const args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1]; - - // Split the arguments string into an array comma delimited. - return args.split(',').map((argument: string): string => - // Ensure no inline comments are parsed and trim the whitespace. - argument.replace(/\/\*.*\*\//, '').trim(), - ).filter((argument: string): boolean => !!argument); -}; - // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => (request: $Request, response: $Response, next: NextFunction) => { @@ -66,14 +55,12 @@ export default (app: $Application, controllers: Array) => { return; } - - const argumentNames = getFunctionArgumentNames(mappedFunction); - app[httpVerb]( route, maybe(oauth.authenticate(), !anonymous), injectUserMiddleware(), async (request: $Request, response: $Response) => { + const argumentNames = request.route.path.split('/:').splice(1); const values = argumentNames .map((argument: string): string => request.params[argument]) .filter((value: ?Object): boolean => value !== undefined); @@ -82,7 +69,6 @@ export default (app: $Application, controllers: Array) => { controllerContext.request = request; controllerContext.response = response; controllerContext.user = request.user; - try { const result = await mappedFunction.call( controllerContext, From 49c277c6eddff81e212f431c728c7c3b06c8d355 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 2 Dec 2016 03:32:03 +0200 Subject: [PATCH 116/504] add basic-auth-parser package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a0ee2cd0..93106d9f 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "start:prod": "npm run build && node ./build/main.js" }, "dependencies": { + "basic-auth-parser": "0.0.2", "body-parser": "^1.15.2", "express": "^4.14.0", "express-oauth-server": "^2.0.0-b1", From d76cd8076c679c1b20aa9cbe7a8e3145eb4d4f6f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 2 Dec 2016 03:33:52 +0200 Subject: [PATCH 117/504] implement getAccessTokens and deleteAccessToken routes in UsersController, remove AccessTokenViews.js --- src/lib/AccessTokenViews.js | 104 ---------------------- src/lib/controllers/UsersController.js | 31 +++++++ src/lib/repository/UsersFileRepository.js | 14 ++- src/main.js | 5 +- 4 files changed, 45 insertions(+), 109 deletions(-) delete mode 100644 src/lib/AccessTokenViews.js diff --git a/src/lib/AccessTokenViews.js b/src/lib/AccessTokenViews.js deleted file mode 100644 index b5b02a0d..00000000 --- a/src/lib/AccessTokenViews.js +++ /dev/null @@ -1,104 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var path = require('path'); -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); -var PasswordHasher = require('./PasswordHasher.js'); -var roles = require('./RolesController.js'); - -var AccessTokenViews = function (options) { - this.options = options; -}; - -AccessTokenViews.prototype = { - loadViews: function (app) { - app.get('/v1/access_tokens', this.index.bind(this)); - app.delete('/v1/access_tokens/:token', this.destroy.bind(this)); - }, - index: function (req, res) { - var credentials = AccessTokenViews.basicAuth(req); - if (!credentials) { - return res.json(401, { - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to view your access tokens."] - }); - } - - //if successful, should return something like: - // [ { token: d.token, expires: d.expires, client: d.client_id } ] - - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - res.json(userObj.access_tokens); - }, - function () { - res.json(401, { ok: false, errors: ['Bad password']}); - }); - }, - - destroy: function (req, res) { - var credentials = AccessTokenViews.basicAuth(req); - if (!credentials) { - return res.json(401, { - ok: false, - errors: ["Unauthorized. You must send username and password in HTTP Basic Auth to delete an access token."] - }); - } - - when(roles.validateLogin(credentials.username, credentials.password)) - .then( - function (userObj) { - try { - roles.destroyAccessToken(req.params.token); - res.json({ ok: true }); - } - catch (ex) { - logger.error("error saving user " + ex); - res.json(401, { ok: false, errors: ['Error updating token']}); - } - }, - function () { - res.json(401, { ok: false, errors: ['Bad password']}); - }); - }, - - basicAuth: function (req) { - var auth = req.get('Authorization'); - if (!auth) return null; - - var matches = auth.match(/Basic\s+(\S+)/); - if (!matches) return null; - - var creds = new Buffer(matches[1], 'base64').toString(); - var separatorIndex = creds.indexOf(':'); - if (-1 === separatorIndex) - return null; - - return { - username: creds.slice(0, separatorIndex), - password: creds.slice(separatorIndex + 1) - }; - } - -}; - -module.exports = AccessTokenViews; diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index c4cc65cb..6302d49d 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -2,6 +2,7 @@ import type { Repository, User, UserCredentials } from '../../types'; +import basicAuthParser from 'basic-auth-parser'; import Controller from './Controller'; import anonymous from '../decorators/anonymous'; import httpVerb from '../decorators/httpVerb'; @@ -19,9 +20,39 @@ class UsersController extends Controller { @route('/v1/users') @anonymous() async createUser(userCredentials: UserCredentials) { + // todo add checking for existing usernames. const newUser = await this._usersRepository.create(userCredentials); return this.ok(newUser); } + + @httpVerb('delete') + @route('/v1/access_tokens/:token') + @anonymous() + async deleteAccessToken(token: string) { + try { + const { username, password } = basicAuthParser(this.request.get('authorization')); + const user = await this._usersRepository.validateLogin(username, password); + + this._usersRepository.deleteAccessToken(user, token); + + return this.ok({ ok: true }); + } catch (error) { + return this.bad(error.message); + } + } + + @httpVerb('get') + @route('/v1/access_tokens') + @anonymous() + async getAccessTokens() { + try { + const { username, password } = basicAuthParser(this.request.get('authorization')); + const user = await this._usersRepository.validateLogin(username, password); + return this.ok(user.accessTokens); + } catch (error) { + return this.bad(error.message); + } + } } export default UsersController; diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UsersFileRepository.js index fce6b474..965ec395 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -59,7 +59,7 @@ class UsersFileRepository { return user; } catch (error) { - return error; + throw error; } } @@ -70,6 +70,18 @@ class UsersFileRepository { ), ); + deleteAccessToken(user: User, token: string) { + const userToSave = { + ...user, + accessTokens: user.accessTokens.filter( + (tokenObject: TokenObject): boolean => + tokenObject.accessToken !== token, + ), + }; + + this._fileManager.writeFile(`${user.id}.json`, userToSave); + } + saveAccessToken(userId: string, tokenObject: TokenObject) { const user = this.getById(userId); diff --git a/src/main.js b/src/main.js index e86d81e4..e1ac0e6c 100644 --- a/src/main.js +++ b/src/main.js @@ -35,7 +35,6 @@ import settings from './settings'; import utilities from './lib/utilities'; import logger from './lib/logger'; -import AccessTokenViews from './lib/AccessTokenViews'; import api from './views/api_v1'; import eventsV1 from './views/EventViews001'; @@ -92,11 +91,9 @@ app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(setCORSHeaders); -const tokenViews = new AccessTokenViews({}); - eventsV1.loadViews(app); api.loadViews(app); -tokenViews.loadViews(app); + routeConfig(app, [ new UsersController(settings.usersRepository), new WebhookController(settings.webhookRepository), From a7c84cd06881a1908872ef178d6d09bc9cb9f0c2 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 2 Dec 2016 18:41:42 +0200 Subject: [PATCH 118/504] remove RolesController, fix getUserId() in api_v1, add temporary authenticate() for old api --- src/lib/RolesController.js | 230 ------------------------------------- src/lib/RouteConfig.js | 7 ++ src/main.js | 6 +- src/views/api_v1.js | 31 +++-- 4 files changed, 25 insertions(+), 249 deletions(-) delete mode 100644 src/lib/RolesController.js diff --git a/src/lib/RolesController.js b/src/lib/RolesController.js deleted file mode 100644 index 72a31d2e..00000000 --- a/src/lib/RolesController.js +++ /dev/null @@ -1,230 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var path = require('path'); -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); -import PasswordHasher from './PasswordHasher'; -var roles = require('./RolesController.js'); -import settings from '../settings'; -var logger = require('./logger.js'); - - -function RolesController() { - this.init(); -}; - -RolesController.prototype = { - users: null, - usersByToken: null, - usersByUsername: null, - tokens: null, - - - init: function () { - this._loadAndCacheUsers(); - }, - - addUser: function (userObj) { - this.users.push(userObj); - this.usersByUsername[ userObj.username ] = userObj; - - if (userObj.access_token) { - this.usersByToken[userObj.access_token] = userObj; - this.tokens.push({ - user_id: userObj._id, - expires: userObj.access_token_expires_at - }); - } - - for (var i = 0; i < userObj.access_tokens.length; i++) { - var token = userObj.access_tokens[i]; - this.usersByToken[ token ] = userObj; - this.tokens[token.token] = token; - } - }, - destroyAccessToken: function (access_token) { - var userObj = this.usersByToken[access_token]; - if (!userObj) { - return true; - } - - delete this.usersByToken[access_token]; - if (userObj.access_token == access_token) { - userObj.access_token = null; - } - var idx = utilities.indexOf(userObj.access_tokens, req.params.token); - if (idx >= 0) { - userObj.access_tokens.splice(idx, 1); - } - - this.saveUser(); - }, - addAccessToken: function (accessToken, clientId, userId, expires) { - var tmp = when.defer(); - try { - var userObj = this.getUserByUserid(userId); - this.usersByToken[accessToken] = userObj; - - var tokenObj = { - user_id: userId, - client_id: clientId, - token: accessToken, - expires: expires, - _id: accessToken - }; - - this.tokens[accessToken] = tokenObj; - userObj.access_tokens.push(tokenObj); - this.saveUser(userObj); - tmp.resolve(); - } - catch (ex) { - logger.error("Error adding access token ", ex); - tmp.reject(ex); - } - return tmp.promise; - }, - - - saveUser: function (userObj) { - var userFile = path.join(settings.userDataDir, userObj.username) + ".json"; - var userJson = JSON.stringify(userObj, null, 2); - fs.writeFileSync(userFile, userJson); - }, - - _loadAndCacheUsers: function () { - this.users = []; - this.usersByToken = {}; - this.usersByUsername = {}; - this.tokens = {}; - - - // list files, load all user objects, index by access_tokens and usernames - - if (!fs.existsSync(settings.userDataDir)) { - fs.mkdirSync(settings.userDataDir); - } - - - var files = fs.readdirSync(settings.userDataDir); - if (!files || (files.length == 0)) { - logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); - } - - for (var i = 0; i < files.length; i++) { - try { - - var filename = path.join(settings.userDataDir, files[i]); - var userObj = JSON.parse(fs.readFileSync(filename)); - - console.log("Loading user " + userObj.username); - this.addUser(userObj); - } - catch (ex) { - logger.error("RolesController - error loading user at " + filename); - } - } - }, - - - getUserByToken: function (access_token) { - return this.usersByToken[access_token]; - }, - - getUserByName: function (username) { - return this.usersByUsername[username]; - }, - getTokenInfoByToken: function (token) { - return this.tokens[token]; - }, - getUserByUserid: function (userid) { - for (var i = 0; i < this.users.length; i++) { - var user = this.users[i]; - if (user._id == userid) { - return user; - } - } - return null; - }, - - - validateHashPromise: function (user, password) { - var tmp = when.defer(); - - PasswordHasher.hash(password, user.salt, function (err, hash) { - if (err) { - logger.error("hash error " + err); - tmp.reject("Bad password"); - } - else if (hash === user.password_hash) { - tmp.resolve(user); - } - else { - tmp.reject("Bad password"); - } - }); - - return tmp.promise; - }, - - - validateLogin: function (username, password) { - var userObj = this.getUserByName(username); - if (!userObj) { - return when.reject("Bad password"); - } - - return this.validateHashPromise(userObj, password); - }, - - createUser: function (username, password) { - var tmp = when.defer(); - var that = this; - - PasswordHasher.generateSalt(function (err, userid) { - userid = userid.toString('base64'); - userid = userid.substring(0, 32); - - PasswordHasher.generateSalt(function (err, salt) { - salt = salt.toString('base64'); - PasswordHasher.hash(password, salt, function (err, hash) { - var user = { - _id: userid, - username: username, - password_hash: hash, - salt: salt, - access_tokens: [] - }; - - var userFile = path.join(settings.userDataDir, username + ".json"); - fs.writeFileSync(userFile, JSON.stringify(user)); - - that.addUser(user); - - tmp.resolve(); - }); - }); - }); - - return tmp.promise; - } -}; -module.exports = global.roles = new RolesController(); \ No newline at end of file diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 88a48256..d6948e6b 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -43,6 +43,13 @@ export default (app: $Application, controllers: Array) => { model: new OAuthModel(settings.usersRepository), }); + // TODO this is temporary authentication for api_v1 and events routes + // until we move them in our controllers: + app.all('/v1/devices*', oauth.authenticate()); + app.all('/v1/provisioning*', oauth.authenticate()); + app.all('/v1/events*', oauth.authenticate()); + // end temporary + app.post(settings.loginRoute, oauth.token()); controllers.forEach((controller: Controller) => { diff --git a/src/main.js b/src/main.js index e1ac0e6c..97d0ca82 100644 --- a/src/main.js +++ b/src/main.js @@ -91,14 +91,14 @@ app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(setCORSHeaders); -eventsV1.loadViews(app); -api.loadViews(app); - routeConfig(app, [ new UsersController(settings.usersRepository), new WebhookController(settings.webhookRepository), ]); +eventsV1.loadViews(app); +api.loadViews(app); + const noRouteMiddleware: Middleware = ( request: $Request, response: $Response, diff --git a/src/views/api_v1.js b/src/views/api_v1.js index fee26576..b7bb74f0 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -19,7 +19,6 @@ var settings = require('../settings.js'); var CoreController = require('../lib/CoreController.js'); -var roles = require('../lib/RolesController.js'); var sequence = require('when/sequence'); var parallel = require('when/parallel'); @@ -70,17 +69,17 @@ var Api = { return userID + "_" + global._socket_counter++; }, - getUserID: function (req) { - if (!req.user) { + getUserID: function (res) { + if (!res.locals.oauth) { logger.log("User obj was empty"); return null; } //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.user.id; + return res.locals.oauth.token.user.id; }, list_devices: function (req, res) { - var userid = Api.getUserID(req); + var userid = Api.getUserID(res); logger.log("ListDevices", { userID: userid }); //give me all the cores @@ -127,7 +126,7 @@ var Api = { }, get_core_attributes: function (req, res) { - var userid = Api.getUserID(req); + var userid = Api.getUserID(res); var socketID = Api.getSocketID(userid), coreID = req.coreID, socket = new CoreController(socketID); @@ -201,7 +200,7 @@ var Api = { set_core_attributes: function (req, res) { var coreID = req.coreID; - var userid = Api.getUserID(req); + var userid = Api.getUserID(res); var promises = []; @@ -338,7 +337,7 @@ var Api = { }; //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api.getUserID(req); + var userid = Api.getUserID(res); var gotCore = utilities.deferredAny([ function () { var core = global.server.getCoreAttributes(req.coreID); @@ -378,7 +377,7 @@ var Api = { }, get_var: function (req, res) { - var userid = Api.getUserID(req); + var userid = Api.getUserID(res); var socketID = Api.getSocketID(userid), coreID = req.coreID, varName = req.params.var, @@ -429,7 +428,7 @@ var Api = { }, fn_call: function (req, res) { - var user_id = Api.getUserID(req), + var user_id = Api.getUserID(res), coreID = req.coreID, funcName = req.params.func, format = req.params.format; @@ -512,7 +511,7 @@ var Api = { core_signal_dfd: function (req) { var tmp = when.defer(); - var userid = Api.getUserID(req), + var userid = Api.getUserID(res), socketID = Api.getSocketID(userid), coreID = req.coreID, showSignal = parseInt(req.body.signal); @@ -547,7 +546,7 @@ var Api = { compile_and__or_flash_dfd: function (req) { var allDone = when.defer(); - var userid = Api.getUserID(req), + var userid = Api.getUserID(res), coreID = req.coreID; @@ -592,10 +591,10 @@ var Api = { * @param req * @returns {promise|*|Function|Promise|when.promise} */ - flash_core_dfd: function (req) { + flash_core_dfd: function (req, res) { var tmp = when.defer(); - var userid = Api.getUserID(req), + var userid = Api.getUserID(res), socketID = Api.getSocketID(userid), coreID = req.coreID; @@ -651,9 +650,9 @@ var Api = { }); }, - provision_core_dfd: function (req) { + provision_core_dfd: function (req, res) { var result = when.defer(), - userid = Api.getUserID(req), + userid = Api.getUserID(res), deviceID = req.body.deviceID, publicKey = req.body.publicKey; From 9af6c3c6c2e760856201a356e102127329aa3292 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sat, 3 Dec 2016 01:04:22 +0200 Subject: [PATCH 119/504] setup test environment --- .babelrc | 2 +- package.json | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.babelrc b/.babelrc index 72aebb68..af6d1d59 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,7 @@ "transform-decorators-legacy", "transform-es2015-destructuring", "transform-es2015-spread", - "transform-flow-strip-types", + "transform-flow-strip-types" ], "presets": ["es2015", "latest", "stage-0", "stage-1"] } diff --git a/package.json b/package.json index 736fe219..607c7cfb 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,19 @@ "lint": "eslint -- .", "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", - "start:prod": "npm run build && node ./build/main.js" + "start:prod": "npm run build && node ./build/main.js", + "test": "ava", + "test:watch": "ava --watch" + }, + "ava": { + "babel": "inherit", + "files": [ + "test/*.js" + ], + "require": [ + "babel-polyfill", + "babel-register" + ] }, "dependencies": { "body-parser": "^1.15.2", @@ -46,6 +58,7 @@ "xtend": "*" }, "devDependencies": { + "ava": "^0.17.0", "babel-cli": "^6.18.0", "babel-eslint": "^7.1.1", "babel-plugin-transform-class-properties": "^6.19.0", @@ -54,10 +67,12 @@ "babel-plugin-transform-es2015-destructuring": "^6.19.0", "babel-plugin-transform-es2015-spread": "^6.8.0", "babel-plugin-transform-flow-strip-types": "^6.18.0", + "babel-polyfill": "^6.16.0", "babel-preset-es2015": "^6.18.0", "babel-preset-latest": "^6.16.0", "babel-preset-stage-0": "^6.16.0", "babel-preset-stage-1": "^6.16.0", + "babel-register": "^6.18.0", "eslint": "^3.11.0", "eslint-config-airbnb-base": "^10.0.1", "eslint-plugin-flowtype": "^2.28.2", @@ -65,6 +80,8 @@ "eslint-plugin-sorting": "^0.3.0", "express-oauth-server": "^2.0.0-b1", "nodemon": "^1.11.0", - "rimraf": "^2.5.4" + "rimraf": "^2.5.4", + "superset": "^1.0.1", + "supertest-as-promised": "^4.0.2" } } From c7dab0e8b56510b6ab2448e13ff1e8923c69b533 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sat, 3 Dec 2016 01:04:22 +0200 Subject: [PATCH 120/504] setup test environment --- .babelrc | 2 +- package.json | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.babelrc b/.babelrc index 72aebb68..af6d1d59 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,7 @@ "transform-decorators-legacy", "transform-es2015-destructuring", "transform-es2015-spread", - "transform-flow-strip-types", + "transform-flow-strip-types" ], "presets": ["es2015", "latest", "stage-0", "stage-1"] } diff --git a/package.json b/package.json index 93106d9f..23e6637f 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,19 @@ "lint": "eslint -- .", "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", - "start:prod": "npm run build && node ./build/main.js" + "start:prod": "npm run build && node ./build/main.js", + "test": "ava", + "test:watch": "ava --watch" + }, + "ava": { + "babel": "inherit", + "files": [ + "test/*.js" + ], + "require": [ + "babel-polyfill", + "babel-register" + ] }, "dependencies": { "basic-auth-parser": "0.0.2", @@ -47,6 +59,7 @@ "xtend": "*" }, "devDependencies": { + "ava": "^0.17.0", "babel-cli": "^6.18.0", "babel-eslint": "^7.1.1", "babel-plugin-transform-class-properties": "^6.19.0", @@ -55,16 +68,20 @@ "babel-plugin-transform-es2015-destructuring": "^6.19.0", "babel-plugin-transform-es2015-spread": "^6.8.0", "babel-plugin-transform-flow-strip-types": "^6.18.0", + "babel-polyfill": "^6.16.0", "babel-preset-es2015": "^6.18.0", "babel-preset-latest": "^6.16.0", "babel-preset-stage-0": "^6.16.0", "babel-preset-stage-1": "^6.16.0", + "babel-register": "^6.18.0", "eslint": "^3.11.0", "eslint-config-airbnb-base": "^10.0.1", "eslint-plugin-flowtype": "^2.28.2", "eslint-plugin-import": "^2.2.0", "eslint-plugin-sorting": "^0.3.0", "nodemon": "^1.11.0", - "rimraf": "^2.5.4" + "rimraf": "^2.5.4", + "superset": "^1.0.1", + "supertest-as-promised": "^4.0.2" } } From b34d48334b8a669e842d54cf78fe02e8935bbcc7 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 5 Dec 2016 07:32:45 -0800 Subject: [PATCH 121/504] Update `===` --- src/lib/CoreController.js | 4 ++-- src/lib/RolesController.js | 6 +++--- src/lib/UserCreator.js | 2 +- src/lib/utilities.js | 16 ++++++++-------- src/views/EventViews001.js | 4 ++-- src/views/api_v1.js | 10 +++++----- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/lib/CoreController.js b/src/lib/CoreController.js index dfa0521b..32bf7cea 100644 --- a/src/lib/CoreController.js +++ b/src/lib/CoreController.js @@ -136,7 +136,7 @@ CoreController.prototype = { }, unsubscribe: function (isPublic, name, userid) { - if (userid && (userid != "")) { + if (userid && (userid !== "")) { name = userid + "/" + name; } @@ -198,7 +198,7 @@ CoreController.prototype = { // // for(var key in global.cores) { // var core = global.cores[key]; -// if (core.coreID == id) { +// if (core.coreID===id) { // cores[id] = core; // break; // } diff --git a/src/lib/RolesController.js b/src/lib/RolesController.js index 72a31d2e..087fa3d0 100644 --- a/src/lib/RolesController.js +++ b/src/lib/RolesController.js @@ -67,7 +67,7 @@ RolesController.prototype = { } delete this.usersByToken[access_token]; - if (userObj.access_token == access_token) { + if (userObj.access_token===access_token) { userObj.access_token = null; } var idx = utilities.indexOf(userObj.access_tokens, req.params.token); @@ -125,7 +125,7 @@ RolesController.prototype = { var files = fs.readdirSync(settings.userDataDir); - if (!files || (files.length == 0)) { + if (!files || (files.length===0)) { logger.error([ "-------", "No users exist, you should create some users!", "-------", ].join("\n")); } @@ -158,7 +158,7 @@ RolesController.prototype = { getUserByUserid: function (userid) { for (var i = 0; i < this.users.length; i++) { var user = this.users[i]; - if (user._id == userid) { + if (user._id===userid) { return user; } } diff --git a/src/lib/UserCreator.js b/src/lib/UserCreator.js index a6234c37..5e5aec38 100644 --- a/src/lib/UserCreator.js +++ b/src/lib/UserCreator.js @@ -36,7 +36,7 @@ UserCreator.prototype = { getMiddleware: function () { var that = this; return function (req, res) { - if ((null != req.body.username) && (null != req.body.password)) { + if ((null !== req.body.username) && (null !== req.body.password)) { var username = req.body.username.toLowerCase(); return that.create(username, req.body.password, function (err) { diff --git a/src/lib/utilities.js b/src/lib/utilities.js index bfa5b69d..84c7c37a 100644 --- a/src/lib/utilities.js +++ b/src/lib/utilities.js @@ -52,10 +52,10 @@ module.exports = that = { * @param right */ bufferCompare: function (left, right) { - if ((left == null) && (right == null)) { + if ((left===null) && (right===null)) { return true; } - else if ((left == null) || (right == null)) { + else if ((left===null) || (right===null)) { return false; } @@ -68,12 +68,12 @@ module.exports = that = { logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); - var same = (left.length == right.length), + var same = (left.length===right.length), i = 0, max = left.length; while (i < max) { - same &= (left[i] == right[i]); + same &= (left[i]===right[i]); i++; } @@ -96,7 +96,7 @@ module.exports = that = { if (!right.hasOwnProperty(prop)) { continue; } - matches &= (left[prop] == right[prop]); + matches &= (left[prop]===right[prop]); } return matches; }, @@ -277,11 +277,11 @@ module.exports = that = { }, indexOf: function (arr, val) { - if (!arr || (arr.length == 0)) { + if (!arr || (arr.length===0)) { return -1; } for (var i = 0; i < arr.length; i++) { - if (arr[i] == val) { + if (arr[i]===val) { return i; } } @@ -367,7 +367,7 @@ module.exports = that = { for (var i = 0; i < nic.length; i++) { var addy = nic[i]; - if ((addy.family != "IPv4") || (addy.address == "127.0.0.1")) { + if ((addy.family !== "IPv4") || (addy.address==="127.0.0.1")) { continue; } diff --git a/src/views/EventViews001.js b/src/views/EventViews001.js index 1f9ff84e..697f31a7 100644 --- a/src/views/EventViews001.js +++ b/src/views/EventViews001.js @@ -135,7 +135,7 @@ var EventsApi = { // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events var writeEventGen = function (isPublic) { return function (name, data, ttl, published_at, coreid) { - if (filterCoreId && (filterCoreId != coreid)) { + if (filterCoreId && (filterCoreId !== coreid)) { return; } @@ -252,7 +252,7 @@ var EventsApi = { ttl = req.body.ttl || 60, private_str = req.body.private; - var is_public = (!private_str || (private_str == "") || (private_str == "false")); + var is_public = (!private_str || (private_str === "") || (private_str === "false")); var socket = new CoreController(socketID); console.log('EventViews001 - send_and_event'); diff --git a/src/views/api_v1.js b/src/views/api_v1.js index fee26576..c0676693 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -148,7 +148,7 @@ var Api = { when(objReady).done(function (results) { try { - if (!results || (results.length != 2)) { + if (!results || (results.length !== 2)) { logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); res.json(404, "Oops, I couldn't find that core"); return; @@ -208,7 +208,7 @@ var Api = { logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); var coreName = req.body ? req.body.name : null; - if (coreName != null) { + if (coreName !== null) { logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); global.server.setCoreAttribute(req.coreID, "name", coreName); @@ -413,7 +413,7 @@ var Api = { msg.coreInfo = req.coreInfo; msg.coreInfo.connected = true; - if (format && (format == "raw")) { + if (format && (format === "raw")) { return res.send("" + msg.result); } else { @@ -464,14 +464,14 @@ var Api = { error: "Function not found" }); } - else if (msg.error != null) { + else if (msg.error !== null) { res.json(400, { ok: false, error: msg.error }); } else { - if (format && (format == "raw")) { + if (format && (format === "raw")) { res.send("" + msg.result); } else { From d59f768fc9b18b959281242ef244f7af3cc37b9d Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 5 Dec 2016 08:44:32 -0800 Subject: [PATCH 122/504] Fixing flow --- src/lib/RouteConfig.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 331e3f2d..d0755ecb 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -5,7 +5,8 @@ import type Controller from './controllers/Controller'; const getFunctionArgumentNames = (func: Function): Array => { // First match everything inside the function argument parens. - const args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1]; + const args = + (func.toString().match(/function\s.*?\(([^)]*)\)/) || [])[1] || ''; // Split the arguments string into an array comma delimited. return args.split(',').map((argument: string): string => @@ -27,10 +28,10 @@ export default (app: $Application, controllers: Array) => { const argumentNames = getFunctionArgumentNames(mappedFunction); - app[httpVerb](route, (request: $Request, response: $Response) => { + (app: any)[httpVerb](route, (request: $Request, response: $Response) => { const values = argumentNames - .map((argument: string): string => request.params[argument]) - .filter((value: ?Object): boolean => value !== undefined); + .map((argument: string): any => request.params[argument]) + .filter((value: ?any): boolean => value !== undefined); const result = mappedFunction.call( controller, From 9de78651fc3aff168f04f88925214069197429a0 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 01:37:06 +0200 Subject: [PATCH 123/504] add NODE_ENV to test scripts, implement test settings; --- package.json | 8 +++++--- src/settings.js | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 23e6637f..6dc9df4a 100644 --- a/package.json +++ b/package.json @@ -32,13 +32,15 @@ "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", "start:prod": "npm run build && node ./build/main.js", - "test": "ava", - "test:watch": "ava --watch" + "test": "NODE_ENV=test ava", + "test:watch": "NODE_ENV=test ava --watch" }, "ava": { + "verbose": true, "babel": "inherit", "files": [ - "test/*.js" + "test/*.test.js", + "!test/__test_data__/*" ], "require": [ "babel-polyfill", diff --git a/src/settings.js b/src/settings.js index 4f36202d..8b29e5ca 100644 --- a/src/settings.js +++ b/src/settings.js @@ -23,7 +23,7 @@ import path from 'path'; import WebhookFileRepository from './lib/repository/WebhookFileRepository'; import UsersFileRepository from './lib/repository/UsersFileRepository'; -export default { +const settings = { accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, @@ -33,7 +33,6 @@ export default { isCoreOnlineTimeout: 2000, maxHooksPerDevice: 10, maxHooksPerUser: 20, - userDataDir: path.join(__dirname, 'users'), loginRoute: '/oauth/token', webhookRepository: new WebhookFileRepository( path.join(__dirname, 'webhooks'), @@ -53,3 +52,17 @@ export default { PORT: 5683, HOST: "localhost", }; + +const testSettings = { + ...settings, + coreKeysDir: path.join(__dirname, '../test/__test_data__/core_keys'), + usersRepository: new UsersFileRepository( + path.join(__dirname, '../test/__test_data__/users'), + ), + webhookRepository: new WebhookFileRepository( + path.join(__dirname, '../test/__test_data__/webhooks'), + ), +}; + +export default process.env.NODE_ENV === 'test' ? testSettings : settings; + From 793cab304ed39aef5c97fbe8904ce4c94ce0506c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 01:39:15 +0200 Subject: [PATCH 124/504] fix TokenObject type typo. --- src/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.js b/src/types.js index 2ba232c7..c7608335 100644 --- a/src/types.js +++ b/src/types.js @@ -35,7 +35,7 @@ export type GrantType = 'refresh_token'; export type TokenObject = { - accessToken: string + accessToken: string, accessTokenExpiresAt: Date, refreshToken: string, refreshTokenExpiresAt: Date, From 493a40c7b9a5df5e1b5b7b738e9bf34e9f4bb6a2 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 01:39:48 +0200 Subject: [PATCH 125/504] implement isUserExist checking in createUser() method of UserController --- src/lib/controllers/UsersController.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index 6302d49d..a4cd2f7b 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -20,9 +20,19 @@ class UsersController extends Controller { @route('/v1/users') @anonymous() async createUser(userCredentials: UserCredentials) { - // todo add checking for existing usernames. - const newUser = await this._usersRepository.create(userCredentials); - return this.ok(newUser); + try { + const isUserNameExist = this._usersRepository.getAll() + .some((user: User): boolean => user.username === userCredentials.username); + + if (isUserNameExist) { + throw new Error('user with the username is already exist'); + } + + const newUser = await this._usersRepository.create(userCredentials); + return this.ok(newUser); + } catch (error) { + return this.bad(error.message); + } } @httpVerb('delete') From ebe168f066b0485800c4c64e20905aa3b5a3da2d Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 01:40:32 +0200 Subject: [PATCH 126/504] add deleteById method in UsersFileRepository --- src/lib/repository/UsersFileRepository.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UsersFileRepository.js index 965ec395..bd82f727 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -82,6 +82,9 @@ class UsersFileRepository { this._fileManager.writeFile(`${user.id}.json`, userToSave); } + deleteById(id: string) { + this._fileManager.deleteFile(`${id}.json`); + } saveAccessToken(userId: string, tokenObject: TokenObject) { const user = this.getById(userId); From b5bf3feb5a284f342fbdfd5ba3d0188f029d27ce Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 01:41:50 +0200 Subject: [PATCH 127/504] implement userControllerTests; don't use mogan in test env; --- src/main.js | 8 ++-- test/UsersController.test.js | 90 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 test/UsersController.test.js diff --git a/src/main.js b/src/main.js index 97d0ca82..3abac65e 100644 --- a/src/main.js +++ b/src/main.js @@ -29,7 +29,6 @@ import type { import bodyParser from 'body-parser'; import express from 'express'; import morgan from 'morgan'; -import path from 'path'; import { DeviceServer } from 'spark-protocol'; import settings from './settings'; @@ -65,7 +64,7 @@ process.on('uncaughtException', (exception: Error) => { logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); -const app = express(); +export const app = express(); const setCORSHeaders: Middleware = ( request: $Request, @@ -86,7 +85,10 @@ const setCORSHeaders: Middleware = ( return next(); }; -app.use(morgan('combined')); +if (process.env.NODE_ENV !== 'test') { + app.use(morgan('combined')); +} + app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(setCORSHeaders); diff --git a/test/UsersController.test.js b/test/UsersController.test.js new file mode 100644 index 00000000..c2ca23f6 --- /dev/null +++ b/test/UsersController.test.js @@ -0,0 +1,90 @@ +// @flow + +import type { TokenObject } from '../src/types.js'; + +import test from 'ava'; +import request from 'supertest-as-promised'; +import ouathClients from '../src/oauthClients.json'; + +import { app } from '../src/main'; +import settings from '../src/settings'; + +const USER_CREDENTIALS = { + password: 'password', + username: 'newUser@test.com', +}; + +let user; +let userToken; + +test.serial('should return a new user object', async t => { + const response = await request(app) + .post('/v1/users') + .send(USER_CREDENTIALS); + + user = response.body; + + t.is(response.status, 200); + t.truthy(user.username === USER_CREDENTIALS.username); + t.truthy(user.id && user.passwordHash && user.salt && user.created_at); +}); + +test.serial('should throw an error if username is already exist', async t => { + const response = await request(app) + .post('/v1/users') + .send(USER_CREDENTIALS); + + t.is(response.status, 400); + t.is(response.body.message, 'user with the username is already exist'); +}); + +test.serial('should login the user', async t => { + const response = await request(app) + .post('/oauth/token') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + client_id: ouathClients[0].clientId, + client_secret: ouathClients[0].clientSecret, + grant_type: 'password', + password: USER_CREDENTIALS.password, + username: USER_CREDENTIALS.username, + }); + + userToken = response.body.access_token; + + t.is(response.status, 200); + t.truthy(userToken && response.body.token_type === 'Bearer'); +}); + +test.serial('should return all access tokens for the user', async t => { + const response = await request(app) + .get('/v1/access_tokens') + .auth(USER_CREDENTIALS.username, USER_CREDENTIALS.password); + + const tokens = response.body; + + t.is(response.status, 200); + t.truthy(Array.isArray(tokens) && tokens.length > 0); +}); + + +test.serial('should delete access token for the user', async t => { + const deleteResponse = await request(app) + .delete(`/v1/access_tokens/${userToken}`) + .auth(USER_CREDENTIALS.username, USER_CREDENTIALS.password); + + const allTokensResponse = await request(app) + .get('/v1/access_tokens') + .auth(USER_CREDENTIALS.username, USER_CREDENTIALS.password); + + const allTokens = allTokensResponse.body; + + t.is(deleteResponse.status, 200); + t.falsy(allTokens.some((tokenObject: TokenObject): boolean => + tokenObject.accessToken === userToken, + )); +}); + +test.after.always(() => { + settings.usersRepository.deleteById(user.id); +}); From 8aa08c6bf0c7c3abea1625a97a7d5789e0ae711c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 01:42:06 +0200 Subject: [PATCH 128/504] add test/__test_data__/* to gitignore --- .gitignore | 1 + test/UsersController.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 35789fae..7b1be020 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ node_modules core_keys users webhooks +test/__test_data__/* *.der *.pem *.pub.pem diff --git a/test/UsersController.test.js b/test/UsersController.test.js index c2ca23f6..5bbb7d57 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -1,6 +1,6 @@ // @flow -import type { TokenObject } from '../src/types.js'; +import type { TokenObject } from '../src/types'; import test from 'ava'; import request from 'supertest-as-promised'; From c5f57c56b206c6b4647dce2880ecd7343b25d0ac Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 02:57:48 +0200 Subject: [PATCH 129/504] move isUserNameInUse to UsersFileRepository --- src/lib/controllers/UsersController.js | 6 +++--- src/lib/repository/UsersFileRepository.js | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index a4cd2f7b..263cf3e0 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -21,10 +21,10 @@ class UsersController extends Controller { @anonymous() async createUser(userCredentials: UserCredentials) { try { - const isUserNameExist = this._usersRepository.getAll() - .some((user: User): boolean => user.username === userCredentials.username); + const isUserNameInUse = + this._usersRepository.isUserNameInUse(userCredentials.username); - if (isUserNameExist) { + if (isUserNameInUse) { throw new Error('user with the username is already exist'); } diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UsersFileRepository.js index bd82f727..fe3c4a3d 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -86,6 +86,11 @@ class UsersFileRepository { this._fileManager.deleteFile(`${id}.json`); } + isUserNameInUse = (username: string): boolean => + this.getAll().some((user: User): boolean => + user.username === username, + ); + saveAccessToken(userId: string, tokenObject: TokenObject) { const user = this.getById(userId); const userToSave = { From 6fd0fa4e19d7facfed8331e8cf1a55a31135714f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 03:05:43 +0200 Subject: [PATCH 130/504] fix test name --- test/UsersController.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UsersController.test.js b/test/UsersController.test.js index 5bbb7d57..1da77ca1 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -29,7 +29,7 @@ test.serial('should return a new user object', async t => { t.truthy(user.id && user.passwordHash && user.salt && user.created_at); }); -test.serial('should throw an error if username is already exist', async t => { +test.serial('should throw an error if username already in use', async t => { const response = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); From aac2a808500337e51ba9b91460e54bed825e5123 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 6 Dec 2016 11:56:15 +0200 Subject: [PATCH 131/504] move app setup to app.js; seperate test settings --- package.json | 4 +- src/app.js | 74 +++++++++++++++++++++++++ src/lib/RouteConfig.js | 10 ++-- src/main.js | 103 +++-------------------------------- src/settings.js | 19 +------ src/types.js | 22 ++++++++ test/UsersController.test.js | 5 +- test/settings.js | 36 ++++++++++++ test/testApp.js | 6 ++ 9 files changed, 160 insertions(+), 119 deletions(-) create mode 100644 src/app.js create mode 100644 test/settings.js create mode 100644 test/testApp.js diff --git a/package.json b/package.json index 6dc9df4a..3fd559a0 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", "start:prod": "npm run build && node ./build/main.js", - "test": "NODE_ENV=test ava", - "test:watch": "NODE_ENV=test ava --watch" + "test": "ava", + "test:watch": "ava --watch" }, "ava": { "verbose": true, diff --git a/src/app.js b/src/app.js new file mode 100644 index 00000000..031e4a49 --- /dev/null +++ b/src/app.js @@ -0,0 +1,74 @@ +// @flow + +import type { + $Application, + $Request, + $Response, + Middleware, + NextFunction, +} from 'express'; +import type { Settings } from './types'; + +import bodyParser from 'body-parser'; +import express from 'express'; +import morgan from 'morgan'; + +import api from './views/api_v1'; +import eventsV1 from './views/EventViews001'; + +// Routing +import routeConfig from './lib/RouteConfig'; +import UsersController from './lib/controllers/UsersController'; +import WebhookController from './lib/controllers/WebhookController'; + +export default (settings: Settings): $Application => { + const app = express(); + + const setCORSHeaders: Middleware = ( + request: $Request, + response: $Response, + next: NextFunction, + ): mixed => { + if (request.method === 'OPTIONS') { + response.set({ + 'Access-Control-Allow-Headers': + 'X-Requested-With, Content-Type, Accept, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Max-Age': '300', + }); + return response.sendStatus(204); + } + response.set({ 'Access-Control-Allow-Origin': '*' }); + return next(); + }; + + if (settings.logRequests) { + app.use(morgan('combined')); + } + + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ extended: true })); + app.use(setCORSHeaders); + + routeConfig( + app, + [ + new UsersController(settings.usersRepository), + new WebhookController(settings.webhookRepository), + ], + settings, + ); + + eventsV1.loadViews(app); + api.loadViews(app); + + const noRouteMiddleware: Middleware = ( + request: $Request, + response: $Response, + ): mixed => response.sendStatus(404); + + app.use(noRouteMiddleware); + + return app; +}; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index d6948e6b..93b44d73 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -7,11 +7,11 @@ import type { Middleware, NextFunction, } from 'express'; +import type { Settings } from '../types'; import type Controller from './controllers/Controller'; import OAuthModel from './OAuthModel'; import OAuthServer from 'express-oauth-server'; -import settings from '../settings'; // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => @@ -34,9 +34,11 @@ const injectUserMiddleware = (): Middleware => }; -export default (app: $Application, controllers: Array) => { - // TODO figure out, may be I need to add oauth to app.oauth or app.locals.oauth - // to be able to inject user object in request. +export default ( + app: $Application, + controllers: Array, + settings: Settings, +) => { const oauth = new OAuthServer({ accessTokenLifetime: settings.accessTokenLifetime, allowBearerTokensInQueryString: true, diff --git a/src/main.js b/src/main.js index 3abac65e..62aa29d0 100644 --- a/src/main.js +++ b/src/main.js @@ -1,52 +1,14 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -* -* @flow -* -*/ - -import type { - $Request, - $Response, - Middleware, - NextFunction, -} from 'express'; - -import bodyParser from 'body-parser'; -import express from 'express'; -import morgan from 'morgan'; -import { DeviceServer } from 'spark-protocol'; -import settings from './settings'; - -import utilities from './lib/utilities'; -import logger from './lib/logger'; - -import api from './views/api_v1'; -import eventsV1 from './views/EventViews001'; - -// Routing -import routeConfig from './lib/RouteConfig'; -import UsersController from './lib/controllers/UsersController'; -import WebhookController from './lib/controllers/WebhookController'; +// @flow import { DeviceAttributeFileRepository, + DeviceServer, ServerConfigFileRepository, } from 'spark-protocol'; +import utilities from './lib/utilities'; +import logger from './lib/logger'; +import makeApp from './app'; +import settings from './settings'; const NODE_PORT = process.env.NODE_PORT || 8080; @@ -64,53 +26,7 @@ process.on('uncaughtException', (exception: Error) => { logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); -export const app = express(); - -const setCORSHeaders: Middleware = ( - request: $Request, - response: $Response, - next: NextFunction, -): mixed => { - if (request.method === 'OPTIONS') { - response.set({ - 'Access-Control-Allow-Headers': - 'X-Requested-With, Content-Type, Accept, Authorization', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Max-Age': '300', - }); - return response.sendStatus(204); - } - response.set({ 'Access-Control-Allow-Origin': '*' }); - return next(); -}; - -if (process.env.NODE_ENV !== 'test') { - app.use(morgan('combined')); -} - -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); -app.use(setCORSHeaders); - -routeConfig(app, [ - new UsersController(settings.usersRepository), - new WebhookController(settings.webhookRepository), -]); - -eventsV1.loadViews(app); -api.loadViews(app); - -const noRouteMiddleware: Middleware = ( - request: $Request, - response: $Response, -): mixed => response.sendStatus(404); - -app.use(noRouteMiddleware); - - -console.log(`Starting server, listening on ${NODE_PORT}`); -app.listen(NODE_PORT); +const app = makeApp(settings); const deviceServer = new DeviceServer({ coreKeysDir: settings.coreKeysDir, @@ -127,11 +43,10 @@ const deviceServer = new DeviceServer({ serverKeyPassFile: settings.serverKeyPassFile, }); -// TODO wny do we need next line? (Anton Puko) global.server = deviceServer; deviceServer.start(); - +app.listen(NODE_PORT, (): void => console.log(`express server started on port ${NODE_PORT}`)); utilities.getIPAddresses().forEach((ip: string): void => - console.log(`Your server IP address is: ${ip}`), + console.log(`Your device server IP address is: ${ip}`), ); diff --git a/src/settings.js b/src/settings.js index 8b29e5ca..74fe8ffd 100644 --- a/src/settings.js +++ b/src/settings.js @@ -23,7 +23,7 @@ import path from 'path'; import WebhookFileRepository from './lib/repository/WebhookFileRepository'; import UsersFileRepository from './lib/repository/UsersFileRepository'; -const settings = { +export default { accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, @@ -31,9 +31,10 @@ const settings = { coreRequestTimeout: 30000, coreSignalTimeout: 30000, isCoreOnlineTimeout: 2000, + loginRoute: '/oauth/token', + logRequests: true, maxHooksPerDevice: 10, maxHooksPerUser: 20, - loginRoute: '/oauth/token', webhookRepository: new WebhookFileRepository( path.join(__dirname, 'webhooks'), ), @@ -52,17 +53,3 @@ const settings = { PORT: 5683, HOST: "localhost", }; - -const testSettings = { - ...settings, - coreKeysDir: path.join(__dirname, '../test/__test_data__/core_keys'), - usersRepository: new UsersFileRepository( - path.join(__dirname, '../test/__test_data__/users'), - ), - webhookRepository: new WebhookFileRepository( - path.join(__dirname, '../test/__test_data__/webhooks'), - ), -}; - -export default process.env.NODE_ENV === 'test' ? testSettings : settings; - diff --git a/src/types.js b/src/types.js index c7608335..95f9fb49 100644 --- a/src/types.js +++ b/src/types.js @@ -62,3 +62,25 @@ export type Repository = { getById: (id: string) => TModel, update: (id: string, model: TModel) => TModel, }; + +export type Settings = { + accessTokenLifetime: number, + baseUrl: string, + coreFlashTimeout: number, + coreKeysDir: string, + coreRequestTimeout: number, + coreSignalTimeout: number, + cryptoSalt: string, + HOST: string, + isCoreOnlineTimeout: number, + loginRoute: string, + logRequests: boolean, + maxHooksPerDevice: number, + maxHooksPerUser: number, + PORT: number, + serverKeyFile: string, + serverKeyPassEnvVar: ?string, + serverKeyPassFile: ?string, + usersRepository: Repository<*>, + webhookRepository: Repository<*>, +}; diff --git a/test/UsersController.test.js b/test/UsersController.test.js index 1da77ca1..da570a15 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -5,9 +5,8 @@ import type { TokenObject } from '../src/types'; import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; - -import { app } from '../src/main'; -import settings from '../src/settings'; +import app from './testApp'; +import settings from './settings'; const USER_CREDENTIALS = { password: 'password', diff --git a/test/settings.js b/test/settings.js new file mode 100644 index 00000000..4d0097b7 --- /dev/null +++ b/test/settings.js @@ -0,0 +1,36 @@ +// @flow + +import path from 'path'; +import WebhookFileRepository from '../src/lib/repository/WebhookFileRepository'; +import UsersFileRepository from '../src/lib/repository/UsersFileRepository'; + +export default { + accessTokenLifetime: 7776000, // 90 days, + baseUrl: 'http://localhost', + coreFlashTimeout: 90000, + coreKeysDir: path.join(__dirname, '__test_data__/core_keys'), + coreRequestTimeout: 30000, + coreSignalTimeout: 30000, + isCoreOnlineTimeout: 2000, + loginRoute: '/oauth/token', + logRequests: false, + maxHooksPerDevice: 10, + maxHooksPerUser: 20, + usersRepository: new UsersFileRepository( + path.join(__dirname, '__test_data__/users'), + ), + webhookRepository: new WebhookFileRepository( + path.join(__dirname, '__test_data__/webhooks'), + ), + + /** + * Your server crypto keys! + */ + cryptoSalt: 'aes-128-cbc', + serverKeyFile: "default_key.pem", + serverKeyPassFile: null, + serverKeyPassEnvVar: null, + + PORT: 5683, + HOST: "localhost", +}; diff --git a/test/testApp.js b/test/testApp.js new file mode 100644 index 00000000..9e5cfc29 --- /dev/null +++ b/test/testApp.js @@ -0,0 +1,6 @@ +// @flow + +import makeApp from '../src/app'; +import settings from './settings'; + +export default makeApp(settings); From 4babcd83efaa284578391fc1c87efa8ebf145db0 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 7 Dec 2016 03:12:50 +0200 Subject: [PATCH 132/504] fix nit --- src/main.js | 4 ++-- test/testApp.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.js b/src/main.js index 62aa29d0..f2a6e9fe 100644 --- a/src/main.js +++ b/src/main.js @@ -7,7 +7,7 @@ import { } from 'spark-protocol'; import utilities from './lib/utilities'; import logger from './lib/logger'; -import makeApp from './app'; +import createApp from './app'; import settings from './settings'; const NODE_PORT = process.env.NODE_PORT || 8080; @@ -26,7 +26,7 @@ process.on('uncaughtException', (exception: Error) => { logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); -const app = makeApp(settings); +const app = createApp(settings); const deviceServer = new DeviceServer({ coreKeysDir: settings.coreKeysDir, diff --git a/test/testApp.js b/test/testApp.js index 9e5cfc29..9ad57cca 100644 --- a/test/testApp.js +++ b/test/testApp.js @@ -1,6 +1,6 @@ // @flow -import makeApp from '../src/app'; +import createApp from '../src/app'; import settings from './settings'; -export default makeApp(settings); +export default createApp(settings); From 1d8207d6579c18b86e6f11277aaf35aa8621fe7f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 7 Dec 2016 18:48:48 +0200 Subject: [PATCH 133/504] add webhook validation --- src/lib/controllers/WebhookController.js | 45 ++++++++++++++++----- src/lib/repository/WebhookFileRepository.js | 5 +++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index 4199830d..f3731f50 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -7,6 +7,18 @@ import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; + +const validateWebhookModel = (webhook: Webhook): ?Error => { + if (!webhook.event) { + return new Error('no event name provided'); + } + if (!webhook.url) { + return new Error('no url provided'); + } + + return null; +}; + class WebhookController extends Controller { _webhookRepository: Repository; @@ -31,15 +43,30 @@ class WebhookController extends Controller { @httpVerb('post') @route('/v1/webhooks') post(model: Webhook) { - const newWebhook = this._webhookRepository.create(model); - return this.ok({ - created_at: newWebhook.created_at, - event: newWebhook.event, - hookUrl: settings.baseUrl + '/v1/webhooks/' + newWebhook.id, - id: newWebhook.id, - ok: true, - url: newWebhook.url, - }); + try { + const validateError = validateWebhookModel(model); + if (validateError) { + throw validateError; + } + + const isEventInUse = + this._webhookRepository.isEventInUse(model.event); + + if (isEventInUse) { + throw new Error(`event ${model.event} is in use`); + } + + const newWebhook = this._webhookRepository.create(model); + return this.ok({ + created_at: newWebhook.created_at, + event: newWebhook.event, + id: newWebhook.id, + ok: true, + url: newWebhook.url, + }); + } catch (error) { + return this.bad(error.message); + } } @httpVerb('delete') diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 9d8c262c..444df1bb 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -33,6 +33,11 @@ class WebhookFileRepository { getById = (id: string): Webhook => this._fileManager.getFile(`${id}.json`); + + isEventInUse = (event: string): boolean => + this.getAll().some((webhook: Webhook): boolean => + webhook.event === event, + ); } export default WebhookFileRepository; From d8a8a8d7d6cf5a55b380a84aa68cee127ea507a1 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 7 Dec 2016 18:49:07 +0200 Subject: [PATCH 134/504] add webhook tests --- test/WebhooksController.test.js | 144 ++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 test/WebhooksController.test.js diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js new file mode 100644 index 00000000..5f180bea --- /dev/null +++ b/test/WebhooksController.test.js @@ -0,0 +1,144 @@ +// @flow + +import type { Webhook } from '../src/types'; + + +import test from 'ava'; +import request from 'supertest-as-promised'; +import ouathClients from '../src/oauthClients.json'; +import app from './testApp'; +import settings from './settings'; + +const USER_CREDENTIALS = { + password: 'password', + username: 'webhookTestUser@test.com', +}; + +const WEBHOOK_MODEL = { + event: 'testEvent', + url: 'http://webhooktest.com/', +}; + +let testUser; +let userToken; +let testWebhook; + +test.before(async() => { + const userResponse = await request(app) + .post('/v1/users') + .send(USER_CREDENTIALS); + + testUser = userResponse.body; + + const tokenResponse = await request(app) + .post('/oauth/token') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + client_id: ouathClients[0].clientId, + client_secret: ouathClients[0].clientSecret, + grant_type: 'password', + password: USER_CREDENTIALS.password, + username: USER_CREDENTIALS.username, + }); + + userToken = tokenResponse.body.access_token; +}); + +test.serial('should create a new webhook object', async t => { + const response = await request(app) + .post('/v1/webhooks') + .query({ access_token: userToken }) + .send({ + event: WEBHOOK_MODEL.event, + url: WEBHOOK_MODEL.url, + }); + + testWebhook = response.body; + + t.is(response.status, 200); + t.truthy(testWebhook.id && testWebhook.event && testWebhook.url); +}); + +test('should throw an error if event isn\'t provided', async t => { + const response = await request(app) + .post('/v1/webhooks') + .query({ access_token: userToken }) + .send({ + url: WEBHOOK_MODEL.url, + }); + + t.is(response.status, 400); + t.is(response.body.message, 'no event name provided'); +}); + +test('should throw an error if url isn\'t provided', async t => { + const response = await request(app) + .post('/v1/webhooks') + .query({ access_token: userToken }) + .send({ + event: WEBHOOK_MODEL.event, + }); + + t.is(response.status, 400); + t.is(response.body.message, 'no url provided'); +}); + +test.serial('should throw an error if event is in use', async t => { + const response = await request(app) + .post('/v1/webhooks') + .query({ access_token: userToken }) + .send({ + event: WEBHOOK_MODEL.event, + url: WEBHOOK_MODEL.url, + }); + + t.is(response.status, 400); + t.is(response.body.message, `event ${WEBHOOK_MODEL.event} is in use`); +}); + +test.serial('should return all webhooks', async t => { + const response = await request(app) + .get('/v1/webhooks') + .query({ access_token: userToken }); + + const webhooks = response.body; + + t.is(response.status, 200); + t.truthy(Array.isArray(webhooks) && webhooks.length > 0); +}); + +test.serial('should return webhook object by id', async t => { + const response = await request(app) + .get(`/v1/webhooks/${testWebhook.id}`) + .query({ access_token: userToken }); + + t.is(response.status, 200); + t.is(testWebhook.id, response.body.id); + t.is(testWebhook.event, response.body.event); + t.is(testWebhook.url, response.body.url); +}); + +test.serial('should delete webhook', async t => { + const deleteResponse = await request(app) + .delete(`/v1/webhooks/${testWebhook.id}`) + .query({ access_token: userToken }); + + t.is(deleteResponse.status, 200); + + const allWebhooksResponse = await request(app) + .get('/v1/webhooks') + .query({ access_token: userToken }); + + t.is(allWebhooksResponse.status, 200); + + const webhooks = allWebhooksResponse.body; + + t.falsy(webhooks.some((webhook: Webhook): boolean => + webhook.id === testWebhook.id, + )); +}); + +test.after.always(() => { + settings.webhookRepository.delete(testWebhook.id); + settings.usersRepository.deleteById(testUser.id); +}); From 7f70944e851a5a92a7f5a0f2cf0b444e0796d471 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 7 Dec 2016 19:31:09 +0200 Subject: [PATCH 135/504] add requestType to required webhook props; remove event uniq checking --- src/lib/controllers/WebhookController.js | 18 +++++++++-------- src/lib/repository/WebhookFileRepository.js | 4 ---- src/types.js | 2 ++ test/WebhooksController.test.js | 22 +++++++++++++++++++-- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index f3731f50..c6fa9099 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -1,12 +1,15 @@ // @flow -import type { Repository, Webhook } from '../../types'; +import type { Repository, Webhook, WebhookRequestType } from '../../types'; import settings from '../../settings'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; +const REQUEST_TYPES: Array = [ + 'DELETE', 'GET', 'POST', 'PUT', +]; const validateWebhookModel = (webhook: Webhook): ?Error => { if (!webhook.event) { @@ -15,6 +18,12 @@ const validateWebhookModel = (webhook: Webhook): ?Error => { if (!webhook.url) { return new Error('no url provided'); } + if (!webhook.requestType) { + return new Error('no requestType provided'); + } + if (!REQUEST_TYPES.includes(webhook.requestType)) { + return new Error('wrong requestType'); + } return null; }; @@ -49,13 +58,6 @@ class WebhookController extends Controller { throw validateError; } - const isEventInUse = - this._webhookRepository.isEventInUse(model.event); - - if (isEventInUse) { - throw new Error(`event ${model.event} is in use`); - } - const newWebhook = this._webhookRepository.create(model); return this.ok({ created_at: newWebhook.created_at, diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 444df1bb..45a708cf 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -34,10 +34,6 @@ class WebhookFileRepository { getById = (id: string): Webhook => this._fileManager.getFile(`${id}.json`); - isEventInUse = (event: string): boolean => - this.getAll().some((webhook: Webhook): boolean => - webhook.event === event, - ); } export default WebhookFileRepository; diff --git a/src/types.js b/src/types.js index 95f9fb49..aa13a1fb 100644 --- a/src/types.js +++ b/src/types.js @@ -13,6 +13,8 @@ export type Webhook = { url: string, }; +export type WebhookRequestType = 'DELETE' | 'GET' | 'POST' | 'PUT'; + export type Client = { clientId: string, diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index 5f180bea..2441fdc2 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -16,6 +16,7 @@ const USER_CREDENTIALS = { const WEBHOOK_MODEL = { event: 'testEvent', + requestType: 'GET', url: 'http://webhooktest.com/', }; @@ -50,6 +51,7 @@ test.serial('should create a new webhook object', async t => { .query({ access_token: userToken }) .send({ event: WEBHOOK_MODEL.event, + requestType: WEBHOOK_MODEL.requestType, url: WEBHOOK_MODEL.url, }); @@ -64,6 +66,7 @@ test('should throw an error if event isn\'t provided', async t => { .post('/v1/webhooks') .query({ access_token: userToken }) .send({ + requestType: WEBHOOK_MODEL.requestType, url: WEBHOOK_MODEL.url, }); @@ -77,13 +80,14 @@ test('should throw an error if url isn\'t provided', async t => { .query({ access_token: userToken }) .send({ event: WEBHOOK_MODEL.event, + requestType: WEBHOOK_MODEL.requestType, }); t.is(response.status, 400); t.is(response.body.message, 'no url provided'); }); -test.serial('should throw an error if event is in use', async t => { +test('should throw an error if requestType isn\'t provided', async t => { const response = await request(app) .post('/v1/webhooks') .query({ access_token: userToken }) @@ -93,7 +97,21 @@ test.serial('should throw an error if event is in use', async t => { }); t.is(response.status, 400); - t.is(response.body.message, `event ${WEBHOOK_MODEL.event} is in use`); + t.is(response.body.message, 'no requestType provided'); +}); + +test('should throw an error if requestType is wrong', async t => { + const response = await request(app) + .post('/v1/webhooks') + .query({ access_token: userToken }) + .send({ + event: WEBHOOK_MODEL.event, + requestType: 'some random value', + url: WEBHOOK_MODEL.url, + }); + + t.is(response.status, 400); + t.is(response.body.message, 'wrong requestType'); }); test.serial('should return all webhooks', async t => { From 4cd5d4e0d1016913f4ed1e63487829203051824e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 8 Dec 2016 02:17:28 +0200 Subject: [PATCH 136/504] clean flow, add WebhookMutator --- src/lib/OAuthModel.js | 28 ++++++++++------- src/lib/controllers/Controller.js | 23 +++++++------- src/lib/controllers/UsersController.js | 15 +++++---- src/lib/controllers/WebhookController.js | 20 +++++++----- src/lib/decorators/types.js | 4 ++- src/lib/repository/UsersFileRepository.js | 30 ++++++++---------- src/lib/repository/WebhookFileRepository.js | 2 +- src/types.js | 35 +++++++++++++++++++-- test/UsersController.test.js | 6 ++-- test/WebhooksController.test.js | 11 +++---- 10 files changed, 106 insertions(+), 68 deletions(-) diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index 01956a61..a1e70e47 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -1,24 +1,30 @@ // @flow -import type { Client, Repository, User } from '../types'; +import type { + Client, + TokenObject, + User, + UsersRepository, +} from '../types'; import ouathClients from '../oauthClients.json'; class OauthModel { - _usersRepository: Repository; + _usersRepository: UsersRepository; - constructor(usersRepository: Repository) { + constructor(usersRepository: UsersRepository) { this._usersRepository = usersRepository; } - getAccessToken = (bearerToken: string) => { + getAccessToken = (bearerToken: string): ?Object => { const user = this._usersRepository.getByAccessToken(bearerToken); if (!user) { - return false; + return null; } - const userTokenObject = user.accessTokens.find((tokenObject) => - tokenObject.accessToken === bearerToken, + const userTokenObject = user.accessTokens.find( + (tokenObject: TokenObject): boolean => + tokenObject.accessToken === bearerToken, ); return { @@ -32,11 +38,11 @@ class OauthModel { client.clientId === clientId && client.clientSecret === clientSecret, ); - getUser = async (username: string, password: string): User => { - return await this._usersRepository.validateLogin(username, password); - }; + getUser = async (username: string, password: string): Promise => + await this._usersRepository.validateLogin(username, password); + - saveToken = (tokenObject, client, user) => { + saveToken = (tokenObject: TokenObject, client: Client, user: User): Object => { this._usersRepository.saveAccessToken(user.id, tokenObject); return { accessToken: tokenObject.accessToken, diff --git a/src/lib/controllers/Controller.js b/src/lib/controllers/Controller.js index e0b9aa38..391db23b 100644 --- a/src/lib/controllers/Controller.js +++ b/src/lib/controllers/Controller.js @@ -1,15 +1,14 @@ +// @flow + +// todo annotate better export default class Controller { - bad(message) { - return { - data: {message}, - status: 400, - }; - } + bad = (message: string): Object => ({ + data: { message }, + status: 400, + }); - ok(output) { - return { - data: output, - status: 200, - }; - } + ok = (output?: Object): Object => ({ + data: output, + status: 200, + }); } diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index 263cf3e0..35b60b40 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -1,6 +1,9 @@ // @flow -import type { Repository, User, UserCredentials } from '../../types'; +import type { + UserCredentials, + UsersRepository, +} from '../../types'; import basicAuthParser from 'basic-auth-parser'; import Controller from './Controller'; @@ -9,9 +12,9 @@ import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; class UsersController extends Controller { - _usersRepository: Repository; + _usersRepository: UsersRepository; - constructor(usersRepository: Repository) { + constructor(usersRepository: UsersRepository) { super(); this._usersRepository = usersRepository; } @@ -19,7 +22,7 @@ class UsersController extends Controller { @httpVerb('post') @route('/v1/users') @anonymous() - async createUser(userCredentials: UserCredentials) { + async createUser(userCredentials: UserCredentials): Promise { try { const isUserNameInUse = this._usersRepository.isUserNameInUse(userCredentials.username); @@ -38,7 +41,7 @@ class UsersController extends Controller { @httpVerb('delete') @route('/v1/access_tokens/:token') @anonymous() - async deleteAccessToken(token: string) { + async deleteAccessToken(token: string): Promise { try { const { username, password } = basicAuthParser(this.request.get('authorization')); const user = await this._usersRepository.validateLogin(username, password); @@ -54,7 +57,7 @@ class UsersController extends Controller { @httpVerb('get') @route('/v1/access_tokens') @anonymous() - async getAccessTokens() { + async getAccessTokens(): Promise { try { const { username, password } = basicAuthParser(this.request.get('authorization')); const user = await this._usersRepository.validateLogin(username, password); diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index c6fa9099..2a22326c 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -1,13 +1,17 @@ // @flow -import type { Repository, Webhook, WebhookRequestType } from '../../types'; +import type { + Repository, + RequestType, + Webhook, + WebhookMutator, +} from '../../types'; -import settings from '../../settings'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; -const REQUEST_TYPES: Array = [ +const REQUEST_TYPES: Array = [ 'DELETE', 'GET', 'POST', 'PUT', ]; @@ -39,19 +43,19 @@ class WebhookController extends Controller { @httpVerb('get') @route('/v1/webhooks') - get() { + getAll(): Object { return this.ok(this._webhookRepository.getAll()); } @httpVerb('get') @route('/v1/webhooks/:webhookId') - getByWebhookId(webhookId: string) { + getById(webhookId: string): Object { return this.ok(this._webhookRepository.getById(webhookId)); } @httpVerb('post') @route('/v1/webhooks') - post(model: Webhook) { + create(model: WebhookMutator): Object { try { const validateError = validateWebhookModel(model); if (validateError) { @@ -73,8 +77,8 @@ class WebhookController extends Controller { @httpVerb('delete') @route('/v1/webhooks/:webhookId') - delete(webhookId: string) { - this._webhookRepository.delete(webhookId); + deleteById(webhookId: string): Object { + this._webhookRepository.deleteById(webhookId); return this.ok(); } } diff --git a/src/lib/decorators/types.js b/src/lib/decorators/types.js index 725fa610..e8ebeb26 100644 --- a/src/lib/decorators/types.js +++ b/src/lib/decorators/types.js @@ -1,3 +1,5 @@ +// @flow + export type Decorator = ( target: Object, name: string, @@ -8,7 +10,7 @@ export type HttpVerb = 'checkout' | 'connect' | 'copy' | - 'delete' | + 'deleteById' | 'head' | 'get' | 'lock' | diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UsersFileRepository.js index fe3c4a3d..8615fc83 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -5,7 +5,6 @@ import type { TokenObject, User, UserCredentials } from '../../types'; import { FileManager, uuid } from 'spark-protocol'; import PasswordHasher from '../PasswordHasher'; -// todo change class methods style to arrow functions. class UsersFileRepository { _fileManager: FileManager; @@ -13,7 +12,7 @@ class UsersFileRepository { this._fileManager = new FileManager(path); } - create = async (userCredentials: UserCredentials): User => { + create = async (userCredentials: UserCredentials): Promise => { const { username, password } = userCredentials; const salt = await PasswordHasher.generateSalt(); @@ -33,19 +32,16 @@ class UsersFileRepository { return modelToSave; }; - getAll(): Array { - return this._fileManager.getAllData(); - } + getAll = (): Array => + this._fileManager.getAllData(); - getById(id: string): User { - return this._fileManager.getFile(id + '.json'); - } + getById = (id: string): User => + this._fileManager.getFile(`${id}.json`); - getByUsername(username: string) { - return this.getAll().find((user: User) => user.username === username); - } + getByUsername = (username: string): ?User => + this.getAll().find((user: User): boolean => user.username === username); - async validateLogin(username: string, password: string) { + async validateLogin(username: string, password: string): User { try { const user = this.getByUsername(username); if (!user) { @@ -70,7 +66,7 @@ class UsersFileRepository { ), ); - deleteAccessToken(user: User, token: string) { + deleteAccessToken = (user: User, token: string) => { const userToSave = { ...user, accessTokens: user.accessTokens.filter( @@ -80,18 +76,18 @@ class UsersFileRepository { }; this._fileManager.writeFile(`${user.id}.json`, userToSave); - } + }; - deleteById(id: string) { + deleteById = (id: string): void => this._fileManager.deleteFile(`${id}.json`); - } + isUserNameInUse = (username: string): boolean => this.getAll().some((user: User): boolean => user.username === username, ); - saveAccessToken(userId: string, tokenObject: TokenObject) { + saveAccessToken = (userId: string, tokenObject: TokenObject) => { const user = this.getById(userId); const userToSave = { ...user, diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 45a708cf..c1989127 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -25,7 +25,7 @@ class WebhookFileRepository { return modelToSave; }; - delete = (id: string): void => + deleteById = (id: string): void => this._fileManager.deleteFile(`${id}.json`); getAll = (): Array => diff --git a/src/types.js b/src/types.js index aa13a1fb..473ad413 100644 --- a/src/types.js +++ b/src/types.js @@ -1,3 +1,5 @@ +// @flow + export type Webhook = { deviceID: string, event: string, @@ -7,14 +9,32 @@ export type Webhook = { mydevices: boolean, productIdOrSlug: ?string, rejectUnauthorized: boolean, - requestType: string, + requestType: RequestType, responseTemplate: ?string, responseTopic: string, url: string, }; -export type WebhookRequestType = 'DELETE' | 'GET' | 'POST' | 'PUT'; +export type WebhookMutator = { + auth?: { Authorization: string }, + deviceID?: boolean, + errorResponseTopic?: string, + event: string, + form?: { [key: string]: Object }, + headers?:{ [key: string]: string }, + json?: { [key: string]: Object }, + mydevices?: boolean, + noDefaults?: boolean, + productIdOrSlug?: string, + query?: { [key: string]: Object }, + rejectUnauthorized?: boolean, + requestType: RequestType, + responseTemplate?: string, + responseTopic?: string, + url: string, +}; +export type RequestType = 'DELETE' | 'GET' | 'POST' | 'PUT'; export type Client = { clientId: string, @@ -59,12 +79,21 @@ export type UserCredentials = { export type Repository = { create: (id: string, model: TModel) => TModel, - delete: (id: string) => void, + deleteById: (id: string) => void, getAll: () => Array, getById: (id: string) => TModel, update: (id: string, model: TModel) => TModel, }; +export type UsersRepository = Repository & { + deleteAccessToken: (accessToken: string) => void, + getByAccessToken: (accessToken: string) => User, + getByUsername: (username: string) => ?User, + isUserNameInUse: (username: string) => boolean, + saveAccessToken: (accessToken: string) => void, + validateLogin: (username: string, password: string) => User, +}; + export type Settings = { accessTokenLifetime: number, baseUrl: string, diff --git a/test/UsersController.test.js b/test/UsersController.test.js index da570a15..9725a9be 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -1,6 +1,6 @@ // @flow -import type { TokenObject } from '../src/types'; +import type { TokenObject, UserCredentials } from '../src/types'; import test from 'ava'; import request from 'supertest-as-promised'; @@ -8,7 +8,7 @@ import ouathClients from '../src/oauthClients.json'; import app from './testApp'; import settings from './settings'; -const USER_CREDENTIALS = { +const USER_CREDENTIALS: UserCredentials = { password: 'password', username: 'newUser@test.com', }; @@ -67,7 +67,7 @@ test.serial('should return all access tokens for the user', async t => { }); -test.serial('should delete access token for the user', async t => { +test.serial('should deleteById access token for the user', async t => { const deleteResponse = await request(app) .delete(`/v1/access_tokens/${userToken}`) .auth(USER_CREDENTIALS.username, USER_CREDENTIALS.password); diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index 2441fdc2..90a7eac4 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -1,7 +1,6 @@ // @flow -import type { Webhook } from '../src/types'; - +import type { Webhook, WebhookMutator } from '../src/types'; import test from 'ava'; import request from 'supertest-as-promised'; @@ -14,7 +13,7 @@ const USER_CREDENTIALS = { username: 'webhookTestUser@test.com', }; -const WEBHOOK_MODEL = { +const WEBHOOK_MODEL: WebhookMutator = { event: 'testEvent', requestType: 'GET', url: 'http://webhooktest.com/', @@ -24,7 +23,7 @@ let testUser; let userToken; let testWebhook; -test.before(async() => { +test.before(async () => { const userResponse = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); @@ -136,7 +135,7 @@ test.serial('should return webhook object by id', async t => { t.is(testWebhook.url, response.body.url); }); -test.serial('should delete webhook', async t => { +test.serial('should deleteById webhook', async t => { const deleteResponse = await request(app) .delete(`/v1/webhooks/${testWebhook.id}`) .query({ access_token: userToken }); @@ -157,6 +156,6 @@ test.serial('should delete webhook', async t => { }); test.after.always(() => { - settings.webhookRepository.delete(testWebhook.id); + settings.webhookRepository.deleteById(testWebhook.id); settings.usersRepository.deleteById(testUser.id); }); From 3bc997dee53220974d18d65b75e5bbf3346cec1c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 8 Dec 2016 15:07:55 +0200 Subject: [PATCH 137/504] flow fixes --- src/lib/controllers/Controller.js | 7 +++-- src/lib/controllers/UsersController.js | 6 ++-- src/lib/controllers/WebhookController.js | 24 ++++++++-------- src/lib/controllers/types.js | 10 +++++++ src/lib/repository/UsersFileRepository.js | 4 +-- src/lib/repository/WebhookFileRepository.js | 5 ++-- src/types.js | 31 ++++++++------------- test/WebhooksController.test.js | 2 +- 8 files changed, 45 insertions(+), 44 deletions(-) create mode 100644 src/lib/controllers/types.js diff --git a/src/lib/controllers/Controller.js b/src/lib/controllers/Controller.js index 391db23b..5d093689 100644 --- a/src/lib/controllers/Controller.js +++ b/src/lib/controllers/Controller.js @@ -1,13 +1,14 @@ // @flow -// todo annotate better +import type { HttpResult } from './types'; + export default class Controller { - bad = (message: string): Object => ({ + bad = (message: string): HttpResult<*> => ({ data: { message }, status: 400, }); - ok = (output?: Object): Object => ({ + ok = (output?: TType): HttpResult => ({ data: output, status: 200, }); diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index 35b60b40..8bcaec57 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -22,7 +22,7 @@ class UsersController extends Controller { @httpVerb('post') @route('/v1/users') @anonymous() - async createUser(userCredentials: UserCredentials): Promise { + async createUser(userCredentials: UserCredentials): Promise<*> { try { const isUserNameInUse = this._usersRepository.isUserNameInUse(userCredentials.username); @@ -41,7 +41,7 @@ class UsersController extends Controller { @httpVerb('delete') @route('/v1/access_tokens/:token') @anonymous() - async deleteAccessToken(token: string): Promise { + async deleteAccessToken(token: string): Promise<*> { try { const { username, password } = basicAuthParser(this.request.get('authorization')); const user = await this._usersRepository.validateLogin(username, password); @@ -57,7 +57,7 @@ class UsersController extends Controller { @httpVerb('get') @route('/v1/access_tokens') @anonymous() - async getAccessTokens(): Promise { + async getAccessTokens(): Promise<*> { try { const { username, password } = basicAuthParser(this.request.get('authorization')); const user = await this._usersRepository.validateLogin(username, password); diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index 2a22326c..ad49939e 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -15,17 +15,17 @@ const REQUEST_TYPES: Array = [ 'DELETE', 'GET', 'POST', 'PUT', ]; -const validateWebhookModel = (webhook: Webhook): ?Error => { - if (!webhook.event) { +const validateWebhookMutator = (webhookMutator: WebhookMutator): ?Error => { + if (!webhookMutator.event) { return new Error('no event name provided'); } - if (!webhook.url) { + if (!webhookMutator.url) { return new Error('no url provided'); } - if (!webhook.requestType) { + if (!webhookMutator.requestType) { return new Error('no requestType provided'); } - if (!REQUEST_TYPES.includes(webhook.requestType)) { + if (!REQUEST_TYPES.includes(webhookMutator.requestType)) { return new Error('wrong requestType'); } @@ -33,9 +33,9 @@ const validateWebhookModel = (webhook: Webhook): ?Error => { }; class WebhookController extends Controller { - _webhookRepository: Repository; + _webhookRepository: Repository; - constructor(webhookRepository: Repository) { + constructor(webhookRepository: Repository) { super(); this._webhookRepository = webhookRepository; @@ -43,21 +43,21 @@ class WebhookController extends Controller { @httpVerb('get') @route('/v1/webhooks') - getAll(): Object { + async getAll(): Promise<*> { return this.ok(this._webhookRepository.getAll()); } @httpVerb('get') @route('/v1/webhooks/:webhookId') - getById(webhookId: string): Object { + async getById(webhookId: string): Promise<*> { return this.ok(this._webhookRepository.getById(webhookId)); } @httpVerb('post') @route('/v1/webhooks') - create(model: WebhookMutator): Object { + async create(model: WebhookMutator): Promise<*> { try { - const validateError = validateWebhookModel(model); + const validateError = validateWebhookMutator(model); if (validateError) { throw validateError; } @@ -77,7 +77,7 @@ class WebhookController extends Controller { @httpVerb('delete') @route('/v1/webhooks/:webhookId') - deleteById(webhookId: string): Object { + async deleteById(webhookId: string): Promise<*> { this._webhookRepository.deleteById(webhookId); return this.ok(); } diff --git a/src/lib/controllers/types.js b/src/lib/controllers/types.js new file mode 100644 index 00000000..725c2895 --- /dev/null +++ b/src/lib/controllers/types.js @@ -0,0 +1,10 @@ +// @flow + + +export type HttpResult = { + data: ?TType, + status: number, +} | { + data: ?string, + status: number, +}; diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UsersFileRepository.js index 8615fc83..57f4c808 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -41,7 +41,7 @@ class UsersFileRepository { getByUsername = (username: string): ?User => this.getAll().find((user: User): boolean => user.username === username); - async validateLogin(username: string, password: string): User { + async validateLogin(username: string, password: string): Promise { try { const user = this.getByUsername(username); if (!user) { @@ -60,7 +60,7 @@ class UsersFileRepository { } getByAccessToken = (accessToken: string): ?User => - this.getAll().find((user: User): User => + this.getAll().find((user: User): boolean => user.accessTokens.some((tokenObject: TokenObject): boolean => tokenObject.accessToken === accessToken, ), diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index c1989127..b819dbfe 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,6 +1,6 @@ // @flow -import type { Webhook } from '../../types'; +import type { Webhook, WebhookMutator } from '../../types'; import { FileManager, uuid } from 'spark-protocol'; @@ -11,7 +11,7 @@ class WebhookFileRepository { this._fileManager = new FileManager(path); } - create = (model: Webhook): Webhook => { + create = (model: WebhookMutator): Webhook => { const modelToSave = { ...model, created_at: new Date(), @@ -33,7 +33,6 @@ class WebhookFileRepository { getById = (id: string): Webhook => this._fileManager.getFile(`${id}.json`); - } export default WebhookFileRepository; diff --git a/src/types.js b/src/types.js index 473ad413..cf884964 100644 --- a/src/types.js +++ b/src/types.js @@ -1,27 +1,17 @@ // @flow -export type Webhook = { - deviceID: string, - event: string, - errorResponseTopic: string, +export type Webhook = WebhookMutator & { + created_at: Date, id: string, - json: {[key: string]: Object}, - mydevices: boolean, - productIdOrSlug: ?string, - rejectUnauthorized: boolean, - requestType: RequestType, - responseTemplate: ?string, - responseTopic: string, - url: string, }; export type WebhookMutator = { auth?: { Authorization: string }, - deviceID?: boolean, + deviceID?: string, errorResponseTopic?: string, event: string, form?: { [key: string]: Object }, - headers?:{ [key: string]: string }, + headers?: { [key: string]: string }, json?: { [key: string]: Object }, mydevices?: boolean, noDefaults?: boolean, @@ -69,6 +59,7 @@ export type User = { created_at: Date, id: string, passwordHash: string, + salt: string, username: string, }; @@ -77,16 +68,16 @@ export type UserCredentials = { password: string, }; -export type Repository = { - create: (id: string, model: TModel) => TModel, +export type Repository = { + create: (model: TMutator) => TModel, deleteById: (id: string) => void, getAll: () => Array, getById: (id: string) => TModel, update: (id: string, model: TModel) => TModel, }; -export type UsersRepository = Repository & { - deleteAccessToken: (accessToken: string) => void, +export type UsersRepository = Repository & { + deleteAccessToken: (user: User, accessToken: string) => void, getByAccessToken: (accessToken: string) => User, getByUsername: (username: string) => ?User, isUserNameInUse: (username: string) => boolean, @@ -112,6 +103,6 @@ export type Settings = { serverKeyFile: string, serverKeyPassEnvVar: ?string, serverKeyPassFile: ?string, - usersRepository: Repository<*>, - webhookRepository: Repository<*>, + usersRepository: Repository<*, *>, + webhookRepository: Repository<*, *>, }; diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index 90a7eac4..528deda1 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -135,7 +135,7 @@ test.serial('should return webhook object by id', async t => { t.is(testWebhook.url, response.body.url); }); -test.serial('should deleteById webhook', async t => { +test.serial('should delete webhook', async t => { const deleteResponse = await request(app) .delete(`/v1/webhooks/${testWebhook.id}`) .query({ access_token: userToken }); From 1825c21a7df07a197c016e65848c271f46a64d53 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 8 Dec 2016 17:35:30 +0200 Subject: [PATCH 138/504] add injected controller props to base Controller class --- src/lib/controllers/Controller.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/controllers/Controller.js b/src/lib/controllers/Controller.js index 5d093689..753fc545 100644 --- a/src/lib/controllers/Controller.js +++ b/src/lib/controllers/Controller.js @@ -1,8 +1,14 @@ // @flow +import type { $Request, $Response } from 'express'; +import type { User } from '../../types'; import type { HttpResult } from './types'; export default class Controller { + user: User; + request: $Request; + response: $Response; + bad = (message: string): HttpResult<*> => ({ data: { message }, status: 400, From 9d602a3e6dbf96c532279cb8a99f760bc0d50712 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 8 Dec 2016 17:47:14 +0200 Subject: [PATCH 139/504] few flow fixes --- src/lib/OAuthModel.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index a1e70e47..20ac323f 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -27,6 +27,10 @@ class OauthModel { tokenObject.accessToken === bearerToken, ); + if (!userTokenObject) { + return null; + } + return { accessToken: userTokenObject.accessToken, user, @@ -34,7 +38,7 @@ class OauthModel { }; getClient = (clientId: string, clientSecret: string): Client => - ouathClients.find((client: Client): Client => + ouathClients.find((client: Client): boolean => client.clientId === clientId && client.clientSecret === clientSecret, ); @@ -52,7 +56,7 @@ class OauthModel { }; // todo figure out this function - validateScope = (user: User, scope) => scope; + validateScope = (user: User, client: Client, scope: string): string => scope; } export default OauthModel; From 6292453e9115fb95ff260668fe7a9dc4771e59c4 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 9 Dec 2016 16:39:01 +0200 Subject: [PATCH 140/504] scope temporary fix --- src/lib/OAuthModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index 20ac323f..acb35c1f 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -56,7 +56,7 @@ class OauthModel { }; // todo figure out this function - validateScope = (user: User, client: Client, scope: string): string => scope; + validateScope = (user: User, client: Client, scope: string): string => true; } export default OauthModel; From 3f790061b22e9541a1b730fa0f3cb55644a9cb69 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 10 Dec 2016 13:12:16 -0800 Subject: [PATCH 141/504] Starting to port over devices controller. --- flow-typed/npm/express_v4.x.x.js | 1 + package.json | 1 + src/lib/CoreController.js | 2 +- src/lib/controllers/DevicesController.js | 53 ++++++ src/lib/utilities.js | 22 +-- src/views/api_v1.js | 200 ++++++++++++----------- 6 files changed, 160 insertions(+), 119 deletions(-) create mode 100644 src/lib/controllers/DevicesController.js diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 0c907c2c..6954434f 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -163,6 +163,7 @@ declare class express$Application extends express$Router mixins events$EventEmit */ // get(name: string): mixed; set(name: string, value: mixed): mixed; + param(name: string, (req: express$Request, res: express$Response) => void): mixed; render(name: string, optionsOrFunction: {[name: string]: mixed}, callback: express$RenderCallback): void; } diff --git a/package.json b/package.json index 607c7cfb..a45ff9f8 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "eslint-plugin-import": "^2.2.0", "eslint-plugin-sorting": "^0.3.0", "express-oauth-server": "^2.0.0-b1", + "flow-bin": "^0.36.0", "nodemon": "^1.11.0", "rimraf": "^2.5.4", "superset": "^1.0.1", diff --git a/src/lib/CoreController.js b/src/lib/CoreController.js index 32bf7cea..d2b952b3 100644 --- a/src/lib/CoreController.js +++ b/src/lib/CoreController.js @@ -81,7 +81,7 @@ CoreController.prototype = { process.nextTick(function () { try { - //console.log("sending message with socketID" + that.socketID); + console.log("sending message with socketID" + that.socketID, msg); core.onApiMessage(that.socketID, msg); } catch (ex) { diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js new file mode 100644 index 00000000..dda362c2 --- /dev/null +++ b/src/lib/controllers/DevicesController.js @@ -0,0 +1,53 @@ +// @flow + +import type { Repository, Webhook } from '../../types'; + +import settings from '../../settings'; +import Controller from './Controller'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; + +class DevicesController extends Controller { + _webhookRepository: Repository; + + constructor(webhookRepository: Repository) { + super(); + + this._webhookRepository = webhookRepository; + } + + @httpVerb('get') + @route('/v1/devices') + get() { + return this.ok(this._webhookRepository.getAll()); + } + + @httpVerb('get') + @route('/v1/webhooks/:webhookId') + getByWebhookId(webhookId: string) { + return this.ok(this._webhookRepository.getById(webhookId)); + } + + @httpVerb('post') + @route('/v1/webhooks') + post(model: Webhook) { + const newWebhook = this._webhookRepository.create(model); + return this.ok({ + created_at: newWebhook.created_at, + event: newWebhook.event, + hookUrl: settings.baseUrl + '/v1/webhooks/' + newWebhook.id, + id: newWebhook.id, + ok: true, + url: newWebhook.url, + }); + } + + @httpVerb('delete') + @route('/v1/webhooks/:webhookId') + delete(webhookId: string) { + this._webhookRepository.delete(webhookId); + return this.ok(); + } +} + +export default DevicesController; diff --git a/src/lib/utilities.js b/src/lib/utilities.js index 84c7c37a..acd79fd2 100644 --- a/src/lib/utilities.js +++ b/src/lib/utilities.js @@ -24,26 +24,6 @@ var fs = require('fs'); var that; module.exports = that = { - - /** - * ensures the function in the provided scope - * @param fn - * @param scope - * @returns {Function} - */ - proxy: function (fn, scope) { - return function () { - try { - return fn.apply(scope, arguments); - } - catch (ex) { - logger.error(ex); - logger.error(ex.stack); - logger.log('error bubbled up ' + ex); - } - } - }, - /** * Surely there is a better way to do this. * NOTE! This function does NOT short-circuit when an in-equality is detected. This is @@ -379,4 +359,4 @@ module.exports = that = { }, foo: null -}; \ No newline at end of file +}; diff --git a/src/views/api_v1.js b/src/views/api_v1.js index c0676693..f44232c5 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -14,8 +14,12 @@ * along with this program. If not, see . * * You can download the source here: https://github.com/spark/spark-server +* +* @flow +* */ +import type { $Application, $Request, $Response } from 'express'; var settings = require('../settings.js'); var CoreController = require('../lib/CoreController.js'); @@ -26,7 +30,7 @@ var parallel = require('when/parallel'); var pipeline = require('when/pipeline'); var logger = require('../lib/logger.js'); -var utilities = require("../lib/utilities.js"); +var utilities = require('../lib/utilities.js'); var fs = require('fs'); var when = require('when'); @@ -43,11 +47,9 @@ var moment = require('moment'); */ var Api = { - loadViews: function (app) { - - //our middleware - app.param("coreid", Api.loadCore); + loadViews: function (app: $Application) { + app.param('coreid', Api.loadCore); //core functions / variables app.post('/v1/devices/:coreid/:func', Api.fn_call); @@ -66,22 +68,23 @@ var Api = { }, - getSocketID: function (userID) { - return userID + "_" + global._socket_counter++; + getSocketID: function (userID: string) { + return userID + '_' + global._socket_counter++; }, - getUserID: function (req) { - if (!req.user) { - logger.log("User obj was empty"); + _getUserID: function (req: $Request) { + const user = (req: any).user; + if (!user) { + logger.log('User obj was empty'); return null; } //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return req.user.id; + return user.id; }, - list_devices: function (req, res) { - var userid = Api.getUserID(req); - logger.log("ListDevices", { userID: userid }); + list_devices: function (req: $Request, res: $Response) { + var userid = Api._getUserID(req); + logger.log('ListDevices', { userID: userid }); //give me all the cores @@ -89,9 +92,9 @@ var Api = { devices = [], connected_promises = []; - for (var coreid in allCoreIDs) { + allCoreIDs.forEach(coreid => { if (!coreid) { - continue; + return; } var core = global.server.getCoreAttributes(coreid); @@ -103,15 +106,18 @@ var Api = { last_heard: null }; + // TODO: Handle this? + /* if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; + device['requires_deep_update'] = true; } - + */ devices.push(device); + console.log(device.id); connected_promises.push(Api.isDeviceOnline(userid, device.id)); - } + }); - logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); + logger.log('ListDevices... waiting for connected state to settle ', { userID: userid }); //switched 'done' to 'then' - threw an exception with 'done' here. when.settle(connected_promises).then(function (descriptors) { @@ -127,20 +133,20 @@ var Api = { }, get_core_attributes: function (req, res) { - var userid = Api.getUserID(req); + var userid = Api._getUserID(req); var socketID = Api.getSocketID(userid), coreID = req.coreID, socket = new CoreController(socketID); - logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); + logger.log('GetAttr', { coreID: coreID, userID: userid.toString() }); var objReady = parallel([ function () { return when.resolve(global.server.getCoreAttributes(coreID)); }, function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: 'Describe' }, { cmd: 'DescribeReturn' })); } ]); @@ -149,8 +155,8 @@ var Api = { try { if (!results || (results.length !== 2)) { - logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); - res.json(404, "Oops, I couldn't find that core"); + logger.error('get_core_attributes results was the wrong length ' + JSON.stringify(results)); + res.json(404, 'Oops, I couldn\'t find that core'); return; } @@ -161,8 +167,8 @@ var Api = { coreState = null; if (!doc || !doc.coreID) { - logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); - res.json(404, "Oops, I couldn't find that core"); + logger.error('get_core_attributes 404 error: ' + JSON.stringify(doc)); + res.json(404, 'Oops, I couldn\'t find that core'); return; } @@ -170,7 +176,7 @@ var Api = { coreState = descResult[1].state || {}; } if (!coreState) { - logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); + logger.error('get_core_attributes didn\'t get description: ' + JSON.stringify(descResult)); } var device = { @@ -184,14 +190,14 @@ var Api = { }; if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; + device['requires_deep_update'] = true; } res.json(device); } catch (ex) { - logger.error("get_core_attributes merge error: " + ex); - res.json(500, { Error: "get_core_attributes error: " + ex }); + logger.error('get_core_attributes merge error: ' + ex); + res.json(500, { Error: 'get_core_attributes error: ' + ex }); } }, null); @@ -201,17 +207,17 @@ var Api = { set_core_attributes: function (req, res) { var coreID = req.coreID; - var userid = Api.getUserID(req); + var userid = Api._getUserID(req); var promises = []; - logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); + logger.log('set_core_attributes', { coreID: coreID, userID: userid.toString() }); var coreName = req.body ? req.body.name : null; if (coreName !== null) { - logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); + logger.log('SetAttr', { coreID: coreID, userID: userid.toString(), name: coreName }); - global.server.setCoreAttribute(req.coreID, "name", coreName); + global.server.setCoreAttribute(req.coreID, 'name', coreName); promises.push(when.resolve({ ok: true, name: coreName })); } @@ -238,7 +244,7 @@ var Api = { promises.push(Api.flash_known_app_dfd(req)); } else { - promises.push(when.reject("Can't flash unknown app " + flashApp)); + promises.push(when.reject('Can\'t flash unknown app ' + flashApp)); } } } @@ -275,46 +281,46 @@ var Api = { ); } else { - logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); - res.json({error: "Nothing to do?"}); + logger.error('set_core_attributes - nothing to do?', { coreID: coreID, userID: userid.toString() }); + res.json({error: 'Nothing to do?'}); } }, - isDeviceOnline: function (userID, coreID) { + isDeviceOnline: function (userID: string, coreID: string) { var tmp = when.defer(); var socketID = Api.getSocketID(userID); var socket = new CoreController(socketID); var failTimer = setTimeout(function () { - logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); + logger.log('isDeviceOnline: Ping timed out ', { coreID: coreID }); socket.close(); - tmp.reject("Device is not connected"); + tmp.reject('Device is not connected'); }, settings.isCoreOnlineTimeout); //setup listener for response back from the device service - socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { + socket.listenFor(coreID, { cmd: 'Pong' }, function (sender, msg) { clearTimeout(failTimer); socket.close(); - logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); + logger.log('isDeviceOnline: Device service thinks it is online... ', { coreID: coreID }); if (msg && msg.online) { tmp.resolve(msg); } else { - tmp.reject(["Core isn't online", 404]); + tmp.reject(['Core isn\'t online', 404]); } }, true); - logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); + logger.log('isDeviceOnline: Pinging core... ', { coreID: coreID }); //send it along to the device service - if (!socket.send(coreID, { cmd: "Ping" })) { - tmp.reject("send failed"); + if (!socket.send(coreID, { cmd: 'Ping' })) { + tmp.reject('send failed'); } return tmp.promise; @@ -331,14 +337,14 @@ var Api = { //load core info! req.coreInfo = { - "last_app": "", - "last_heard": new Date(), - "connected": false, - "deviceID": req.coreID + 'last_app': '', + 'last_heard': new Date(), + 'connected': false, + 'deviceID': req.coreID }; //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api.getUserID(req); + var userid = Api._getUserID(req); var gotCore = utilities.deferredAny([ function () { var core = global.server.getCoreAttributes(req.coreID); @@ -378,21 +384,21 @@ var Api = { }, get_var: function (req, res) { - var userid = Api.getUserID(req); + var userid = Api._getUserID(req); var socketID = Api.getSocketID(userid), coreID = req.coreID, varName = req.params.var, format = req.params.format; - logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); + logger.log('GetVar', {coreID: coreID, userID: userid.toString()}); //send it along to the device service //and listen for a response back from the device service var socket = new CoreController(socketID); var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "GetVar", name: varName }, - { cmd: "VarReturn", name: varName }, + { cmd: 'GetVar', name: varName }, + { cmd: 'VarReturn', name: varName }, settings.coreRequestTimeout ); @@ -413,15 +419,15 @@ var Api = { msg.coreInfo = req.coreInfo; msg.coreInfo.connected = true; - if (format && (format === "raw")) { - return res.send("" + msg.result); + if (format && (format === 'raw')) { + return res.send('' + msg.result); } else { return res.json(msg); } }, function () { - res.json(408, {error: "Timed out."}); + res.json(408, {error: 'Timed out.'}); } ).ensure(function () { socket.close(); @@ -429,12 +435,12 @@ var Api = { }, fn_call: function (req, res) { - var user_id = Api.getUserID(req), + var user_id = Api._getUserID(req), coreID = req.coreID, funcName = req.params.func, format = req.params.format; - logger.log("FunCall", { coreID: coreID, user_id: user_id.toString() }); + logger.log('FunCall', { coreID: coreID, user_id: user_id.toString() }); var socketID = Api.getSocketID(user_id); var socket = new CoreController(socketID); @@ -443,10 +449,10 @@ var Api = { var args = req.body; delete args.access_token; - logger.log("FunCall - calling core ", { coreID: coreID, user_id: user_id.toString() }); + logger.log('FunCall - calling core ', { coreID: coreID, user_id: user_id.toString() }); var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "CallFn", name: funcName, args: args }, - { cmd: "FnReturn", name: funcName }, + { cmd: 'CallFn', name: funcName, args: args }, + { cmd: 'FnReturn', name: funcName }, settings.coreRequestTimeout ); @@ -457,11 +463,11 @@ var Api = { var sender = arr[0], msg = arr[1]; try { - //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); - if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { + //logger.log('FunCall - heard back ', { coreID: coreID, user_id: user_id.toString() }); + if (msg.error && (msg.error.indexOf('Unknown Function') >= 0)) { res.json(404, { ok: false, - error: "Function not found" + error: 'Function not found' }); } else if (msg.error !== null) { @@ -471,8 +477,8 @@ var Api = { }); } else { - if (format && (format === "raw")) { - res.send("" + msg.result); + if (format && (format === 'raw')) { + res.send('' + msg.result); } else { res.json({ @@ -486,47 +492,47 @@ var Api = { } } catch (ex) { - logger.error("FunCall handling resp error " + ex); + logger.error('FunCall handling resp error ' + ex); res.json(500, { ok: false, - error: "Error while api was rendering response" + error: 'Error while api was rendering response' }); } }, function () { - res.json(408, {error: "Timed out."}); + res.json(408, {error: 'Timed out.'}); } ).ensure(function () { socket.close(); }); - //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); + //socket.send(coreID, { cmd: 'CallFn', name: funcName, args: args }); // send the function call along to the device service }, /** - * Ask the core to start / stop the "RaiseYourHand" signal + * Ask the core to start / stop the 'RaiseYourHand' signal * @param req */ core_signal_dfd: function (req) { var tmp = when.defer(); - var userid = Api.getUserID(req), + var userid = Api._getUserID(req), socketID = Api.getSocketID(userid), coreID = req.coreID, showSignal = parseInt(req.body.signal); - logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); + logger.log('SignalCore', { coreID: coreID, userID: userid.toString()}); var socket = new CoreController(socketID); var failTimer = setTimeout(function () { socket.close(); - tmp.reject({error: "Timed out, didn't hear back"}); + tmp.reject({error: 'Timed out, didn\'t hear back'}); }, settings.coreSignalTimeout); //listen for a response back from the device service - socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, + socket.listenFor(coreID, { cmd: 'RaiseHandReturn'}, function () { clearTimeout(failTimer); socket.close(); @@ -540,14 +546,14 @@ var Api = { //send it along to the core via the device service - socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); + socket.send(coreID, { cmd: 'RaiseHand', args: { signal: showSignal } }); return tmp.promise; }, compile_and__or_flash_dfd: function (req) { var allDone = when.defer(); - var userid = Api.getUserID(req), + var userid = Api._getUserID(req), coreID = req.coreID; @@ -555,7 +561,7 @@ var Api = { // Did they pass us a source file or a binary file? // var hasSourceFiles = false; - var sourceExts = [".cpp", ".c", ".h", ".ino" ]; + var sourceExts = ['.cpp', '.c', '.h', '.ino' ]; if (req.files) { for (var name in req.files) { if (!req.files.hasOwnProperty(name)) { @@ -573,7 +579,7 @@ var Api = { if (hasSourceFiles) { //TODO: federate? - allDone.reject("Not yet implemented"); + allDone.reject('Not yet implemented'); } else { //they sent a binary, just flash it! @@ -595,11 +601,11 @@ var Api = { flash_core_dfd: function (req) { var tmp = when.defer(); - var userid = Api.getUserID(req), + var userid = Api._getUserID(req), socketID = Api.getSocketID(userid), coreID = req.coreID; - logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); + logger.log('FlashCore', {coreID: coreID, userID: userid.toString()}); var args = req.query; delete args.coreid; @@ -611,28 +617,28 @@ var Api = { var socket = new CoreController(socketID); var failTimer = setTimeout(function () { socket.close(); - tmp.reject({error: "Timed out."}); + tmp.reject({error: 'Timed out.'}); }, settings.coreFlashTimeout); //listen for the first response back from the device service - socket.listenFor(coreID, { cmd: "Event", name: "Update" }, + socket.listenFor(coreID, { cmd: 'Event', name: 'Update' }, function (sender, msg) { clearTimeout(failTimer); socket.close(); var response = { id: coreID, status: msg.message }; - if ("Update started" === msg.message) { + if ('Update started' === msg.message) { tmp.resolve(response); } else { - logger.error("flash_core_dfd rejected ", response); + logger.error('flash_core_dfd rejected ', response); tmp.reject(response); } }, true); //send it along to the device service - socket.send(coreID, { cmd: "UFlash", args: args }); + socket.send(coreID, { cmd: 'UFlash', args: args }); return tmp.promise; }, @@ -653,30 +659,30 @@ var Api = { provision_core_dfd: function (req) { var result = when.defer(), - userid = Api.getUserID(req), + userid = Api._getUserID(req), deviceID = req.body.deviceID, publicKey = req.body.publicKey; if (!deviceID) { - return when.reject({ error: "No deviceID provided" }); + return when.reject({ error: 'No deviceID provided' }); } try { var keyObj = ursa.createPublicKey(publicKey); if (!publicKey || (!ursa.isPublicKey(keyObj))) { - return when.reject({ error: "No key provided" }); + return when.reject({ error: 'No key provided' }); } } catch (ex) { - logger.error("error while parsing publicKey " + ex); - return when.reject({ error: "Key error " + ex }); + logger.error('error while parsing publicKey ' + ex); + return when.reject({ error: 'Key error ' + ex }); } global.server.addCoreKey(deviceID, publicKey); - global.server.setCoreAttribute(deviceID, "registrar", userid); - global.server.setCoreAttribute(deviceID, "timestamp", new Date()); - result.resolve("Success!"); + global.server.setCoreAttribute(deviceID, 'registrar', userid); + global.server.setCoreAttribute(deviceID, 'timestamp', new Date()); + result.resolve('Success!'); return result.promise; }, From f2a13a235113fcdb2efbe611a66c05b9698ec3e1 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 10 Dec 2016 13:13:08 -0800 Subject: [PATCH 142/504] Removing this for now. --- src/lib/controllers/DevicesController.js | 53 ------------------------ 1 file changed, 53 deletions(-) delete mode 100644 src/lib/controllers/DevicesController.js diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js deleted file mode 100644 index dda362c2..00000000 --- a/src/lib/controllers/DevicesController.js +++ /dev/null @@ -1,53 +0,0 @@ -// @flow - -import type { Repository, Webhook } from '../../types'; - -import settings from '../../settings'; -import Controller from './Controller'; -import httpVerb from '../decorators/httpVerb'; -import route from '../decorators/route'; - -class DevicesController extends Controller { - _webhookRepository: Repository; - - constructor(webhookRepository: Repository) { - super(); - - this._webhookRepository = webhookRepository; - } - - @httpVerb('get') - @route('/v1/devices') - get() { - return this.ok(this._webhookRepository.getAll()); - } - - @httpVerb('get') - @route('/v1/webhooks/:webhookId') - getByWebhookId(webhookId: string) { - return this.ok(this._webhookRepository.getById(webhookId)); - } - - @httpVerb('post') - @route('/v1/webhooks') - post(model: Webhook) { - const newWebhook = this._webhookRepository.create(model); - return this.ok({ - created_at: newWebhook.created_at, - event: newWebhook.event, - hookUrl: settings.baseUrl + '/v1/webhooks/' + newWebhook.id, - id: newWebhook.id, - ok: true, - url: newWebhook.url, - }); - } - - @httpVerb('delete') - @route('/v1/webhooks/:webhookId') - delete(webhookId: string) { - this._webhookRepository.delete(webhookId); - return this.ok(); - } -} - -export default DevicesController; From ab127e7ca229c67f721cc4269305c4bdadbea699 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 08:00:57 -0800 Subject: [PATCH 143/504] Started work on migrating api_v1.js --- src/lib/DeviceConnection.js | 2 +- src/lib/RouteConfig.js | 9 +- .../controllers/DevicesController__john.js | 48 +++++ src/lib/repository/DeviceRepository__john.js | 47 +++++ src/main.js | 28 ++- src/types.js | 13 +- src/views/api_v1.js | 197 +++++++++--------- 7 files changed, 232 insertions(+), 112 deletions(-) create mode 100644 src/lib/controllers/DevicesController__john.js create mode 100644 src/lib/repository/DeviceRepository__john.js diff --git a/src/lib/DeviceConnection.js b/src/lib/DeviceConnection.js index 8f3a3d13..1b8f07a9 100644 --- a/src/lib/DeviceConnection.js +++ b/src/lib/DeviceConnection.js @@ -9,7 +9,7 @@ class DeviceConnection { this._socketId = CONNECTION_COUNTER++; } - getDeviceById(coreId: string): Device { + async getStatus(deviceID: string): Object { } } diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index d0755ecb..0be60141 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -38,8 +38,13 @@ export default (app: $Application, controllers: Array) => { ...values, request.body, ); - - response.status(result.status).json(result.data); + if (result.then) { + result.then(result => { + response.status(result.status).json(result.data); + }) + } else { + response.status(result.status).json(result.data); + } }); }); }); diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js new file mode 100644 index 00000000..d84baaaa --- /dev/null +++ b/src/lib/controllers/DevicesController__john.js @@ -0,0 +1,48 @@ +// @flow + +import type { Device, DeviceRepository } from '../../types'; + +import settings from '../../settings'; +import Controller from './Controller'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; + +type APIType = {| + connected: boolean, + id: string, + last_app: ?string, + last_heard: ?Date, + name: string; +|}; +const toAPI = (device: Device): APIType => ({ + connected: device.connected, + id: device.coreID, + last_app: device.lastFlashedAppName, + last_heard: device.lastHeard, + name: device.name, +}); + +class DevicesController extends Controller { + _deviceRepository: DeviceRepository; + + constructor(deviceRepository: DeviceRepository) { + super(); + + this._deviceRepository = deviceRepository; + } + + @httpVerb('get') + @route('/v1/devices') + async get() { + try { + const devices = await this._deviceRepository.getAll(); + + return this.ok(devices.map(device => toAPI(device))); + } catch (exception) { + // I wish we could return no devices found but meh :/ + return this.ok([]); + } + } +} + +export default DevicesController; diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js new file mode 100644 index 00000000..97f10165 --- /dev/null +++ b/src/lib/repository/DeviceRepository__john.js @@ -0,0 +1,47 @@ +// @flow + +import type { DeviceServer } from 'spark-protocol'; +import type { Device, DeviceAttributes, Repository } from '../../types'; + +class DeviceRepository { + _deviceAttributeRepository: Repository; + _deviceServer: DeviceServer; + + constructor( + deviceAttributeRepository: Repository, + deviceServer: DeviceServer, + ) { + this._deviceAttributeRepository = deviceAttributeRepository; + this._deviceServer = deviceServer; + } + + async getAll(): Promise> { + const deviceAttributes = await this._deviceAttributeRepository.getAll(); + + const devicePromises = deviceAttributes.map(async attributes => { + const core = this._deviceServer.getCore(attributes.coreID); + + // TODO: Not sure if this should actually be the core ID that gets sent + // but that's what the old source code does :/ + const response = core + ? await core.onApiMessage( + attributes.coreID, + { cmd: 'Ping' }, + ) + : { + connected: false, + lastPing: null, + }; + + return { + ...attributes, + connected: response.connected, + lastHeard: response.lastPing, + }; + }); + + return Promise.all(devicePromises); + } +} + +export default DeviceRepository; diff --git a/src/main.js b/src/main.js index 4532299c..9e455b50 100644 --- a/src/main.js +++ b/src/main.js @@ -43,9 +43,13 @@ import UserCreator from './lib/UserCreator'; import api from './views/api_v1'; import eventsV1 from './views/EventViews001'; +// Repositories +import DeviceRepository from './lib/repository/DeviceRepository__john'; + // Routing import routeConfig from './lib/RouteConfig'; import WebhookController from './lib/controllers/WebhookController'; +import DevicesController from './lib/controllers/DevicesController__john'; import { DeviceAttributeFileRepository, @@ -114,26 +118,26 @@ const tokenViews = new AccessTokenViews({}); eventsV1.loadViews(app); api.loadViews(app); tokenViews.loadViews(app); -routeConfig(app, [ - new WebhookController(settings.webhookRepository), -]); +/* const noRouteMiddleware: Middleware = ( request: $Request, response: $Response, ): mixed => response.sendStatus(404); app.use(noRouteMiddleware); - +*/ console.log(`Starting server, listening on ${NODE_PORT}`); app.listen(NODE_PORT); +const deviceAttributeRepository = new DeviceAttributeFileRepository( + settings.coreKeysDir, +); + const deviceServer = new DeviceServer({ coreKeysDir: settings.coreKeysDir, - deviceAttributeRepository: new DeviceAttributeFileRepository( - settings.coreKeysDir, - ), + deviceAttributeRepository, host: settings.HOST, port: settings.PORT, serverConfigRepository: new ServerConfigFileRepository( @@ -144,6 +148,16 @@ const deviceServer = new DeviceServer({ serverKeyPassFile: settings.serverKeyPassFile, }); +const deviceRepository = new DeviceRepository( + deviceAttributeRepository, + deviceServer, +); + +routeConfig(app, [ + new DevicesController(deviceRepository), + new WebhookController(settings.webhookRepository), +]); + // TODO wny do we need next line? (Anton Puko) global.server = deviceServer; deviceServer.start(); diff --git a/src/types.js b/src/types.js index c6e997cf..cf59b7c1 100644 --- a/src/types.js +++ b/src/types.js @@ -13,7 +13,7 @@ export type Webhook = { url: string, }; -export type Device = { +export type DeviceAttributes = { deviceId: string, ip: string, particleProductId: number, @@ -22,6 +22,13 @@ export type Device = { timestamp: Date, }; + +export type Device = DeviceAttributes & { + connected: boolean, + lastFlashedAppName: ?string, + lastHeard: ?Date, +}; + export type Repository = { create: (id: string, model: TModel) => TModel, delete: (id: string) => void, @@ -29,3 +36,7 @@ export type Repository = { getById: (id: string) => TModel, update: (id: string, model: TModel) => TModel, }; + +export type DeviceRepository = { + getAll(): Promise>, +}; diff --git a/src/views/api_v1.js b/src/views/api_v1.js index f44232c5..58393312 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -14,12 +14,8 @@ * along with this program. If not, see . * * You can download the source here: https://github.com/spark/spark-server -* -* @flow -* */ -import type { $Application, $Request, $Response } from 'express'; var settings = require('../settings.js'); var CoreController = require('../lib/CoreController.js'); @@ -30,7 +26,7 @@ var parallel = require('when/parallel'); var pipeline = require('when/pipeline'); var logger = require('../lib/logger.js'); -var utilities = require('../lib/utilities.js'); +var utilities = require("../lib/utilities.js"); var fs = require('fs'); var when = require('when'); @@ -47,9 +43,11 @@ var moment = require('moment'); */ var Api = { - loadViews: function (app: $Application) { + loadViews: function (app) { + + //our middleware + app.param("coreid", Api.loadCore); - app.param('coreid', Api.loadCore); //core functions / variables app.post('/v1/devices/:coreid/:func', Api.fn_call); @@ -59,7 +57,7 @@ var Api = { app.get('/v1/devices/:coreid', Api.get_core_attributes); //doesn't need per-core permissions, only shows owned cores. - app.get('/v1/devices', Api.list_devices); + //app.get('/v1/devices', Api.list_devices); app.post('/v1/provisioning/:coreid', Api.provision_core); @@ -68,23 +66,22 @@ var Api = { }, - getSocketID: function (userID: string) { - return userID + '_' + global._socket_counter++; + getSocketID: function (userID) { + return userID + "_" + global._socket_counter++; }, - _getUserID: function (req: $Request) { - const user = (req: any).user; - if (!user) { - logger.log('User obj was empty'); + getUserID: function (req) { + if (!req.user) { + logger.log("User obj was empty"); return null; } //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return user.id; + return req.user.id; }, - list_devices: function (req: $Request, res: $Response) { - var userid = Api._getUserID(req); - logger.log('ListDevices', { userID: userid }); + list_devices: function (req, res) { + var userid = Api.getUserID(req); + logger.log("ListDevices", { userID: userid }); //give me all the cores @@ -106,18 +103,16 @@ var Api = { last_heard: null }; - // TODO: Handle this? - /* if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { - device['requires_deep_update'] = true; + device["requires_deep_update"] = true; } - */ + devices.push(device); console.log(device.id); connected_promises.push(Api.isDeviceOnline(userid, device.id)); }); - logger.log('ListDevices... waiting for connected state to settle ', { userID: userid }); + logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); //switched 'done' to 'then' - threw an exception with 'done' here. when.settle(connected_promises).then(function (descriptors) { @@ -133,20 +128,20 @@ var Api = { }, get_core_attributes: function (req, res) { - var userid = Api._getUserID(req); + var userid = Api.getUserID(req); var socketID = Api.getSocketID(userid), coreID = req.coreID, socket = new CoreController(socketID); - logger.log('GetAttr', { coreID: coreID, userID: userid.toString() }); + logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); var objReady = parallel([ function () { return when.resolve(global.server.getCoreAttributes(coreID)); }, function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: 'Describe' }, { cmd: 'DescribeReturn' })); + return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); } ]); @@ -155,8 +150,8 @@ var Api = { try { if (!results || (results.length !== 2)) { - logger.error('get_core_attributes results was the wrong length ' + JSON.stringify(results)); - res.json(404, 'Oops, I couldn\'t find that core'); + logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); + res.json(404, "Oops, I couldn't find that core"); return; } @@ -167,8 +162,8 @@ var Api = { coreState = null; if (!doc || !doc.coreID) { - logger.error('get_core_attributes 404 error: ' + JSON.stringify(doc)); - res.json(404, 'Oops, I couldn\'t find that core'); + logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); + res.json(404, "Oops, I couldn't find that core"); return; } @@ -176,7 +171,7 @@ var Api = { coreState = descResult[1].state || {}; } if (!coreState) { - logger.error('get_core_attributes didn\'t get description: ' + JSON.stringify(descResult)); + logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); } var device = { @@ -190,14 +185,14 @@ var Api = { }; if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { - device['requires_deep_update'] = true; + device["requires_deep_update"] = true; } res.json(device); } catch (ex) { - logger.error('get_core_attributes merge error: ' + ex); - res.json(500, { Error: 'get_core_attributes error: ' + ex }); + logger.error("get_core_attributes merge error: " + ex); + res.json(500, { Error: "get_core_attributes error: " + ex }); } }, null); @@ -207,17 +202,17 @@ var Api = { set_core_attributes: function (req, res) { var coreID = req.coreID; - var userid = Api._getUserID(req); + var userid = Api.getUserID(req); var promises = []; - logger.log('set_core_attributes', { coreID: coreID, userID: userid.toString() }); + logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); var coreName = req.body ? req.body.name : null; if (coreName !== null) { - logger.log('SetAttr', { coreID: coreID, userID: userid.toString(), name: coreName }); + logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); - global.server.setCoreAttribute(req.coreID, 'name', coreName); + global.server.setCoreAttribute(req.coreID, "name", coreName); promises.push(when.resolve({ ok: true, name: coreName })); } @@ -244,7 +239,7 @@ var Api = { promises.push(Api.flash_known_app_dfd(req)); } else { - promises.push(when.reject('Can\'t flash unknown app ' + flashApp)); + promises.push(when.reject("Can't flash unknown app " + flashApp)); } } } @@ -281,46 +276,46 @@ var Api = { ); } else { - logger.error('set_core_attributes - nothing to do?', { coreID: coreID, userID: userid.toString() }); - res.json({error: 'Nothing to do?'}); + logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); + res.json({error: "Nothing to do?"}); } }, - isDeviceOnline: function (userID: string, coreID: string) { + isDeviceOnline: function (userID, coreID) { var tmp = when.defer(); var socketID = Api.getSocketID(userID); var socket = new CoreController(socketID); var failTimer = setTimeout(function () { - logger.log('isDeviceOnline: Ping timed out ', { coreID: coreID }); + logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); socket.close(); - tmp.reject('Device is not connected'); + tmp.reject("Device is not connected"); }, settings.isCoreOnlineTimeout); //setup listener for response back from the device service - socket.listenFor(coreID, { cmd: 'Pong' }, function (sender, msg) { + socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { clearTimeout(failTimer); socket.close(); - logger.log('isDeviceOnline: Device service thinks it is online... ', { coreID: coreID }); + logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); - if (msg && msg.online) { + if (msg && msg.connected) { tmp.resolve(msg); } else { - tmp.reject(['Core isn\'t online', 404]); + tmp.reject(["Core isn't online", 404]); } }, true); - logger.log('isDeviceOnline: Pinging core... ', { coreID: coreID }); + logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); //send it along to the device service - if (!socket.send(coreID, { cmd: 'Ping' })) { - tmp.reject('send failed'); + if (!socket.send(coreID, { cmd: "Ping" })) { + tmp.reject("send failed"); } return tmp.promise; @@ -337,14 +332,14 @@ var Api = { //load core info! req.coreInfo = { - 'last_app': '', - 'last_heard': new Date(), - 'connected': false, - 'deviceID': req.coreID + "last_app": "", + "last_heard": new Date(), + "connected": false, + "deviceID": req.coreID }; //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api._getUserID(req); + var userid = Api.getUserID(req); var gotCore = utilities.deferredAny([ function () { var core = global.server.getCoreAttributes(req.coreID); @@ -384,21 +379,21 @@ var Api = { }, get_var: function (req, res) { - var userid = Api._getUserID(req); + var userid = Api.getUserID(req); var socketID = Api.getSocketID(userid), coreID = req.coreID, varName = req.params.var, format = req.params.format; - logger.log('GetVar', {coreID: coreID, userID: userid.toString()}); + logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); //send it along to the device service //and listen for a response back from the device service var socket = new CoreController(socketID); var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: 'GetVar', name: varName }, - { cmd: 'VarReturn', name: varName }, + { cmd: "GetVar", name: varName }, + { cmd: "VarReturn", name: varName }, settings.coreRequestTimeout ); @@ -419,15 +414,15 @@ var Api = { msg.coreInfo = req.coreInfo; msg.coreInfo.connected = true; - if (format && (format === 'raw')) { - return res.send('' + msg.result); + if (format && (format === "raw")) { + return res.send("" + msg.result); } else { return res.json(msg); } }, function () { - res.json(408, {error: 'Timed out.'}); + res.json(408, {error: "Timed out."}); } ).ensure(function () { socket.close(); @@ -435,12 +430,12 @@ var Api = { }, fn_call: function (req, res) { - var user_id = Api._getUserID(req), + var user_id = Api.getUserID(req), coreID = req.coreID, funcName = req.params.func, format = req.params.format; - logger.log('FunCall', { coreID: coreID, user_id: user_id.toString() }); + logger.log("FunCall", { coreID: coreID, user_id: user_id.toString() }); var socketID = Api.getSocketID(user_id); var socket = new CoreController(socketID); @@ -449,10 +444,10 @@ var Api = { var args = req.body; delete args.access_token; - logger.log('FunCall - calling core ', { coreID: coreID, user_id: user_id.toString() }); + logger.log("FunCall - calling core ", { coreID: coreID, user_id: user_id.toString() }); var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: 'CallFn', name: funcName, args: args }, - { cmd: 'FnReturn', name: funcName }, + { cmd: "CallFn", name: funcName, args: args }, + { cmd: "FnReturn", name: funcName }, settings.coreRequestTimeout ); @@ -463,11 +458,11 @@ var Api = { var sender = arr[0], msg = arr[1]; try { - //logger.log('FunCall - heard back ', { coreID: coreID, user_id: user_id.toString() }); - if (msg.error && (msg.error.indexOf('Unknown Function') >= 0)) { + //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); + if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { res.json(404, { ok: false, - error: 'Function not found' + error: "Function not found" }); } else if (msg.error !== null) { @@ -477,8 +472,8 @@ var Api = { }); } else { - if (format && (format === 'raw')) { - res.send('' + msg.result); + if (format && (format === "raw")) { + res.send("" + msg.result); } else { res.json({ @@ -492,47 +487,47 @@ var Api = { } } catch (ex) { - logger.error('FunCall handling resp error ' + ex); + logger.error("FunCall handling resp error " + ex); res.json(500, { ok: false, - error: 'Error while api was rendering response' + error: "Error while api was rendering response" }); } }, function () { - res.json(408, {error: 'Timed out.'}); + res.json(408, {error: "Timed out."}); } ).ensure(function () { socket.close(); }); - //socket.send(coreID, { cmd: 'CallFn', name: funcName, args: args }); + //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); // send the function call along to the device service }, /** - * Ask the core to start / stop the 'RaiseYourHand' signal + * Ask the core to start / stop the "RaiseYourHand" signal * @param req */ core_signal_dfd: function (req) { var tmp = when.defer(); - var userid = Api._getUserID(req), + var userid = Api.getUserID(req), socketID = Api.getSocketID(userid), coreID = req.coreID, showSignal = parseInt(req.body.signal); - logger.log('SignalCore', { coreID: coreID, userID: userid.toString()}); + logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); var socket = new CoreController(socketID); var failTimer = setTimeout(function () { socket.close(); - tmp.reject({error: 'Timed out, didn\'t hear back'}); + tmp.reject({error: "Timed out, didn't hear back"}); }, settings.coreSignalTimeout); //listen for a response back from the device service - socket.listenFor(coreID, { cmd: 'RaiseHandReturn'}, + socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, function () { clearTimeout(failTimer); socket.close(); @@ -546,14 +541,14 @@ var Api = { //send it along to the core via the device service - socket.send(coreID, { cmd: 'RaiseHand', args: { signal: showSignal } }); + socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); return tmp.promise; }, compile_and__or_flash_dfd: function (req) { var allDone = when.defer(); - var userid = Api._getUserID(req), + var userid = Api.getUserID(req), coreID = req.coreID; @@ -561,7 +556,7 @@ var Api = { // Did they pass us a source file or a binary file? // var hasSourceFiles = false; - var sourceExts = ['.cpp', '.c', '.h', '.ino' ]; + var sourceExts = [".cpp", ".c", ".h", ".ino" ]; if (req.files) { for (var name in req.files) { if (!req.files.hasOwnProperty(name)) { @@ -579,7 +574,7 @@ var Api = { if (hasSourceFiles) { //TODO: federate? - allDone.reject('Not yet implemented'); + allDone.reject("Not yet implemented"); } else { //they sent a binary, just flash it! @@ -601,11 +596,11 @@ var Api = { flash_core_dfd: function (req) { var tmp = when.defer(); - var userid = Api._getUserID(req), + var userid = Api.getUserID(req), socketID = Api.getSocketID(userid), coreID = req.coreID; - logger.log('FlashCore', {coreID: coreID, userID: userid.toString()}); + logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); var args = req.query; delete args.coreid; @@ -617,28 +612,28 @@ var Api = { var socket = new CoreController(socketID); var failTimer = setTimeout(function () { socket.close(); - tmp.reject({error: 'Timed out.'}); + tmp.reject({error: "Timed out."}); }, settings.coreFlashTimeout); //listen for the first response back from the device service - socket.listenFor(coreID, { cmd: 'Event', name: 'Update' }, + socket.listenFor(coreID, { cmd: "Event", name: "Update" }, function (sender, msg) { clearTimeout(failTimer); socket.close(); var response = { id: coreID, status: msg.message }; - if ('Update started' === msg.message) { + if ("Update started" === msg.message) { tmp.resolve(response); } else { - logger.error('flash_core_dfd rejected ', response); + logger.error("flash_core_dfd rejected ", response); tmp.reject(response); } }, true); //send it along to the device service - socket.send(coreID, { cmd: 'UFlash', args: args }); + socket.send(coreID, { cmd: "UFlash", args: args }); return tmp.promise; }, @@ -659,30 +654,30 @@ var Api = { provision_core_dfd: function (req) { var result = when.defer(), - userid = Api._getUserID(req), + userid = Api.getUserID(req), deviceID = req.body.deviceID, publicKey = req.body.publicKey; if (!deviceID) { - return when.reject({ error: 'No deviceID provided' }); + return when.reject({ error: "No deviceID provided" }); } try { var keyObj = ursa.createPublicKey(publicKey); if (!publicKey || (!ursa.isPublicKey(keyObj))) { - return when.reject({ error: 'No key provided' }); + return when.reject({ error: "No key provided" }); } } catch (ex) { - logger.error('error while parsing publicKey ' + ex); - return when.reject({ error: 'Key error ' + ex }); + logger.error("error while parsing publicKey " + ex); + return when.reject({ error: "Key error " + ex }); } global.server.addCoreKey(deviceID, publicKey); - global.server.setCoreAttribute(deviceID, 'registrar', userid); - global.server.setCoreAttribute(deviceID, 'timestamp', new Date()); - result.resolve('Success!'); + global.server.setCoreAttribute(deviceID, "registrar", userid); + global.server.setCoreAttribute(deviceID, "timestamp", new Date()); + result.resolve("Success!"); return result.promise; }, From 5f1d344bcef52d403a92008e6e67f378e64b1276 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 10:14:36 -0800 Subject: [PATCH 144/504] Added in ProvisioningController Working on device function calls. --- src/lib/RouteConfig.js | 16 +----- .../controllers/DevicesController__john.js | 23 +++++++- src/lib/controllers/ProvisioningController.js | 40 +++++++++++++ src/lib/repository/DeviceRepository__john.js | 56 +++++++++++++++++++ src/lib/repository/WebhookFileRepository.js | 6 +- src/main.js | 6 +- src/views/api_v1.js | 4 +- 7 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 src/lib/controllers/ProvisioningController.js diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 0be60141..fffbf770 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -3,18 +3,6 @@ import type { $Application, $Request, $Response } from 'express'; import type Controller from './controllers/Controller'; -const getFunctionArgumentNames = (func: Function): Array => { - // First match everything inside the function argument parens. - const args = - (func.toString().match(/function\s.*?\(([^)]*)\)/) || [])[1] || ''; - - // Split the arguments string into an array comma delimited. - return args.split(',').map((argument: string): string => - // Ensure no inline comments are parsed and trim the whitespace. - argument.replace(/\/\*.*\*\//, '').trim(), - ).filter((argument: string): boolean => !!argument); -}; - export default (app: $Application, controllers: Array) => { controllers.forEach((controller: Controller) => { Object.getOwnPropertyNames( @@ -26,7 +14,9 @@ export default (app: $Application, controllers: Array) => { return; } - const argumentNames = getFunctionArgumentNames(mappedFunction); + const argumentNames = (route.match(/:[\w]*/g) || []).map( + arumentName => arumentName.replace(':', ''), + ); (app: any)[httpVerb](route, (request: $Request, response: $Response) => { const values = argumentNames diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index d84baaaa..50e6b2a0 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -33,7 +33,7 @@ class DevicesController extends Controller { @httpVerb('get') @route('/v1/devices') - async get() { + async getDevices() { try { const devices = await this._deviceRepository.getAll(); @@ -43,6 +43,27 @@ class DevicesController extends Controller { return this.ok([]); } } + + @httpVerb('post') + @route('/v1/devices/:coreID/:functionName') + async callDeviceFunction( + coreID: string, + functionName: string, + postBody: {arg: string}, + ) { + try { + const devices = await this._deviceRepository.callFunction( + coreID, + functionName, + postBody.arg, + ); + + return this.ok([]); + } catch (exception) { + // I wish we could return no devices found but meh :/ + return this.ok([]); + } + } } export default DevicesController; diff --git a/src/lib/controllers/ProvisioningController.js b/src/lib/controllers/ProvisioningController.js new file mode 100644 index 00000000..160dc90b --- /dev/null +++ b/src/lib/controllers/ProvisioningController.js @@ -0,0 +1,40 @@ +// @flow + +import type { Device, DeviceRepository } from '../../types'; + +import settings from '../../settings'; +import Controller from './Controller'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; + +class ProvisioningController extends Controller { + _deviceRepository: DeviceRepository; + + constructor(deviceRepository: DeviceRepository) { + super(); + + this._deviceRepository = deviceRepository; + } + + @httpVerb('post') + @route('/v1/provisioning/:coreID') + async provision( + coreID: string, + postBody: {publicKey: string}, + ) { + try { + const devices = await this._deviceRepository.provision( + coreID, + 'UserIDGoesHere', + postBody.publicKey, + ); + + return this.ok([]); + } catch (exception) { + // I wish we could return no devices found but meh :/ + return this.ok([]); + } + } +} + +export default ProvisioningController; diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index 97f10165..91f93b36 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -3,15 +3,21 @@ import type { DeviceServer } from 'spark-protocol'; import type { Device, DeviceAttributes, Repository } from '../../types'; +import ursa from 'ursa'; +import logger from '../logger'; + class DeviceRepository { _deviceAttributeRepository: Repository; + _deviceKeyRepository: Repository; _deviceServer: DeviceServer; constructor( deviceAttributeRepository: Repository, + deviceKeyRepository: Repository, deviceServer: DeviceServer, ) { this._deviceAttributeRepository = deviceAttributeRepository; + this._deviceKeyRepository = deviceKeyRepository; this._deviceServer = deviceServer; } @@ -42,6 +48,56 @@ class DeviceRepository { return Promise.all(devicePromises); } + + async callFunction( + deviceID: string, + functionName: string, + functionArguments: string, + ): Promise<*> { + const core = this._deviceServer.getCore(deviceID); + if (!core) { + return null; + } + + const result = await core.onApiMessage( + deviceID, + { cmd:'CallFn', name: functionName, args: functionArguments }, + ); + + console.log(); + console.log(); + console.log(result); + console.log(); + console.log(); + } + + async provision( + deviceID: string, + userID: string, + publicKey: string, + ): Promise<*> { + + if (!deviceID) { + throw 'No deviceID provided'; + } + + try { + const createdKey = ursa.createPublicKey(publicKey); + if (!publicKey || !ursa.isPublicKey(createdKey)) { + throw 'No key provided'; + } + } catch (exception) { + logger.error('error while parsing publicKey', exception); + throw 'Key error ' + exception; + } + this._deviceKeyRepository.update(deviceID, publicKey); + const attributes = { + registrar: userID, + timestamp: new Date(), + }; + console.log(attributes); + this._deviceAttributeRepository.update(deviceID, attributes); + } } export default DeviceRepository; diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 9d8c262c..6f059e1d 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -2,13 +2,13 @@ import type { Webhook } from '../../types'; -import { FileManager, uuid } from 'spark-protocol'; +import { JSONFileManager, uuid } from 'spark-protocol'; class WebhookFileRepository { - _fileManager: FileManager; + _fileManager: JSONFileManager; constructor(path: string) { - this._fileManager = new FileManager(path); + this._fileManager = new JSONFileManager(path); } create = (model: Webhook): Webhook => { diff --git a/src/main.js b/src/main.js index 9e455b50..8577ac47 100644 --- a/src/main.js +++ b/src/main.js @@ -48,11 +48,13 @@ import DeviceRepository from './lib/repository/DeviceRepository__john'; // Routing import routeConfig from './lib/RouteConfig'; -import WebhookController from './lib/controllers/WebhookController'; import DevicesController from './lib/controllers/DevicesController__john'; +import ProvisioningController from './lib/controllers/ProvisioningController'; +import WebhookController from './lib/controllers/WebhookController'; import { DeviceAttributeFileRepository, + DeviceKeyFileRepository, ServerConfigFileRepository, } from 'spark-protocol'; @@ -150,11 +152,13 @@ const deviceServer = new DeviceServer({ const deviceRepository = new DeviceRepository( deviceAttributeRepository, + new DeviceKeyFileRepository(settings.coreKeysDir), deviceServer, ); routeConfig(app, [ new DevicesController(deviceRepository), + new ProvisioningController(deviceRepository), new WebhookController(settings.webhookRepository), ]); diff --git a/src/views/api_v1.js b/src/views/api_v1.js index 58393312..222a40bb 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -50,7 +50,7 @@ var Api = { //core functions / variables - app.post('/v1/devices/:coreid/:func', Api.fn_call); + //app.post('/v1/devices/:coreid/:func', Api.fn_call); app.get('/v1/devices/:coreid/:var', Api.get_var); app.put('/v1/devices/:coreid', Api.set_core_attributes); @@ -59,7 +59,7 @@ var Api = { //doesn't need per-core permissions, only shows owned cores. //app.get('/v1/devices', Api.list_devices); - app.post('/v1/provisioning/:coreid', Api.provision_core); + // app.post('/v1/provisioning/:coreid', Api.provision_core); //app.delete('/v1/devices/:coreid', Api.release_device); app.post('/v1/devices', Api.claim_device); From f5af38b28c88a8217dbbc8c4f0f4c30d23149622 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 12:19:48 -0800 Subject: [PATCH 145/504] Finished making function calls work. --- flow-typed/npm/express_v4.x.x.js | 2 +- src/lib/RouteConfig.js | 9 +++- src/lib/controllers/Controller.js | 9 ++-- .../controllers/DevicesController__john.js | 37 ++++++-------- src/lib/controllers/ProvisioningController.js | 5 +- src/lib/deviceToAPI.js | 23 +++++++++ src/lib/repository/DeviceRepository__john.js | 51 +++++++++++++++---- 7 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 src/lib/deviceToAPI.js diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 6954434f..62ebacb4 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -17,7 +17,7 @@ declare class express$RequestResponseBase { declare class express$Request extends http$IncomingMessage mixins express$RequestResponseBase { baseUrl: string; - body: mixed; + body: Object; cookies: {[cookie: string]: string}; fresh: boolean; hostname: boolean; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index fffbf770..08a110c4 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -23,15 +23,20 @@ export default (app: $Application, controllers: Array) => { .map((argument: string): any => request.params[argument]) .filter((value: ?any): boolean => value !== undefined); + // Take access token out if it's posted. + const { + access_token, + ...body, + } = request.body; const result = mappedFunction.call( controller, ...values, - request.body, + body, ); if (result.then) { result.then(result => { response.status(result.status).json(result.data); - }) + }); } else { response.status(result.status).json(result.data); } diff --git a/src/lib/controllers/Controller.js b/src/lib/controllers/Controller.js index e0b9aa38..a32975aa 100644 --- a/src/lib/controllers/Controller.js +++ b/src/lib/controllers/Controller.js @@ -1,8 +1,11 @@ export default class Controller { - bad(message) { + bad(message, status: number = 400) { return { - data: {message}, - status: 400, + data: { + error: message, + ok: false, + }, + status: status, }; } diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index 50e6b2a0..325123c7 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -6,21 +6,7 @@ import settings from '../../settings'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; - -type APIType = {| - connected: boolean, - id: string, - last_app: ?string, - last_heard: ?Date, - name: string; -|}; -const toAPI = (device: Device): APIType => ({ - connected: device.connected, - id: device.coreID, - last_app: device.lastFlashedAppName, - last_heard: device.lastHeard, - name: device.name, -}); +import deviceToAPI from '../deviceToAPI'; class DevicesController extends Controller { _deviceRepository: DeviceRepository; @@ -37,7 +23,7 @@ class DevicesController extends Controller { try { const devices = await this._deviceRepository.getAll(); - return this.ok(devices.map(device => toAPI(device))); + return this.ok(devices.map(device => deviceToAPI(device))); } catch (exception) { // I wish we could return no devices found but meh :/ return this.ok([]); @@ -49,19 +35,26 @@ class DevicesController extends Controller { async callDeviceFunction( coreID: string, functionName: string, - postBody: {arg: string}, + postBody: Object, ) { try { - const devices = await this._deviceRepository.callFunction( + const result = await this._deviceRepository.callFunction( coreID, functionName, - postBody.arg, + postBody, ); - return this.ok([]); + const device = await this._deviceRepository.getByID(coreID); + return this.ok(deviceToAPI(device, result)); } catch (exception) { - // I wish we could return no devices found but meh :/ - return this.ok([]); + if (exception.indexOf('Unknown Function') >= 0) { + return this.bad( + 'Function not found', + 404, + ); + } + + return this.bad(exception); } } } diff --git a/src/lib/controllers/ProvisioningController.js b/src/lib/controllers/ProvisioningController.js index 160dc90b..cc8e8879 100644 --- a/src/lib/controllers/ProvisioningController.js +++ b/src/lib/controllers/ProvisioningController.js @@ -6,6 +6,7 @@ import settings from '../../settings'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; +import deviceToAPI from '../deviceToAPI'; class ProvisioningController extends Controller { _deviceRepository: DeviceRepository; @@ -23,13 +24,13 @@ class ProvisioningController extends Controller { postBody: {publicKey: string}, ) { try { - const devices = await this._deviceRepository.provision( + const device = await this._deviceRepository.provision( coreID, 'UserIDGoesHere', postBody.publicKey, ); - return this.ok([]); + return this.ok(deviceToAPI(device)); } catch (exception) { // I wish we could return no devices found but meh :/ return this.ok([]); diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js new file mode 100644 index 00000000..188e853f --- /dev/null +++ b/src/lib/deviceToAPI.js @@ -0,0 +1,23 @@ +// @flow + +import type { Device } from '../types'; + +export type DeviceAPIType = {| + connected: boolean, + id: string, + last_app: ?string, + last_heard: ?Date, + name: string; + return_value?: mixed +|}; + +const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ + connected: device.connected, + id: device.coreID, + last_app: device.lastFlashedAppName, + last_heard: device.lastHeard, + name: device.name, + return_value: result, +}); + +export default deviceToAPI; diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index 91f93b36..61e51b04 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -3,9 +3,12 @@ import type { DeviceServer } from 'spark-protocol'; import type { Device, DeviceAttributes, Repository } from '../../types'; +import Moniker from 'moniker'; import ursa from 'ursa'; import logger from '../logger'; +const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); + class DeviceRepository { _deviceAttributeRepository: Repository; _deviceKeyRepository: Repository; @@ -21,12 +24,33 @@ class DeviceRepository { this._deviceServer = deviceServer; } - async getAll(): Promise> { - const deviceAttributes = await this._deviceAttributeRepository.getAll(); + async getByID(deviceID: string): Promise { + const attributes = await this._deviceAttributeRepository.getById(deviceID); + const core = this._deviceServer.getCore(attributes.coreID); + // TODO: Not sure if this should actually be the core ID that gets sent + // but that's what the old source code does :/ + const response = core + ? await core.onApiMessage( + attributes.coreID, + { cmd: 'Ping' }, + ) + : { + connected: false, + lastPing: null, + }; - const devicePromises = deviceAttributes.map(async attributes => { - const core = this._deviceServer.getCore(attributes.coreID); + return { + ...attributes, + connected: response.connected, + lastHeard: response.lastPing, + }; + } + async getAll(): Promise> { + const devicesAttributes = await this._deviceAttributeRepository.getAll(); + const devicePromises = devicesAttributes.map(async attributes => { + const core = this._deviceServer.getCore(attributes.coreID); + console.log(attributes); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ const response = core @@ -64,11 +88,11 @@ class DeviceRepository { { cmd:'CallFn', name: functionName, args: functionArguments }, ); - console.log(); - console.log(); - console.log(result); - console.log(); - console.log(); + if (result.error) { + throw result.error; + } + + return result.result; } async provision( @@ -76,7 +100,6 @@ class DeviceRepository { userID: string, publicKey: string, ): Promise<*> { - if (!deviceID) { throw 'No deviceID provided'; } @@ -91,12 +114,18 @@ class DeviceRepository { throw 'Key error ' + exception; } this._deviceKeyRepository.update(deviceID, publicKey); + const existingAttributes = this._deviceAttributeRepository.getById( + deviceID, + ); const attributes = { + name: NAME_GENERATOR.choose(), + ...existingAttributes, registrar: userID, timestamp: new Date(), }; - console.log(attributes); this._deviceAttributeRepository.update(deviceID, attributes); + + return await this.getByID(deviceID); } } From fbe5cb36205ca91775d1d1e5b016651e07667bec Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 16:41:46 -0800 Subject: [PATCH 146/504] Added all the props that get returned from the particle server for devices. --- src/lib/deviceToAPI.js | 16 ++++++++++++++-- src/types.js | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js index 188e853f..a3a7a572 100644 --- a/src/lib/deviceToAPI.js +++ b/src/lib/deviceToAPI.js @@ -3,21 +3,33 @@ import type { Device } from '../types'; export type DeviceAPIType = {| + cellular: boolean, connected: boolean, + current_build_target: string, id: string, last_app: ?string, last_heard: ?Date, - name: string; - return_value?: mixed + last_ip_address: ?string, + name: string, + platform_id: number, + product_id: number, + return_value?: mixed, + status: string, |}; const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ + cellular: false, // TODO: populate this from device. connected: device.connected, + current_build_target: '', // TODO: populate this as well :( id: device.coreID, last_app: device.lastFlashedAppName, last_heard: device.lastHeard, + last_ip_address: device.ip, name: device.name, + platform_id: device.particleProductId, + product_id: device.particleProductId, return_value: result, + status: 'normal', // TODO: populate this from device }); export default deviceToAPI; diff --git a/src/types.js b/src/types.js index cf59b7c1..5c196548 100644 --- a/src/types.js +++ b/src/types.js @@ -17,7 +17,7 @@ export type DeviceAttributes = { deviceId: string, ip: string, particleProductId: number, - productFirmwareVersion: number, + productFirmwareVersion: string, registrar: string, timestamp: Date, }; From 6ca40d2e7be27ece69604db629c528a28947d690 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 18:16:43 -0800 Subject: [PATCH 147/504] Merged OAuth branch. --- flow-typed/npm/express_v4.x.x.js | 3 +- src/lib/OAuthModel.js | 2 +- src/lib/RouteConfig.js | 8 ++-- .../controllers/DevicesController__john.js | 1 + src/lib/controllers/UsersController.js | 17 ++++++-- src/lib/controllers/WebhookController.js | 14 ++++--- src/lib/deviceToAPI.js | 2 +- src/lib/repository/DeviceRepository__john.js | 14 ++++--- src/lib/repository/UsersFileRepository.js | 30 +++++++++----- src/lib/repository/WebhookFileRepository.js | 11 ++--- src/types.js | 40 ++++++++++--------- test/UsersController.test.js | 2 - test/WebhooksController.test.js | 2 - test/testApp.js | 4 +- 14 files changed, 90 insertions(+), 60 deletions(-) diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 62ebacb4..6854bd3a 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -92,8 +92,7 @@ declare class express$Response extends http$ClientRequest mixins express$Request declare type express$NextFunction = (err?: ?Error) => mixed; declare type express$Middleware = - ((req: express$Request, res: express$Response, next: express$NextFunction) => mixed) | - ((error: ?Error, req: express$Request, res: express$Response, next: express$NextFunction) => mixed); + (req: express$Request, res: express$Response, next: express$NextFunction) => mixed; declare interface express$RouteMethodType { (middleware: express$Middleware): T; (...middleware: Array): T; diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index acb35c1f..02822b9f 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -56,7 +56,7 @@ class OauthModel { }; // todo figure out this function - validateScope = (user: User, client: Client, scope: string): string => true; + validateScope = (user: User, client: Client, scope: string): string => 'true'; } export default OauthModel; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 698af3a3..d547c227 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -27,8 +27,9 @@ const injectUserMiddleware = (): Middleware => (request: $Request, response: $Response, next: NextFunction) => { const oauthInfo = response.locals.oauth; if (oauthInfo) { + const token = (oauthInfo: any).token; // eslint-disable-next-line no-param-reassign - request.user = oauthInfo.token.user; + (request: any).user = token && token.user; } next(); }; @@ -56,9 +57,9 @@ export default ( controllers.forEach((controller: Controller) => { Object.getOwnPropertyNames( - Object.getPrototypeOf(controller), + (Object.getPrototypeOf(controller): any), ).forEach((functionName: string) => { - const mappedFunction = controller[functionName]; + const mappedFunction = (controller: any)[functionName]; const { httpVerb, route, anonymous } = mappedFunction; if (!httpVerb) { return; @@ -70,6 +71,7 @@ export default ( injectUserMiddleware(), async (request: $Request, response: $Response, ) => { + console.log('foao'); const argumentNames = (route.match(/:[\w]*/g) || []).map( argumentName => argumentName.replace(':', ''), ); diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index 325123c7..75f2a3e9 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -38,6 +38,7 @@ class DevicesController extends Controller { postBody: Object, ) { try { + console.log('foooobar') const result = await this._deviceRepository.callFunction( coreID, functionName, diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index 8bcaec57..0062fe87 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -31,7 +31,9 @@ class UsersController extends Controller { throw new Error('user with the username is already exist'); } - const newUser = await this._usersRepository.create(userCredentials); + const newUser = await this._usersRepository.createWithCredentials( + userCredentials, + ); return this.ok(newUser); } catch (error) { return this.bad(error.message); @@ -43,8 +45,13 @@ class UsersController extends Controller { @anonymous() async deleteAccessToken(token: string): Promise<*> { try { - const { username, password } = basicAuthParser(this.request.get('authorization')); - const user = await this._usersRepository.validateLogin(username, password); + const { username, password } = basicAuthParser( + this.request.get('authorization'), + ); + const user = await this._usersRepository.validateLogin( + username, + password, + ); this._usersRepository.deleteAccessToken(user, token); @@ -59,7 +66,9 @@ class UsersController extends Controller { @anonymous() async getAccessTokens(): Promise<*> { try { - const { username, password } = basicAuthParser(this.request.get('authorization')); + const { username, password } = basicAuthParser( + this.request.get('authorization'), + ); const user = await this._usersRepository.validateLogin(username, password); return this.ok(user.accessTokens); } catch (error) { diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhookController.js index ad49939e..19f3dfe0 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhookController.js @@ -33,9 +33,9 @@ const validateWebhookMutator = (webhookMutator: WebhookMutator): ?Error => { }; class WebhookController extends Controller { - _webhookRepository: Repository; + _webhookRepository: Repository; - constructor(webhookRepository: Repository) { + constructor(webhookRepository: Repository) { super(); this._webhookRepository = webhookRepository; @@ -44,13 +44,13 @@ class WebhookController extends Controller { @httpVerb('get') @route('/v1/webhooks') async getAll(): Promise<*> { - return this.ok(this._webhookRepository.getAll()); + return this.ok(await this._webhookRepository.getAll()); } @httpVerb('get') @route('/v1/webhooks/:webhookId') async getById(webhookId: string): Promise<*> { - return this.ok(this._webhookRepository.getById(webhookId)); + return this.ok(await this._webhookRepository.getById(webhookId)); } @httpVerb('post') @@ -62,7 +62,11 @@ class WebhookController extends Controller { throw validateError; } - const newWebhook = this._webhookRepository.create(model); + const newWebhook = await this._webhookRepository.create({ + ...model, + created_at: new Date(), + id: '', + }); return this.ok({ created_at: newWebhook.created_at, event: newWebhook.event, diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js index a3a7a572..f49e95fd 100644 --- a/src/lib/deviceToAPI.js +++ b/src/lib/deviceToAPI.js @@ -21,7 +21,7 @@ const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ cellular: false, // TODO: populate this from device. connected: device.connected, current_build_target: '', // TODO: populate this as well :( - id: device.coreID, + id: device.deviceID, last_app: device.lastFlashedAppName, last_heard: device.lastHeard, last_ip_address: device.ip, diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index 61e51b04..239dff5d 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -26,12 +26,12 @@ class DeviceRepository { async getByID(deviceID: string): Promise { const attributes = await this._deviceAttributeRepository.getById(deviceID); - const core = this._deviceServer.getCore(attributes.coreID); + const core = this._deviceServer.getCore(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ const response = core ? await core.onApiMessage( - attributes.coreID, + attributes.deviceID, { cmd: 'Ping' }, ) : { @@ -42,6 +42,7 @@ class DeviceRepository { return { ...attributes, connected: response.connected, + lastFlashedAppName: null, lastHeard: response.lastPing, }; } @@ -49,13 +50,13 @@ class DeviceRepository { async getAll(): Promise> { const devicesAttributes = await this._deviceAttributeRepository.getAll(); const devicePromises = devicesAttributes.map(async attributes => { - const core = this._deviceServer.getCore(attributes.coreID); + const core = this._deviceServer.getCore(attributes.deviceID); console.log(attributes); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ const response = core ? await core.onApiMessage( - attributes.coreID, + attributes.deviceID, { cmd: 'Ping' }, ) : { @@ -66,6 +67,7 @@ class DeviceRepository { return { ...attributes, connected: response.connected, + lastFlashedAppName: null, lastHeard: response.lastPing, }; }); @@ -76,7 +78,7 @@ class DeviceRepository { async callFunction( deviceID: string, functionName: string, - functionArguments: string, + functionArguments: Object, ): Promise<*> { const core = this._deviceServer.getCore(deviceID); if (!core) { @@ -123,7 +125,7 @@ class DeviceRepository { registrar: userID, timestamp: new Date(), }; - this._deviceAttributeRepository.update(deviceID, attributes); + this._deviceAttributeRepository.update(attributes); return await this.getByID(deviceID); } diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UsersFileRepository.js index 57f4c808..c8d26928 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UsersFileRepository.js @@ -2,17 +2,19 @@ import type { TokenObject, User, UserCredentials } from '../../types'; -import { FileManager, uuid } from 'spark-protocol'; +import { JSONFileManager, uuid } from 'spark-protocol'; import PasswordHasher from '../PasswordHasher'; class UsersFileRepository { - _fileManager: FileManager; + _fileManager: JSONFileManager; constructor(path: string) { - this._fileManager = new FileManager(path); + this._fileManager = new JSONFileManager(path); } - create = async (userCredentials: UserCredentials): Promise => { + createWithCredentials = async ( + userCredentials: UserCredentials, + ): Promise => { const { username, password } = userCredentials; const salt = await PasswordHasher.generateSalt(); @@ -32,6 +34,14 @@ class UsersFileRepository { return modelToSave; }; + create = (user: User): User => { + throw 'Not implemented'; + }; + + update = (user: User): User => { + throw 'Not implemented'; + }; + getAll = (): Array => this._fileManager.getAllData(); @@ -41,23 +51,23 @@ class UsersFileRepository { getByUsername = (username: string): ?User => this.getAll().find((user: User): boolean => user.username === username); - async validateLogin(username: string, password: string): Promise { + validateLogin = async (username: string, password: string): Promise => { try { const user = this.getByUsername(username); if (!user) { - throw new Error('user doesn\'t exist'); + throw new Error('User doesn\'t exist'); } const hash = await PasswordHasher.hash(password, user.salt); if (hash !== user.passwordHash) { - throw new Error('wrong password'); + throw new Error('Wrong password'); } return user; } catch (error) { throw error; } - } + }; getByAccessToken = (accessToken: string): ?User => this.getAll().find((user: User): boolean => @@ -66,7 +76,7 @@ class UsersFileRepository { ), ); - deleteAccessToken = (user: User, token: string) => { + deleteAccessToken = (user: User, token: string): void => { const userToSave = { ...user, accessTokens: user.accessTokens.filter( @@ -87,7 +97,7 @@ class UsersFileRepository { user.username === username, ); - saveAccessToken = (userId: string, tokenObject: TokenObject) => { + saveAccessToken = (userId: string, tokenObject: TokenObject): void => { const user = this.getById(userId); const userToSave = { ...user, diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index cce9aee9..1507f456 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -1,6 +1,6 @@ // @flow -import type { Webhook, WebhookMutator } from '../../types'; +import type { Webhook } from '../../types'; import { JSONFileManager, uuid } from 'spark-protocol'; @@ -11,13 +11,10 @@ class WebhookFileRepository { this._fileManager = new JSONFileManager(path); } - create = (model: WebhookMutator): Webhook => { + create = (model: Webhook): Webhook => { const modelToSave = { ...model, created_at: new Date(), - // TODO: Add another repository for fetching users. This should be - // injected on every request so we can easily get the current user - created_by: null, // user id id: uuid(), }; @@ -33,6 +30,10 @@ class WebhookFileRepository { getById = (id: string): Webhook => this._fileManager.getFile(`${id}.json`); + + update = (model: Webhook): Webhook => { + throw 'Not implemented'; + }; } export default WebhookFileRepository; diff --git a/src/types.js b/src/types.js index 4c68edcb..ecded3a0 100644 --- a/src/types.js +++ b/src/types.js @@ -32,10 +32,10 @@ export type Client = { grants: Array, }; -export type Device = { export type DeviceAttributes = { - deviceId: string, + deviceID: string, ip: string, + name: string, particleProductId: number, productFirmwareVersion: string, registrar: string, @@ -69,10 +69,6 @@ export type UserCredentials = { password: string, }; -export type Repository = { - create: (model: TMutator) => TModel, - deleteById: (id: string) => void, - export type Device = DeviceAttributes & { connected: boolean, lastFlashedAppName: ?string, @@ -80,20 +76,21 @@ export type Device = DeviceAttributes & { }; export type Repository = { - create: (id: string, model: TModel) => TModel, - delete: (id: string) => void, + create: (model: TModel) => TModel, + deleteById: (id: string) => void, getAll: () => Array, getById: (id: string) => TModel, - update: (id: string, model: TModel) => TModel, + update: (model: TModel) => TModel, }; -export type UsersRepository = Repository & { - deleteAccessToken: (user: User, accessToken: string) => void, - getByAccessToken: (accessToken: string) => User, - getByUsername: (username: string) => ?User, - isUserNameInUse: (username: string) => boolean, - saveAccessToken: (accessToken: string) => void, - validateLogin: (username: string, password: string) => User, +export type UsersRepository = Repository & { + createWithCredentials(credentials: UserCredentials): Promise, + deleteAccessToken(user: User, accessToken: string): void, + getByAccessToken(accessToken: string): ?User, + getByUsername(username: string): ?User, + isUserNameInUse(username: string): boolean, + saveAccessToken(userId: string, tokenObject: TokenObject): void, + validateLogin(username: string, password: string): Promise, }; export type Settings = { @@ -114,10 +111,17 @@ export type Settings = { serverKeyFile: string, serverKeyPassEnvVar: ?string, serverKeyPassFile: ?string, - usersRepository: Repository<*, *>, - webhookRepository: Repository<*, *>, + usersRepository: UsersRepository, + webhookRepository: Repository<*>, }; export type DeviceRepository = { + callFunction( + deviceID: string, + functionName: string, + functionArguments: Object, + ): Promise<*>, getAll(): Promise>, + getByID(deviceID: string): Promise, + provision(deviceID: string, userID: string, publicKey: string): Promise<*>, }; diff --git a/test/UsersController.test.js b/test/UsersController.test.js index 9725a9be..c2c36a09 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -1,5 +1,3 @@ -// @flow - import type { TokenObject, UserCredentials } from '../src/types'; import test from 'ava'; diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index 528deda1..43168007 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -1,5 +1,3 @@ -// @flow - import type { Webhook, WebhookMutator } from '../src/types'; import test from 'ava'; diff --git a/test/testApp.js b/test/testApp.js index 9ad57cca..e3c4cf7b 100644 --- a/test/testApp.js +++ b/test/testApp.js @@ -3,4 +3,6 @@ import createApp from '../src/app'; import settings from './settings'; -export default createApp(settings); +// TODO: mock the server or create a bootstrapper so there is only one instance +// of the device server +export default createApp(settings, {}); From 214fc51f8b775c0e6110423786aa1701a74a7616 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 18:23:54 -0800 Subject: [PATCH 148/504] Removing console.log --- src/lib/RouteConfig.js | 1 - src/lib/controllers/DevicesController__john.js | 1 - src/lib/repository/DeviceRepository__john.js | 1 - 3 files changed, 3 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index d547c227..691652d3 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -71,7 +71,6 @@ export default ( injectUserMiddleware(), async (request: $Request, response: $Response, ) => { - console.log('foao'); const argumentNames = (route.match(/:[\w]*/g) || []).map( argumentName => argumentName.replace(':', ''), ); diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index 75f2a3e9..325123c7 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -38,7 +38,6 @@ class DevicesController extends Controller { postBody: Object, ) { try { - console.log('foooobar') const result = await this._deviceRepository.callFunction( coreID, functionName, diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index 239dff5d..f3d843d3 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -51,7 +51,6 @@ class DeviceRepository { const devicesAttributes = await this._deviceAttributeRepository.getAll(); const devicePromises = devicesAttributes.map(async attributes => { const core = this._deviceServer.getCore(attributes.deviceID); - console.log(attributes); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ const response = core From 637f148eb9a279ce5177a7d5013a28b0db6a8545 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 18:46:59 -0800 Subject: [PATCH 149/504] Fixed `particle list` --- src/lib/controllers/DevicesController__john.js | 8 ++++---- src/lib/repository/DeviceRepository__john.js | 1 + src/views/api_v1.js | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index 325123c7..fee0132e 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -31,20 +31,20 @@ class DevicesController extends Controller { } @httpVerb('post') - @route('/v1/devices/:coreID/:functionName') + @route('/v1/devices/:deviceID/:functionName') async callDeviceFunction( - coreID: string, + deviceID: string, functionName: string, postBody: Object, ) { try { const result = await this._deviceRepository.callFunction( - coreID, + deviceID, functionName, postBody, ); - const device = await this._deviceRepository.getByID(coreID); + const device = await this._deviceRepository.getByID(deviceID); return this.ok(deviceToAPI(device, result)); } catch (exception) { if (exception.indexOf('Unknown Function') >= 0) { diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index f3d843d3..ea3fe233 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -120,6 +120,7 @@ class DeviceRepository { ); const attributes = { name: NAME_GENERATOR.choose(), + deviceID: deviceID, ...existingAttributes, registrar: userID, timestamp: new Date(), diff --git a/src/views/api_v1.js b/src/views/api_v1.js index ac5547db..1713a344 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -160,7 +160,7 @@ var Api = { descResult = results[1], coreState = null; - if (!doc || !doc.coreID) { + if (!doc || !doc.deviceID) { logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); res.json(404, "Oops, I couldn't find that core"); return; @@ -174,7 +174,7 @@ var Api = { } var device = { - id: doc.coreID, + id: doc.deviceID, name: doc.name || null, last_app: doc.last_flashed, connected: !!coreState, From e75ef89f81e2acfe7e55e95f9cbf82b48cda8633 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 11 Dec 2016 20:40:22 -0800 Subject: [PATCH 150/504] Migrated device state to controller. --- .../controllers/DevicesController__john.js | 11 +++++++++ src/lib/deviceToAPI.js | 4 ++++ src/lib/repository/DeviceRepository__john.js | 24 +++++++++++++++++++ src/types.js | 3 +++ src/views/api_v1.js | 2 +- 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index fee0132e..9ed29ad8 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -30,6 +30,17 @@ class DevicesController extends Controller { } } + @httpVerb('get') + @route('/v1/devices/:deviceID') + async getDevice(deviceID: string) { + try { + const device = await this._deviceRepository.getDetailsByID(deviceID); + return this.ok(deviceToAPI(device)); + } catch (exception) { + return this.bad(exception); + } + } + @httpVerb('post') @route('/v1/devices/:deviceID/:functionName') async callDeviceFunction( diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js index f49e95fd..16b1c9bb 100644 --- a/src/lib/deviceToAPI.js +++ b/src/lib/deviceToAPI.js @@ -6,6 +6,7 @@ export type DeviceAPIType = {| cellular: boolean, connected: boolean, current_build_target: string, + functions?: Array, id: string, last_app: ?string, last_heard: ?Date, @@ -15,12 +16,14 @@ export type DeviceAPIType = {| product_id: number, return_value?: mixed, status: string, + variables?: Object, |}; const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ cellular: false, // TODO: populate this from device. connected: device.connected, current_build_target: '', // TODO: populate this as well :( + functions: device.functions, id: device.deviceID, last_app: device.lastFlashedAppName, last_heard: device.lastHeard, @@ -30,6 +33,7 @@ const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ product_id: device.particleProductId, return_value: result, status: 'normal', // TODO: populate this from device + variables: device.variables, }); export default deviceToAPI; diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index ea3fe233..935d408b 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -47,6 +47,30 @@ class DeviceRepository { }; } + async getDetailsByID(deviceID: string): Promise { + const core = this._deviceServer.getCore(deviceID); + if (!core) { + throw 'Could not get device for ID'; + } + + return Promise.all([ + this._deviceAttributeRepository.getById(deviceID), + core.onApiMessage( + deviceID, + { cmd: "Describe" }, + ) + ]).then(([attributes, description]) => { + return { + ...attributes, + connected: true, + lastFlashedAppName: null, + lastHeard: new Date(), + functions: description.f, + variables: description.v, + }; + }) + + } async getAll(): Promise> { const devicesAttributes = await this._deviceAttributeRepository.getAll(); const devicePromises = devicesAttributes.map(async attributes => { diff --git a/src/types.js b/src/types.js index ecded3a0..6c79c0a7 100644 --- a/src/types.js +++ b/src/types.js @@ -71,8 +71,10 @@ export type UserCredentials = { export type Device = DeviceAttributes & { connected: boolean, + functions?: Array, lastFlashedAppName: ?string, lastHeard: ?Date, + variables?: Object, }; export type Repository = { @@ -122,6 +124,7 @@ export type DeviceRepository = { functionArguments: Object, ): Promise<*>, getAll(): Promise>, + getDetailsByID(deviceID: string): Promise<*>, getByID(deviceID: string): Promise, provision(deviceID: string, userID: string, publicKey: string): Promise<*>, }; diff --git a/src/views/api_v1.js b/src/views/api_v1.js index 1713a344..da9bc32e 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -53,7 +53,7 @@ var Api = { app.get('/v1/devices/:coreid/:var', Api.get_var); app.put('/v1/devices/:coreid', Api.set_core_attributes); - app.get('/v1/devices/:coreid', Api.get_core_attributes); + //app.get('/v1/devices/:coreid', Api.get_core_attributes); //doesn't need per-core permissions, only shows owned cores. //app.get('/v1/devices', Api.list_devices); From eae4de0c52af653eaea192faa7c5ee9ab60998be Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 12 Dec 2016 13:32:00 +0200 Subject: [PATCH 151/504] add moniker dep --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 8c304e57..9d53f5dd 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "express": "^4.14.0", "express-oauth-server": "^2.0.0-b1", "moment": "*", + "moniker": "^0.1.2", "morgan": "^1.7.0", "request": "*", "spark-protocol": "../spark-protocol", From fcbafd21832669dd7b0f0b411e75abfc68f87ce8 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 12 Dec 2016 14:50:01 +0200 Subject: [PATCH 152/504] clean --- src/lib/controllers/Controller.js | 20 ++--- .../controllers/DevicesController__john.js | 7 +- src/lib/controllers/ProvisioningController.js | 9 +-- src/lib/controllers/types.js | 5 +- src/lib/repository/DeviceRepository__john.js | 80 +++++++++---------- 5 files changed, 58 insertions(+), 63 deletions(-) diff --git a/src/lib/controllers/Controller.js b/src/lib/controllers/Controller.js index 1e662dda..c8c65b41 100644 --- a/src/lib/controllers/Controller.js +++ b/src/lib/controllers/Controller.js @@ -5,22 +5,16 @@ import type { User } from '../../types'; import type { HttpResult } from './types'; export default class Controller { - user: User; request: $Request; response: $Response; - bad(message: string, status: number = 400) { - return { - data: { - error: message, - ok: false, - }, - status: status, - }; - } + user: User; - bad = (message: string): HttpResult<*> => ({ - data: { message }, - status: 400, + bad = (message: string, status: number = 400): HttpResult<*> => ({ + data: { + error: message, + ok: false, + }, + status, }); ok = (output?: TType): HttpResult => ({ diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index 9ed29ad8..ed02dc79 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -1,8 +1,8 @@ // @flow import type { Device, DeviceRepository } from '../../types'; +import type { DeviceAPIType } from '../deviceToAPI'; -import settings from '../../settings'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; @@ -19,11 +19,12 @@ class DevicesController extends Controller { @httpVerb('get') @route('/v1/devices') - async getDevices() { + async getDevices(): Promise<*> { try { const devices = await this._deviceRepository.getAll(); - return this.ok(devices.map(device => deviceToAPI(device))); + return this.ok(devices.map((device: Device): DeviceAPIType => + deviceToAPI(device))); } catch (exception) { // I wish we could return no devices found but meh :/ return this.ok([]); diff --git a/src/lib/controllers/ProvisioningController.js b/src/lib/controllers/ProvisioningController.js index cc8e8879..3721575e 100644 --- a/src/lib/controllers/ProvisioningController.js +++ b/src/lib/controllers/ProvisioningController.js @@ -1,8 +1,7 @@ // @flow -import type { Device, DeviceRepository } from '../../types'; +import type { DeviceRepository } from '../../types'; -import settings from '../../settings'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; @@ -21,12 +20,12 @@ class ProvisioningController extends Controller { @route('/v1/provisioning/:coreID') async provision( coreID: string, - postBody: {publicKey: string}, - ) { + postBody: { publicKey: string }, + ): Promise<*> { try { const device = await this._deviceRepository.provision( coreID, - 'UserIDGoesHere', + this.user.id, postBody.publicKey, ); diff --git a/src/lib/controllers/types.js b/src/lib/controllers/types.js index 725c2895..e4e2b066 100644 --- a/src/lib/controllers/types.js +++ b/src/lib/controllers/types.js @@ -5,6 +5,9 @@ export type HttpResult = { data: ?TType, status: number, } | { - data: ?string, + data: { + error: string, + ok: false, + }, status: number, }; diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index 935d408b..e1fa9cb9 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -24,7 +24,7 @@ class DeviceRepository { this._deviceServer = deviceServer; } - async getByID(deviceID: string): Promise { + getByID = async (deviceID: string): Promise => { const attributes = await this._deviceAttributeRepository.getById(deviceID); const core = this._deviceServer.getCore(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent @@ -45,33 +45,31 @@ class DeviceRepository { lastFlashedAppName: null, lastHeard: response.lastPing, }; - } + }; - async getDetailsByID(deviceID: string): Promise { + getDetailsByID = async (deviceID: string): Promise => { const core = this._deviceServer.getCore(deviceID); if (!core) { - throw 'Could not get device for ID'; + throw new Error('Could not get device for ID'); } return Promise.all([ this._deviceAttributeRepository.getById(deviceID), core.onApiMessage( - deviceID, - { cmd: "Describe" }, - ) - ]).then(([attributes, description]) => { - return { - ...attributes, - connected: true, - lastFlashedAppName: null, - lastHeard: new Date(), - functions: description.f, - variables: description.v, - }; - }) + deviceID, + { cmd: 'Describe' }, + ), + ]).then(([attributes, description]): Device => ({ + ...attributes, + connected: true, + functions: description.f, + lastFlashedAppName: null, + lastHeard: new Date(), + variables: description.v, + })); + }; - } - async getAll(): Promise> { + getAll = async (): Promise> => { const devicesAttributes = await this._deviceAttributeRepository.getAll(); const devicePromises = devicesAttributes.map(async attributes => { const core = this._deviceServer.getCore(attributes.deviceID); @@ -96,21 +94,21 @@ class DeviceRepository { }); return Promise.all(devicePromises); - } + }; - async callFunction( + callFunction= async ( deviceID: string, functionName: string, functionArguments: Object, - ): Promise<*> { + ): Promise<*> => { const core = this._deviceServer.getCore(deviceID); if (!core) { return null; } - + console.log(functionArguments); const result = await core.onApiMessage( deviceID, - { cmd:'CallFn', name: functionName, args: functionArguments }, + { cmd: 'CallFn', name: functionName, args: functionArguments }, ); if (result.error) { @@ -118,33 +116,33 @@ class DeviceRepository { } return result.result; - } + }; - async provision( + provision = async ( deviceID: string, userID: string, publicKey: string, - ): Promise<*> { - if (!deviceID) { - throw 'No deviceID provided'; - } - - try { - const createdKey = ursa.createPublicKey(publicKey); - if (!publicKey || !ursa.isPublicKey(createdKey)) { - throw 'No key provided'; - } - } catch (exception) { - logger.error('error while parsing publicKey', exception); - throw 'Key error ' + exception; - } + ): Promise<*> => { + if (!deviceID) { + throw new Error('No deviceID provided'); + } + + try { + const createdKey = ursa.createPublicKey(publicKey); + if (!publicKey || !ursa.isPublicKey(createdKey)) { + throw new Error('No key provided'); + } + } catch (exception) { + logger.error('error while parsing publicKey', exception); + throw new Error(`Key error ${exception}`); + } this._deviceKeyRepository.update(deviceID, publicKey); const existingAttributes = this._deviceAttributeRepository.getById( deviceID, ); const attributes = { + deviceID, name: NAME_GENERATOR.choose(), - deviceID: deviceID, ...existingAttributes, registrar: userID, timestamp: new Date(), From 789a74823bd8b2428c736c95529282ab5d01eaa3 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 12 Dec 2016 15:53:03 +0200 Subject: [PATCH 153/504] remove noRouteMiddleWare, add 404 route in RouteConfig --- src/app.js | 8 -------- src/lib/RouteConfig.js | 4 ++++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/app.js b/src/app.js index 20f933e6..141a57ba 100644 --- a/src/app.js +++ b/src/app.js @@ -81,16 +81,8 @@ export default (settings: Settings, deviceServer: Object): $Application => { settings, ); - // TODO wny do we need next line? (Anton Puko) eventsV1.loadViews(app); api.loadViews(app); - const noRouteMiddleware: Middleware = ( - request: $Request, - response: $Response, - ): mixed => response.sendStatus(404); - - app.use(noRouteMiddleware); - return app; }; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 691652d3..60abbb49 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -103,4 +103,8 @@ export default ( }); }); }); + + app.all('*', (request: $Request, response: $Response): void => + response.sendStatus(404), + ); }; From 9fc4e0fba59f35e30ad79c60da730bf662ab35e4 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Mon, 12 Dec 2016 16:21:32 +0200 Subject: [PATCH 154/504] add rename device --- .../controllers/DevicesController__john.js | 25 +++++++++++++++++-- src/lib/repository/DeviceRepository__john.js | 9 +++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index ed02dc79..f03beb3a 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -33,7 +33,7 @@ class DevicesController extends Controller { @httpVerb('get') @route('/v1/devices/:deviceID') - async getDevice(deviceID: string) { + async getDevice(deviceID: string): Promise<*> { try { const device = await this._deviceRepository.getDetailsByID(deviceID); return this.ok(deviceToAPI(device)); @@ -42,13 +42,34 @@ class DevicesController extends Controller { } } + @httpVerb('put') + @route('/v1/devices/:deviceID') + async updateDevice(deviceID: string, postBody: { name?: string }): Promise<*> { + try { + // 1 rename device + if (postBody.name) { + const updatedAttributes = this._deviceRepository.renameDevice( + deviceID, + postBody.name, + ); + + return this.ok({ name: updatedAttributes.name, ok: true }); + } + + + return this.ok(); + } catch (exception) { + return this.bad(exception); + } + } + @httpVerb('post') @route('/v1/devices/:deviceID/:functionName') async callDeviceFunction( deviceID: string, functionName: string, postBody: Object, - ) { + ): Promise<*> { try { const result = await this._deviceRepository.callFunction( deviceID, diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index e1fa9cb9..3aaa7b74 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -150,6 +150,15 @@ class DeviceRepository { this._deviceAttributeRepository.update(attributes); return await this.getByID(deviceID); + }; + + renameDevice = (deviceID: string, name: string): DeviceAttributes => { + const attributes = this._deviceAttributeRepository.getById(deviceID); + const attributesToSave = { + ...attributes, + name, + }; + return this._deviceAttributeRepository.update(attributesToSave); } } From 6bcb8af2c0c1db549b9b43c92abac92e2ad6df34 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 13 Dec 2016 00:36:11 +0200 Subject: [PATCH 155/504] add multer, format routeConfig indentation --- package.json | 2 +- src/lib/RouteConfig.js | 62 ++++++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 9d53f5dd..e1441ecd 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "build:clean": "rimraf ./build", "lint": "eslint -- .", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks --ignore uploads", "start:prod": "npm run build && node ./build/main.js", "test": "ava", "test:watch": "ava --watch" diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 60abbb49..e0b8f60a 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -12,6 +12,7 @@ import type Controller from './controllers/Controller'; import OAuthModel from './OAuthModel'; import OAuthServer from 'express-oauth-server'; +import multer from 'multer'; // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => @@ -53,6 +54,8 @@ export default ( app.all('/v1/events*', oauth.authenticate()); // end temporary + const injectFilesMiddleware = multer(); + app.post(settings.loginRoute, oauth.token()); controllers.forEach((controller: Controller) => { @@ -69,38 +72,39 @@ export default ( route, maybe(oauth.authenticate(), !anonymous), injectUserMiddleware(), - async (request: $Request, response: $Response, - ) => { - const argumentNames = (route.match(/:[\w]*/g) || []).map( - argumentName => argumentName.replace(':', ''), - ); - const values = argumentNames - .map((argument: string): string => request.params[argument]) - .filter((value: ?Object): boolean => value !== undefined); + injectFilesMiddleware.any(), + async(request: $Request, response: $Response) => { + const argumentNames = (route.match(/:[\w]*/g) || []).map( + (argumentName: string): string => argumentName.replace(':', ''), + ); + const values = argumentNames + .map((argument: string): string => request.params[argument]) + .filter((value: ?Object): boolean => value !== undefined); - const controllerContext = Object.create(controller); - controllerContext.request = request; - controllerContext.response = response; - controllerContext.user = (request: any).user; + const controllerContext = Object.create(controller); + controllerContext.request = request; + controllerContext.response = response; + controllerContext.user = (request: any).user; - // Take access token out if it's posted. - const { - access_token, - ...body, - } = request.body; - const result = mappedFunction.call( - controllerContext, - ...values, - body, - ); - if (result.then) { - result.then(result => { + // Take access token out if it's posted. + const { + access_token, + ...body + } = request.body; + const result = mappedFunction.call( + controllerContext, + ...values, + body, + ); + if (result.then) { + // eslint-disable-next-line no-shadow + result.then((result: Object): void => + response.status(result.status).json(result.data), + ); + } else { response.status(result.status).json(result.data); - }); - } else { - response.status(result.status).json(result.data); - } - }); + } + }); }); }); From 7c3b68c5a6a450ea30a55be6cf58d9c224b8298a Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 13 Dec 2016 00:40:58 +0200 Subject: [PATCH 156/504] start implementing flashing device --- .../controllers/DevicesController__john.js | 15 ++++++- src/lib/repository/DeviceRepository__john.js | 45 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DevicesController__john.js index f03beb3a..ba085f9f 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DevicesController__john.js @@ -3,6 +3,7 @@ import type { Device, DeviceRepository } from '../../types'; import type { DeviceAPIType } from '../deviceToAPI'; + import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; @@ -55,9 +56,19 @@ class DevicesController extends Controller { return this.ok({ name: updatedAttributes.name, ok: true }); } + // TODO not implemented yet + // 2 flash device with known app + if (this.request.app_id) { + this._deviceRepository.flashKnownApp(deviceID, this.request.files); + return this.ok({ id: deviceID, status: 'Update started' }); + } - - return this.ok(); + // TODO not implemented yet + // 3 flash device with precompiled binary + if (this.request.files) { + this._deviceRepository.flashBinary(deviceID, this.request.files); + return this.ok({ id: deviceID, status: 'Update started' }); + } } catch (exception) { return this.bad(exception); } diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository__john.js index 3aaa7b74..ed3b11df 100644 --- a/src/lib/repository/DeviceRepository__john.js +++ b/src/lib/repository/DeviceRepository__john.js @@ -105,7 +105,6 @@ class DeviceRepository { if (!core) { return null; } - console.log(functionArguments); const result = await core.onApiMessage( deviceID, { cmd: 'CallFn', name: functionName, args: functionArguments }, @@ -118,6 +117,50 @@ class DeviceRepository { return result.result; }; + flashBinary = async ( + deviceID: string, + files: string, + ) => { + // TODO not implemented yet + const core = this._deviceServer.getCore(deviceID); + if (!core) { + return null; + } + + const result = await core.onApiMessage( + deviceID, + { cmd: 'UFlash', args: { data: files[0].buffer } }, + ); + + if (result.error) { + throw result.error; + } + + return result.result; + }; + + flashKnownApp = async ( + deviceID: string, + app: string, + ) => { + // TODO not implemented yet + const core = this._deviceServer.getCore(deviceID); + if (!core) { + return null; + } + + const result = await core.onApiMessage( + deviceID, + { cmd: 'FlashKnown', app }, + ); + + if (result.error) { + throw result.error; + } + + return result.result; + }; + provision = async ( deviceID: string, userID: string, From e32a58d702d48796d6527d49df9e727d23c6e275 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 13 Dec 2016 00:46:52 +0200 Subject: [PATCH 157/504] remove __john from file names. --- package.json | 2 +- src/app.js | 6 +++--- .../{DevicesController__john.js => DeviceController.js} | 4 ++-- .../{DeviceRepository__john.js => DeviceRepository.js} | 0 4 files changed, 6 insertions(+), 6 deletions(-) rename src/lib/controllers/{DevicesController__john.js => DeviceController.js} (97%) rename src/lib/repository/{DeviceRepository__john.js => DeviceRepository.js} (100%) diff --git a/package.json b/package.json index e1441ecd..9d53f5dd 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "build:clean": "rimraf ./build", "lint": "eslint -- .", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks --ignore uploads", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", "start:prod": "npm run build && node ./build/main.js", "test": "ava", "test:watch": "ava --watch" diff --git a/src/app.js b/src/app.js index 141a57ba..0a5d2688 100644 --- a/src/app.js +++ b/src/app.js @@ -17,7 +17,7 @@ import api from './views/api_v1'; import eventsV1 from './views/EventViews001'; // Repositories -import DeviceRepository from './lib/repository/DeviceRepository__john'; +import DeviceRepository from './lib/repository/DeviceRepository'; import { DeviceAttributeFileRepository, DeviceKeyFileRepository, @@ -25,7 +25,7 @@ import { // Routing import routeConfig from './lib/RouteConfig'; -import DevicesController from './lib/controllers/DevicesController__john'; +import DeviceController from './lib/controllers/DeviceController'; import ProvisioningController from './lib/controllers/ProvisioningController'; import UsersController from './lib/controllers/UsersController'; import WebhookController from './lib/controllers/WebhookController'; @@ -73,7 +73,7 @@ export default (settings: Settings, deviceServer: Object): $Application => { routeConfig( app, [ - new DevicesController(deviceRepository), + new DeviceController(deviceRepository), new ProvisioningController(deviceRepository), new UsersController(settings.usersRepository), new WebhookController(settings.webhookRepository), diff --git a/src/lib/controllers/DevicesController__john.js b/src/lib/controllers/DeviceController.js similarity index 97% rename from src/lib/controllers/DevicesController__john.js rename to src/lib/controllers/DeviceController.js index ba085f9f..e13c3c23 100644 --- a/src/lib/controllers/DevicesController__john.js +++ b/src/lib/controllers/DeviceController.js @@ -9,7 +9,7 @@ import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; import deviceToAPI from '../deviceToAPI'; -class DevicesController extends Controller { +class DeviceController extends Controller { _deviceRepository: DeviceRepository; constructor(deviceRepository: DeviceRepository) { @@ -103,4 +103,4 @@ class DevicesController extends Controller { } } -export default DevicesController; +export default DeviceController; diff --git a/src/lib/repository/DeviceRepository__john.js b/src/lib/repository/DeviceRepository.js similarity index 100% rename from src/lib/repository/DeviceRepository__john.js rename to src/lib/repository/DeviceRepository.js From 8bf718e20bcaf4036d58ccdd984210c818204584 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 13 Dec 2016 02:24:51 +0200 Subject: [PATCH 158/504] rename files --- src/app.js | 8 ++++---- src/lib/OAuthModel.js | 14 +++++++------- ...eviceController.js => DevicesController.js} | 4 ++-- src/lib/controllers/UsersController.js | 18 +++++++++--------- ...hookController.js => WebhooksController.js} | 4 ++-- ...FileRepository.js => UserFileRepository.js} | 4 ++-- src/settings.js | 2 +- src/types.js | 4 ++-- test/settings.js | 2 +- 9 files changed, 30 insertions(+), 30 deletions(-) rename src/lib/controllers/{DeviceController.js => DevicesController.js} (97%) rename src/lib/controllers/{WebhookController.js => WebhooksController.js} (96%) rename src/lib/repository/{UsersFileRepository.js => UserFileRepository.js} (97%) diff --git a/src/app.js b/src/app.js index 0a5d2688..e872d4d8 100644 --- a/src/app.js +++ b/src/app.js @@ -25,10 +25,10 @@ import { // Routing import routeConfig from './lib/RouteConfig'; -import DeviceController from './lib/controllers/DeviceController'; +import DevicesController from './lib/controllers/DevicesController'; import ProvisioningController from './lib/controllers/ProvisioningController'; import UsersController from './lib/controllers/UsersController'; -import WebhookController from './lib/controllers/WebhookController'; +import WebhooksController from './lib/controllers/WebhooksController'; export default (settings: Settings, deviceServer: Object): $Application => { const app = express(); @@ -73,10 +73,10 @@ export default (settings: Settings, deviceServer: Object): $Application => { routeConfig( app, [ - new DeviceController(deviceRepository), + new DevicesController(deviceRepository), new ProvisioningController(deviceRepository), new UsersController(settings.usersRepository), - new WebhookController(settings.webhookRepository), + new WebhooksController(settings.webhookRepository), ], settings, ); diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index 02822b9f..01457242 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -4,20 +4,20 @@ import type { Client, TokenObject, User, - UsersRepository, + UserRepository, } from '../types'; import ouathClients from '../oauthClients.json'; class OauthModel { - _usersRepository: UsersRepository; + _userRepository: UserRepository; - constructor(usersRepository: UsersRepository) { - this._usersRepository = usersRepository; + constructor(userRepository: UserRepository) { + this._userRepository = userRepository; } getAccessToken = (bearerToken: string): ?Object => { - const user = this._usersRepository.getByAccessToken(bearerToken); + const user = this._userRepository.getByAccessToken(bearerToken); if (!user) { return null; } @@ -43,11 +43,11 @@ class OauthModel { ); getUser = async (username: string, password: string): Promise => - await this._usersRepository.validateLogin(username, password); + await this._userRepository.validateLogin(username, password); saveToken = (tokenObject: TokenObject, client: Client, user: User): Object => { - this._usersRepository.saveAccessToken(user.id, tokenObject); + this._userRepository.saveAccessToken(user.id, tokenObject); return { accessToken: tokenObject.accessToken, client, diff --git a/src/lib/controllers/DeviceController.js b/src/lib/controllers/DevicesController.js similarity index 97% rename from src/lib/controllers/DeviceController.js rename to src/lib/controllers/DevicesController.js index e13c3c23..ba085f9f 100644 --- a/src/lib/controllers/DeviceController.js +++ b/src/lib/controllers/DevicesController.js @@ -9,7 +9,7 @@ import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; import deviceToAPI from '../deviceToAPI'; -class DeviceController extends Controller { +class DevicesController extends Controller { _deviceRepository: DeviceRepository; constructor(deviceRepository: DeviceRepository) { @@ -103,4 +103,4 @@ class DeviceController extends Controller { } } -export default DeviceController; +export default DevicesController; diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index 0062fe87..870f2fb6 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -2,7 +2,7 @@ import type { UserCredentials, - UsersRepository, + UserRepository, } from '../../types'; import basicAuthParser from 'basic-auth-parser'; @@ -12,11 +12,11 @@ import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; class UsersController extends Controller { - _usersRepository: UsersRepository; + _userRepository: UserRepository; - constructor(usersRepository: UsersRepository) { + constructor(userRepository: UserRepository) { super(); - this._usersRepository = usersRepository; + this._userRepository = userRepository; } @httpVerb('post') @@ -25,13 +25,13 @@ class UsersController extends Controller { async createUser(userCredentials: UserCredentials): Promise<*> { try { const isUserNameInUse = - this._usersRepository.isUserNameInUse(userCredentials.username); + this._userRepository.isUserNameInUse(userCredentials.username); if (isUserNameInUse) { throw new Error('user with the username is already exist'); } - const newUser = await this._usersRepository.createWithCredentials( + const newUser = await this._userRepository.createWithCredentials( userCredentials, ); return this.ok(newUser); @@ -48,12 +48,12 @@ class UsersController extends Controller { const { username, password } = basicAuthParser( this.request.get('authorization'), ); - const user = await this._usersRepository.validateLogin( + const user = await this._userRepository.validateLogin( username, password, ); - this._usersRepository.deleteAccessToken(user, token); + this._userRepository.deleteAccessToken(user, token); return this.ok({ ok: true }); } catch (error) { @@ -69,7 +69,7 @@ class UsersController extends Controller { const { username, password } = basicAuthParser( this.request.get('authorization'), ); - const user = await this._usersRepository.validateLogin(username, password); + const user = await this._userRepository.validateLogin(username, password); return this.ok(user.accessTokens); } catch (error) { return this.bad(error.message); diff --git a/src/lib/controllers/WebhookController.js b/src/lib/controllers/WebhooksController.js similarity index 96% rename from src/lib/controllers/WebhookController.js rename to src/lib/controllers/WebhooksController.js index 19f3dfe0..d85dec9d 100644 --- a/src/lib/controllers/WebhookController.js +++ b/src/lib/controllers/WebhooksController.js @@ -32,7 +32,7 @@ const validateWebhookMutator = (webhookMutator: WebhookMutator): ?Error => { return null; }; -class WebhookController extends Controller { +class WebhooksController extends Controller { _webhookRepository: Repository; constructor(webhookRepository: Repository) { @@ -87,4 +87,4 @@ class WebhookController extends Controller { } } -export default WebhookController; +export default WebhooksController; diff --git a/src/lib/repository/UsersFileRepository.js b/src/lib/repository/UserFileRepository.js similarity index 97% rename from src/lib/repository/UsersFileRepository.js rename to src/lib/repository/UserFileRepository.js index c8d26928..0ac4be0f 100644 --- a/src/lib/repository/UsersFileRepository.js +++ b/src/lib/repository/UserFileRepository.js @@ -5,7 +5,7 @@ import type { TokenObject, User, UserCredentials } from '../../types'; import { JSONFileManager, uuid } from 'spark-protocol'; import PasswordHasher from '../PasswordHasher'; -class UsersFileRepository { +class UserFileRepository { _fileManager: JSONFileManager; constructor(path: string) { @@ -108,4 +108,4 @@ class UsersFileRepository { } } -export default UsersFileRepository; +export default UserFileRepository; diff --git a/src/settings.js b/src/settings.js index 74fe8ffd..6f8642e6 100644 --- a/src/settings.js +++ b/src/settings.js @@ -21,7 +21,7 @@ import path from 'path'; import WebhookFileRepository from './lib/repository/WebhookFileRepository'; -import UsersFileRepository from './lib/repository/UsersFileRepository'; +import UsersFileRepository from './lib/repository/UserFileRepository'; export default { accessTokenLifetime: 7776000, // 90 days, diff --git a/src/types.js b/src/types.js index 6c79c0a7..04752a7e 100644 --- a/src/types.js +++ b/src/types.js @@ -85,7 +85,7 @@ export type Repository = { update: (model: TModel) => TModel, }; -export type UsersRepository = Repository & { +export type UserRepository = Repository & { createWithCredentials(credentials: UserCredentials): Promise, deleteAccessToken(user: User, accessToken: string): void, getByAccessToken(accessToken: string): ?User, @@ -113,7 +113,7 @@ export type Settings = { serverKeyFile: string, serverKeyPassEnvVar: ?string, serverKeyPassFile: ?string, - usersRepository: UsersRepository, + usersRepository: UserRepository, webhookRepository: Repository<*>, }; diff --git a/test/settings.js b/test/settings.js index 4d0097b7..1d1cbd56 100644 --- a/test/settings.js +++ b/test/settings.js @@ -2,7 +2,7 @@ import path from 'path'; import WebhookFileRepository from '../src/lib/repository/WebhookFileRepository'; -import UsersFileRepository from '../src/lib/repository/UsersFileRepository'; +import UsersFileRepository from '../src/lib/repository/UserFileRepository'; export default { accessTokenLifetime: 7776000, // 90 days, From b7a2b1a73fe68ddf2be52a385acfcc14e8d110db Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 12 Dec 2016 22:07:37 -0800 Subject: [PATCH 159/504] Fixed flow error and probably some bugs --- flow-typed/npm/express_v4.x.x.js | 14 ++++++++++++++ src/lib/RouteConfig.js | 12 ++++++------ src/lib/controllers/DevicesController.js | 16 +++++++++++----- src/lib/repository/DeviceRepository.js | 13 ++++++++----- src/types.js | 5 +++++ 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 6854bd3a..7f59461c 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -15,10 +15,23 @@ declare class express$RequestResponseBase { get(field: string): string | void; } +declare type $File = { + buffer: Buffer, + destination: string, + encoding: string, + fieldname: string, + filename: string, + mimetype: string, + originalname: string, + path: string, + size: number, +}; + declare class express$Request extends http$IncomingMessage mixins express$RequestResponseBase { baseUrl: string; body: Object; cookies: {[cookie: string]: string}; + files: Array<$File>, fresh: boolean; hostname: boolean; ip: string; @@ -171,6 +184,7 @@ declare module 'express' { declare type RouterOptions = express$RouterOptions; declare type CookieOptions = express$CookieOptions; + declare type File = $File; declare type Middleware = express$Middleware; declare type NextFunction = express$NextFunction; declare type $Response = express$Response; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index e0b8f60a..5fb1c090 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -98,9 +98,9 @@ export default ( ); if (result.then) { // eslint-disable-next-line no-shadow - result.then((result: Object): void => - response.status(result.status).json(result.data), - ); + result.then((result: Object): void => { + response.status(result.status).json(result.data); + }); } else { response.status(result.status).json(result.data); } @@ -108,7 +108,7 @@ export default ( }); }); - app.all('*', (request: $Request, response: $Response): void => - response.sendStatus(404), - ); + app.all('*', (request: $Request, response: $Response): void => { + response.sendStatus(404); + }); }; diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index ba085f9f..e02bca92 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -45,11 +45,14 @@ class DevicesController extends Controller { @httpVerb('put') @route('/v1/devices/:deviceID') - async updateDevice(deviceID: string, postBody: { name?: string }): Promise<*> { + async updateDevice( + deviceID: string, + postBody: { app_id?: string, name?: string }, + ): Promise<*> { try { // 1 rename device if (postBody.name) { - const updatedAttributes = this._deviceRepository.renameDevice( + const updatedAttributes = await this._deviceRepository.renameDevice( deviceID, postBody.name, ); @@ -58,15 +61,18 @@ class DevicesController extends Controller { } // TODO not implemented yet // 2 flash device with known app - if (this.request.app_id) { - this._deviceRepository.flashKnownApp(deviceID, this.request.files); + if (postBody.app_id) { + await this._deviceRepository.flashKnownApp( + deviceID, + postBody.app_id, + ); return this.ok({ id: deviceID, status: 'Update started' }); } // TODO not implemented yet // 3 flash device with precompiled binary if (this.request.files) { - this._deviceRepository.flashBinary(deviceID, this.request.files); + await this._deviceRepository.flashBinary(deviceID, this.request.files); return this.ok({ id: deviceID, status: 'Update started' }); } } catch (exception) { diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index ed3b11df..443340fd 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -1,5 +1,6 @@ // @flow +import type {File} from 'express'; import type { DeviceServer } from 'spark-protocol'; import type { Device, DeviceAttributes, Repository } from '../../types'; @@ -119,9 +120,8 @@ class DeviceRepository { flashBinary = async ( deviceID: string, - files: string, + files: Array, ) => { - // TODO not implemented yet const core = this._deviceServer.getCore(deviceID); if (!core) { return null; @@ -195,13 +195,16 @@ class DeviceRepository { return await this.getByID(deviceID); }; - renameDevice = (deviceID: string, name: string): DeviceAttributes => { - const attributes = this._deviceAttributeRepository.getById(deviceID); + renameDevice = async ( + deviceID: string, + name: string, + ): Promise => { + const attributes = await this._deviceAttributeRepository.getById(deviceID); const attributesToSave = { ...attributes, name, }; - return this._deviceAttributeRepository.update(attributesToSave); + return await this._deviceAttributeRepository.update(attributesToSave); } } diff --git a/src/types.js b/src/types.js index 04752a7e..f107b872 100644 --- a/src/types.js +++ b/src/types.js @@ -1,5 +1,7 @@ // @flow +import type {File} from 'express'; + export type Webhook = WebhookMutator & { created_at: Date, id: string, @@ -123,8 +125,11 @@ export type DeviceRepository = { functionName: string, functionArguments: Object, ): Promise<*>, + flashKnownApp(deviceID: string, app: string): Promise<*>, + flashBinary(deviceID: string, files: Array<$File>): Promise<*>, getAll(): Promise>, getDetailsByID(deviceID: string): Promise<*>, getByID(deviceID: string): Promise, provision(deviceID: string, userID: string, publicKey: string): Promise<*>, + renameDevice(deviceID: string, name: string): Promise, }; From b0cae830d0d64b7d3f7565ba1148b67dc1986db4 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Tue, 13 Dec 2016 07:32:48 -0800 Subject: [PATCH 160/504] Added `allowUpload` and filter out file.s --- flow-typed/npm/express_v4.x.x.js | 2 +- src/lib/RouteConfig.js | 23 ++++++++++++++++++++--- src/lib/controllers/DevicesController.js | 14 +++++++++----- src/lib/decorators/allowUpload.js | 14 ++++++++++++++ 4 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 src/lib/decorators/allowUpload.js diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 7f59461c..5d49c478 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -31,7 +31,7 @@ declare class express$Request extends http$IncomingMessage mixins express$Reques baseUrl: string; body: Object; cookies: {[cookie: string]: string}; - files: Array<$File>, + files: {[key: string]: Array<$File>}, fresh: boolean; hostname: boolean; ip: string; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 5fb1c090..84761ca2 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -34,7 +34,8 @@ const injectUserMiddleware = (): Middleware => } next(); }; - +const defaultMiddleware = + (request: $Request, response: $Response, next: NextFunction) => next(); export default ( app: $Application, @@ -63,7 +64,7 @@ export default ( (Object.getPrototypeOf(controller): any), ).forEach((functionName: string) => { const mappedFunction = (controller: any)[functionName]; - const { httpVerb, route, anonymous } = mappedFunction; + const { allowedUploads, anonymous, httpVerb, route } = mappedFunction; if (!httpVerb) { return; } @@ -72,7 +73,9 @@ export default ( route, maybe(oauth.authenticate(), !anonymous), injectUserMiddleware(), - injectFilesMiddleware.any(), + allowedUploads + ? injectFilesMiddleware.fields(allowedUploads) + : defaultMiddleware, async(request: $Request, response: $Response) => { const argumentNames = (route.match(/:[\w]*/g) || []).map( (argumentName: string): string => argumentName.replace(':', ''), @@ -111,4 +114,18 @@ export default ( app.all('*', (request: $Request, response: $Response): void => { response.sendStatus(404); }); + + (app: any).use(( + error: string, + request: $Request, + response: $Response, + next: NextFunction + ): void => { + response + .status(400) + .json({ + error: error.code ? error.code : error, + ok: false, + }); + }) }; diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index e02bca92..d6982121 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -5,6 +5,7 @@ import type { DeviceAPIType } from '../deviceToAPI'; import Controller from './Controller'; +import allowUpload from '../decorators/allowUpload'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; import deviceToAPI from '../deviceToAPI'; @@ -45,9 +46,10 @@ class DevicesController extends Controller { @httpVerb('put') @route('/v1/devices/:deviceID') + @allowUpload('file', 1) async updateDevice( deviceID: string, - postBody: { app_id?: string, name?: string }, + postBody: { app_id?: string, name?: string, file_type?: 'binary' }, ): Promise<*> { try { // 1 rename device @@ -62,19 +64,21 @@ class DevicesController extends Controller { // TODO not implemented yet // 2 flash device with known app if (postBody.app_id) { - await this._deviceRepository.flashKnownApp( + this._deviceRepository.flashKnownApp( deviceID, postBody.app_id, ); return this.ok({ id: deviceID, status: 'Update started' }); } - +console.log(postBody); // TODO not implemented yet // 3 flash device with precompiled binary - if (this.request.files) { - await this._deviceRepository.flashBinary(deviceID, this.request.files); + if (postBody.file_type === 'binary' && this.request.files.file) { + this._deviceRepository.flashBinary(deviceID, this.request.files.file); return this.ok({ id: deviceID, status: 'Update started' }); } + + throw new Error('Did not update device'); } catch (exception) { return this.bad(exception); } diff --git a/src/lib/decorators/allowUpload.js b/src/lib/decorators/allowUpload.js new file mode 100644 index 00000000..4df92724 --- /dev/null +++ b/src/lib/decorators/allowUpload.js @@ -0,0 +1,14 @@ +import type { Decorator, HttpVerb } from './types'; + +export default ( + fileName: string, + maxCount: number = 0, +): Decorator => (target, name, descriptor): Object => { + const allowedUploads = target[name].allowedUploads || []; + allowedUploads.push({ + maxCount: maxCount ? maxCount : undefined, + name: fileName, + }); + target[name].allowedUploads = allowedUploads; + return descriptor; +}; From 47eeeab7443b54b73bc3be29e8aade1303d4492c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 13 Dec 2016 17:59:04 +0200 Subject: [PATCH 161/504] add claim/unclaim device, add user checking for getDevices() and renameDevice() --- src/lib/controllers/DevicesController.js | 43 ++++++++++++++-- src/lib/repository/DeviceRepository.js | 64 +++++++++++++++++++++--- src/types.js | 13 +++-- 3 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index d6982121..d2454deb 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -19,11 +19,40 @@ class DevicesController extends Controller { this._deviceRepository = deviceRepository; } + @httpVerb('post') + @route('/v1/devices') + async claimDevice(postBody: { id: string}): Promise<*> { + try { + const deviceID = postBody.id; + const userID = this.user.id; + + await this._deviceRepository.claimDevice(deviceID, userID); + + return this.ok({ ok: true }); + } catch (exception) { + return this.bad(exception.message); + } + } + + @httpVerb('delete') + @route('/v1/devices/:deviceID') + async unclaimDevice(deviceID: string): Promise<*> { + try { + const userID = this.user.id; + await this._deviceRepository.unclaimDevice(deviceID, userID); + + return this.ok({ ok: true }); + } catch (exception) { + return this.bad(exception.message); + } + } + @httpVerb('get') @route('/v1/devices') async getDevices(): Promise<*> { try { - const devices = await this._deviceRepository.getAll(); + const userID = this.user.id; + const devices = await this._deviceRepository.getAll(userID); return this.ok(devices.map((device: Device): DeviceAPIType => deviceToAPI(device))); @@ -37,10 +66,11 @@ class DevicesController extends Controller { @route('/v1/devices/:deviceID') async getDevice(deviceID: string): Promise<*> { try { + // TODO add userID checking const device = await this._deviceRepository.getDetailsByID(deviceID); return this.ok(deviceToAPI(device)); } catch (exception) { - return this.bad(exception); + return this.bad(exception.message); } } @@ -52,10 +82,12 @@ class DevicesController extends Controller { postBody: { app_id?: string, name?: string, file_type?: 'binary' }, ): Promise<*> { try { + const userID = this.user.id; // 1 rename device if (postBody.name) { const updatedAttributes = await this._deviceRepository.renameDevice( deviceID, + userID, postBody.name, ); @@ -70,17 +102,17 @@ class DevicesController extends Controller { ); return this.ok({ id: deviceID, status: 'Update started' }); } -console.log(postBody); + // TODO not implemented yet // 3 flash device with precompiled binary if (postBody.file_type === 'binary' && this.request.files.file) { - this._deviceRepository.flashBinary(deviceID, this.request.files.file); + await this._deviceRepository.flashBinary(deviceID, this.request.files.file); return this.ok({ id: deviceID, status: 'Update started' }); } throw new Error('Did not update device'); } catch (exception) { - return this.bad(exception); + return this.bad(exception.message); } } @@ -98,6 +130,7 @@ console.log(postBody); postBody, ); + // TODO add userID checking const device = await this._deviceRepository.getByID(deviceID); return this.ok(deviceToAPI(device, result)); } catch (exception) { diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index 443340fd..7da1eb1d 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -1,6 +1,6 @@ // @flow -import type {File} from 'express'; +import type { File } from 'express'; import type { DeviceServer } from 'spark-protocol'; import type { Device, DeviceAttributes, Repository } from '../../types'; @@ -25,8 +25,49 @@ class DeviceRepository { this._deviceServer = deviceServer; } - getByID = async (deviceID: string): Promise => { - const attributes = await this._deviceAttributeRepository.getById(deviceID); + claimDevice = async ( + deviceID: string, + userID: string, + ): Promise => { + const deviceAttributes = await this._deviceAttributeRepository.getById(deviceID); + + if (!deviceAttributes) { + throw new Error('No device found'); + } + if (deviceAttributes.ownerID && deviceAttributes.ownerID !== userID) { + throw new Error('The device belongs to someone else.'); + } + + const attributesToSave = { + ...deviceAttributes, + ownerID: userID, + }; + return await this._deviceAttributeRepository.update(attributesToSave); + }; + + unclaimDevice = async ( + deviceID: string, + userID: string, + ): Promise => { + const userDevicesAttributes = await this.getAll(userID); + const deviceAttributes = userDevicesAttributes.find( + (attributes: DeviceAttributes): boolean => + attributes.deviceID === deviceID, + ); + + if (!deviceAttributes) { + throw new Error('No device found'); + } + + const attributesToSave = { + ...deviceAttributes, + ownerID: null, + }; + return await this._deviceAttributeRepository.update(attributesToSave); + }; + + getByID = async (deviceID: string, userID: string): Promise => { + const attributes = await this._deviceAttributeRepository.getById(deviceID, userID); const core = this._deviceServer.getCore(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ @@ -70,8 +111,8 @@ class DeviceRepository { })); }; - getAll = async (): Promise> => { - const devicesAttributes = await this._deviceAttributeRepository.getAll(); + getAll = async (userID: string): Promise> => { + const devicesAttributes = await this._deviceAttributeRepository.getAll(userID); const devicePromises = devicesAttributes.map(async attributes => { const core = this._deviceServer.getCore(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent @@ -187,19 +228,26 @@ class DeviceRepository { deviceID, name: NAME_GENERATOR.choose(), ...existingAttributes, + ownerID: userID, registrar: userID, timestamp: new Date(), }; - this._deviceAttributeRepository.update(attributes); + await this._deviceAttributeRepository.update(attributes); - return await this.getByID(deviceID); + return await this.getByID(deviceID, userID); }; renameDevice = async ( deviceID: string, + userID: string, name: string, ): Promise => { - const attributes = await this._deviceAttributeRepository.getById(deviceID); + const attributes = await this._deviceAttributeRepository.getById(deviceID, userID); + + if (!attributes) { + throw new Error('No device found'); + } + const attributesToSave = { ...attributes, name, diff --git a/src/types.js b/src/types.js index f107b872..a29d6913 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,6 @@ // @flow -import type {File} from 'express'; +import type { File } from 'express'; export type Webhook = WebhookMutator & { created_at: Date, @@ -38,6 +38,7 @@ export type DeviceAttributes = { deviceID: string, ip: string, name: string, + ownerID: ?string, particleProductId: number, productFirmwareVersion: string, registrar: string, @@ -125,11 +126,13 @@ export type DeviceRepository = { functionName: string, functionArguments: Object, ): Promise<*>, - flashKnownApp(deviceID: string, app: string): Promise<*>, + claimDevice(deviceID: string, userID: string): Promise, flashBinary(deviceID: string, files: Array<$File>): Promise<*>, - getAll(): Promise>, + flashKnownApp(deviceID: string, app: string): Promise<*>, + getAll(userID: string): Promise>, + getByID(deviceID: string, userID: string): Promise, getDetailsByID(deviceID: string): Promise<*>, - getByID(deviceID: string): Promise, provision(deviceID: string, userID: string, publicKey: string): Promise<*>, - renameDevice(deviceID: string, name: string): Promise, + renameDevice(deviceID: string, userID: string, name: string): Promise, + unclaimDevice(deviceID: string, userID: string): Promise, }; From c7876dda2512e0be3c6f4fe20574e7ce9c90997f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 13 Dec 2016 18:32:55 +0200 Subject: [PATCH 162/504] fix unclaimDevice to getById --- src/lib/repository/DeviceRepository.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index 7da1eb1d..afc79795 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -49,11 +49,8 @@ class DeviceRepository { deviceID: string, userID: string, ): Promise => { - const userDevicesAttributes = await this.getAll(userID); - const deviceAttributes = userDevicesAttributes.find( - (attributes: DeviceAttributes): boolean => - attributes.deviceID === deviceID, - ); + const deviceAttributes = + await this._deviceAttributeRepository.getById(deviceID, userID); if (!deviceAttributes) { throw new Error('No device found'); From 3c604d33acdcd3a67989bc5a6ab7b6d2a2f98a21 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 13 Dec 2016 23:58:59 +0200 Subject: [PATCH 163/504] add HttpError, add error handling in RouteConfig, remove try catch from claim/unclaim --- src/lib/HttpError.js | 14 ++++++++ src/lib/RouteConfig.js | 41 ++++++++++++++++-------- src/lib/controllers/DevicesController.js | 27 ++++++---------- src/lib/repository/DeviceRepository.js | 7 ++-- 4 files changed, 55 insertions(+), 34 deletions(-) create mode 100644 src/lib/HttpError.js diff --git a/src/lib/HttpError.js b/src/lib/HttpError.js new file mode 100644 index 00000000..3bf4ab49 --- /dev/null +++ b/src/lib/HttpError.js @@ -0,0 +1,14 @@ +// @flow + +class HttpError extends Error { + status: number; + + constructor(message: string, status?: number = 400) { + super(); + + this.message = message; + this.status = status; + } +} + +export default HttpError; diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 84761ca2..cb507557 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -9,6 +9,7 @@ import type { } from 'express'; import type { Settings } from '../types'; import type Controller from './controllers/Controller'; +import type HttpError from './HttpError'; import OAuthModel from './OAuthModel'; import OAuthServer from 'express-oauth-server'; @@ -94,18 +95,32 @@ export default ( access_token, ...body } = request.body; - const result = mappedFunction.call( - controllerContext, - ...values, - body, - ); - if (result.then) { - // eslint-disable-next-line no-shadow - result.then((result: Object): void => { + + try { + const result = mappedFunction.call( + controllerContext, + ...values, + body, + ); + + if (result.then) { + // eslint-disable-next-line no-shadow + result.then((result: Object): void => { + response.status(result.status).json(result.data); + }).catch((error: HttpError) => { + response.status(error.status).json({ + error: error.message, + ok: false, + }); + }); + } else { response.status(result.status).json(result.data); + } + } catch (error) { + response.status(error.status).json({ + error: error.message, + ok: false, }); - } else { - response.status(result.status).json(result.data); } }); }); @@ -119,13 +134,13 @@ export default ( error: string, request: $Request, response: $Response, - next: NextFunction - ): void => { + next: NextFunction, + ) => { response .status(400) .json({ error: error.code ? error.code : error, ok: false, }); - }) + }); }; diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index d2454deb..f225a51d 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -21,30 +21,21 @@ class DevicesController extends Controller { @httpVerb('post') @route('/v1/devices') - async claimDevice(postBody: { id: string}): Promise<*> { - try { - const deviceID = postBody.id; - const userID = this.user.id; + async claimDevice(postBody: { id: string }): Promise<*> { + const deviceID = postBody.id; + const userID = this.user.id; + await this._deviceRepository.claimDevice(deviceID, userID); - await this._deviceRepository.claimDevice(deviceID, userID); - - return this.ok({ ok: true }); - } catch (exception) { - return this.bad(exception.message); - } + return this.ok({ ok: true }); } @httpVerb('delete') @route('/v1/devices/:deviceID') async unclaimDevice(deviceID: string): Promise<*> { - try { - const userID = this.user.id; - await this._deviceRepository.unclaimDevice(deviceID, userID); + const userID = this.user.id; + await this._deviceRepository.unclaimDevice(deviceID, userID); - return this.ok({ ok: true }); - } catch (exception) { - return this.bad(exception.message); - } + return this.ok({ ok: true }); } @httpVerb('get') @@ -70,7 +61,7 @@ class DevicesController extends Controller { const device = await this._deviceRepository.getDetailsByID(deviceID); return this.ok(deviceToAPI(device)); } catch (exception) { - return this.bad(exception.message); + return this.bad(exception); } } diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index afc79795..c1405f4a 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -7,6 +7,7 @@ import type { Device, DeviceAttributes, Repository } from '../../types'; import Moniker from 'moniker'; import ursa from 'ursa'; import logger from '../logger'; +import HttpError from '../HttpError'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); @@ -32,10 +33,10 @@ class DeviceRepository { const deviceAttributes = await this._deviceAttributeRepository.getById(deviceID); if (!deviceAttributes) { - throw new Error('No device found'); + throw new HttpError('No device found', 404); } if (deviceAttributes.ownerID && deviceAttributes.ownerID !== userID) { - throw new Error('The device belongs to someone else.'); + throw new HttpError('The device belongs to someone else.'); } const attributesToSave = { @@ -53,7 +54,7 @@ class DeviceRepository { await this._deviceAttributeRepository.getById(deviceID, userID); if (!deviceAttributes) { - throw new Error('No device found'); + throw new Error('No device found', 404); } const attributesToSave = { From 86984ec0f801e6f87791dbea2165c88e9593d86c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 00:20:51 +0200 Subject: [PATCH 164/504] fix nits, clean some eslint/flow errors in RouteConfig --- src/lib/HttpError.js | 4 +--- src/lib/RouteConfig.js | 32 +++++++++++++------------- src/lib/repository/DeviceRepository.js | 2 +- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/lib/HttpError.js b/src/lib/HttpError.js index 3bf4ab49..91269c22 100644 --- a/src/lib/HttpError.js +++ b/src/lib/HttpError.js @@ -4,9 +4,7 @@ class HttpError extends Error { status: number; constructor(message: string, status?: number = 400) { - super(); - - this.message = message; + super(message); this.status = status; } } diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index cb507557..0ed78655 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -36,7 +36,7 @@ const injectUserMiddleware = (): Middleware => next(); }; const defaultMiddleware = - (request: $Request, response: $Response, next: NextFunction) => next(); + (request: $Request, response: $Response, next: NextFunction): mixed => next(); export default ( app: $Application, @@ -77,7 +77,7 @@ export default ( allowedUploads ? injectFilesMiddleware.fields(allowedUploads) : defaultMiddleware, - async(request: $Request, response: $Response) => { + (request: $Request, response: $Response) => { const argumentNames = (route.match(/:[\w]*/g) || []).map( (argumentName: string): string => argumentName.replace(':', ''), ); @@ -92,29 +92,30 @@ export default ( // Take access token out if it's posted. const { - access_token, + access_token, // eslint-disable-line no-unused-vars ...body } = request.body; try { - const result = mappedFunction.call( + const functionResult = mappedFunction.call( controllerContext, ...values, body, ); - if (result.then) { - // eslint-disable-next-line no-shadow - result.then((result: Object): void => { - response.status(result.status).json(result.data); - }).catch((error: HttpError) => { - response.status(error.status).json({ - error: error.message, - ok: false, + if (functionResult.then) { + functionResult + .then((result: Object) => { + response.status(result.status).json(result.data); + }) + .catch((error: HttpError) => { + response.status(error.status).json({ + error: error.message, + ok: false, + }); }); - }); } else { - response.status(result.status).json(result.data); + response.status(functionResult.status).json(functionResult.data); } } catch (error) { response.status(error.status).json({ @@ -126,7 +127,7 @@ export default ( }); }); - app.all('*', (request: $Request, response: $Response): void => { + app.all('*', (request: $Request, response: $Response) => { response.sendStatus(404); }); @@ -134,7 +135,6 @@ export default ( error: string, request: $Request, response: $Response, - next: NextFunction, ) => { response .status(400) diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index c1405f4a..c936a3da 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -54,7 +54,7 @@ class DeviceRepository { await this._deviceAttributeRepository.getById(deviceID, userID); if (!deviceAttributes) { - throw new Error('No device found', 404); + throw new HttpError('No device found', 404); } const attributesToSave = { From bc66a6f3a27966edf1e2c416df452ad80fa312ff Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 01:48:19 +0200 Subject: [PATCH 165/504] remove trycatch whereever is possible --- src/lib/controllers/DevicesController.js | 63 +++++++++---------- src/lib/controllers/ProvisioningController.js | 19 +++--- src/lib/controllers/UsersController.js | 39 +++++------- src/lib/controllers/WebhooksController.js | 47 +++++++------- src/lib/repository/DeviceRepository.js | 20 +++--- src/lib/repository/UserFileRepository.js | 9 +-- src/lib/repository/WebhookFileRepository.js | 3 +- test/UsersController.test.js | 3 +- test/WebhooksController.test.js | 8 +-- 9 files changed, 95 insertions(+), 116 deletions(-) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index f225a51d..b6cd9b66 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -5,6 +5,7 @@ import type { DeviceAPIType } from '../deviceToAPI'; import Controller from './Controller'; +import HttpError from '../HttpError'; import allowUpload from '../decorators/allowUpload'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; @@ -44,10 +45,10 @@ class DevicesController extends Controller { try { const userID = this.user.id; const devices = await this._deviceRepository.getAll(userID); - return this.ok(devices.map((device: Device): DeviceAPIType => - deviceToAPI(device))); - } catch (exception) { + deviceToAPI(device)), + ); + } catch (error) { // I wish we could return no devices found but meh :/ return this.ok([]); } @@ -56,13 +57,9 @@ class DevicesController extends Controller { @httpVerb('get') @route('/v1/devices/:deviceID') async getDevice(deviceID: string): Promise<*> { - try { - // TODO add userID checking - const device = await this._deviceRepository.getDetailsByID(deviceID); - return this.ok(deviceToAPI(device)); - } catch (exception) { - return this.bad(exception); - } + // TODO add userID checking + const device = await this._deviceRepository.getDetailsByID(deviceID); + return this.ok(deviceToAPI(device)); } @httpVerb('put') @@ -72,20 +69,20 @@ class DevicesController extends Controller { deviceID: string, postBody: { app_id?: string, name?: string, file_type?: 'binary' }, ): Promise<*> { - try { - const userID = this.user.id; - // 1 rename device - if (postBody.name) { - const updatedAttributes = await this._deviceRepository.renameDevice( - deviceID, - userID, - postBody.name, - ); + const userID = this.user.id; + // 1 rename device + if (postBody.name) { + const updatedAttributes = await this._deviceRepository.renameDevice( + deviceID, + userID, + postBody.name, + ); - return this.ok({ name: updatedAttributes.name, ok: true }); - } - // TODO not implemented yet - // 2 flash device with known app + return this.ok({ name: updatedAttributes.name, ok: true }); + } + // TODO not implemented yet + // 2 flash device with known app + try { if (postBody.app_id) { this._deviceRepository.flashKnownApp( deviceID, @@ -100,11 +97,11 @@ class DevicesController extends Controller { await this._deviceRepository.flashBinary(deviceID, this.request.files.file); return this.ok({ id: deviceID, status: 'Update started' }); } - - throw new Error('Did not update device'); - } catch (exception) { - return this.bad(exception.message); + } catch (error) { + throw new HttpError(error.message); } + + throw new HttpError('Did not update device'); } @httpVerb('post') @@ -124,15 +121,11 @@ class DevicesController extends Controller { // TODO add userID checking const device = await this._deviceRepository.getByID(deviceID); return this.ok(deviceToAPI(device, result)); - } catch (exception) { - if (exception.indexOf('Unknown Function') >= 0) { - return this.bad( - 'Function not found', - 404, - ); + } catch (error) { + if (error.indexOf('Unknown Function') >= 0) { + throw new HttpError('Function not found', 404); } - - return this.bad(exception); + throw new HttpError(error.message); } } } diff --git a/src/lib/controllers/ProvisioningController.js b/src/lib/controllers/ProvisioningController.js index 3721575e..532cbc0f 100644 --- a/src/lib/controllers/ProvisioningController.js +++ b/src/lib/controllers/ProvisioningController.js @@ -22,18 +22,13 @@ class ProvisioningController extends Controller { coreID: string, postBody: { publicKey: string }, ): Promise<*> { - try { - const device = await this._deviceRepository.provision( - coreID, - this.user.id, - postBody.publicKey, - ); - - return this.ok(deviceToAPI(device)); - } catch (exception) { - // I wish we could return no devices found but meh :/ - return this.ok([]); - } + const device = await this._deviceRepository.provision( + coreID, + this.user.id, + postBody.publicKey, + ); + + return this.ok(deviceToAPI(device)); } } diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index 870f2fb6..ab0c615f 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -7,6 +7,7 @@ import type { import basicAuthParser from 'basic-auth-parser'; import Controller from './Controller'; +import HttpError from '../HttpError'; import anonymous from '../decorators/anonymous'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; @@ -28,7 +29,7 @@ class UsersController extends Controller { this._userRepository.isUserNameInUse(userCredentials.username); if (isUserNameInUse) { - throw new Error('user with the username is already exist'); + throw new HttpError('user with the username is already exist'); } const newUser = await this._userRepository.createWithCredentials( @@ -44,36 +45,28 @@ class UsersController extends Controller { @route('/v1/access_tokens/:token') @anonymous() async deleteAccessToken(token: string): Promise<*> { - try { - const { username, password } = basicAuthParser( - this.request.get('authorization'), - ); - const user = await this._userRepository.validateLogin( - username, - password, - ); + const { username, password } = basicAuthParser( + this.request.get('authorization'), + ); + const user = await this._userRepository.validateLogin( + username, + password, + ); - this._userRepository.deleteAccessToken(user, token); + this._userRepository.deleteAccessToken(user, token); - return this.ok({ ok: true }); - } catch (error) { - return this.bad(error.message); - } + return this.ok({ ok: true }); } @httpVerb('get') @route('/v1/access_tokens') @anonymous() async getAccessTokens(): Promise<*> { - try { - const { username, password } = basicAuthParser( - this.request.get('authorization'), - ); - const user = await this._userRepository.validateLogin(username, password); - return this.ok(user.accessTokens); - } catch (error) { - return this.bad(error.message); - } + const { username, password } = basicAuthParser( + this.request.get('authorization'), + ); + const user = await this._userRepository.validateLogin(username, password); + return this.ok(user.accessTokens); } } diff --git a/src/lib/controllers/WebhooksController.js b/src/lib/controllers/WebhooksController.js index d85dec9d..82a9548e 100644 --- a/src/lib/controllers/WebhooksController.js +++ b/src/lib/controllers/WebhooksController.js @@ -8,6 +8,7 @@ import type { } from '../../types'; import Controller from './Controller'; +import HttpError from '../HttpError'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; @@ -15,18 +16,18 @@ const REQUEST_TYPES: Array = [ 'DELETE', 'GET', 'POST', 'PUT', ]; -const validateWebhookMutator = (webhookMutator: WebhookMutator): ?Error => { +const validateWebhookMutator = (webhookMutator: WebhookMutator): ?HttpError => { if (!webhookMutator.event) { - return new Error('no event name provided'); + return new HttpError('no event name provided'); } if (!webhookMutator.url) { - return new Error('no url provided'); + return new HttpError('no url provided'); } if (!webhookMutator.requestType) { - return new Error('no requestType provided'); + return new HttpError('no requestType provided'); } if (!REQUEST_TYPES.includes(webhookMutator.requestType)) { - return new Error('wrong requestType'); + return new HttpError('wrong requestType'); } return null; @@ -56,27 +57,23 @@ class WebhooksController extends Controller { @httpVerb('post') @route('/v1/webhooks') async create(model: WebhookMutator): Promise<*> { - try { - const validateError = validateWebhookMutator(model); - if (validateError) { - throw validateError; - } - - const newWebhook = await this._webhookRepository.create({ - ...model, - created_at: new Date(), - id: '', - }); - return this.ok({ - created_at: newWebhook.created_at, - event: newWebhook.event, - id: newWebhook.id, - ok: true, - url: newWebhook.url, - }); - } catch (error) { - return this.bad(error.message); + const validateError = validateWebhookMutator(model); + if (validateError) { + throw validateError; } + + const newWebhook = await this._webhookRepository.create({ + ...model, + created_at: new Date(), + id: '', + }); + return this.ok({ + created_at: newWebhook.created_at, + event: newWebhook.event, + id: newWebhook.id, + ok: true, + url: newWebhook.url, + }); } @httpVerb('delete') diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index c936a3da..20a6591d 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -90,7 +90,7 @@ class DeviceRepository { getDetailsByID = async (deviceID: string): Promise => { const core = this._deviceServer.getCore(deviceID); if (!core) { - throw new Error('Could not get device for ID'); + throw new HttpError('Could not get device for ID', 404); } return Promise.all([ @@ -143,7 +143,7 @@ class DeviceRepository { ): Promise<*> => { const core = this._deviceServer.getCore(deviceID); if (!core) { - return null; + throw new HttpError('Could not get device for ID', 404); } const result = await core.onApiMessage( deviceID, @@ -163,7 +163,7 @@ class DeviceRepository { ) => { const core = this._deviceServer.getCore(deviceID); if (!core) { - return null; + throw new HttpError('Could not get device for ID', 404); } const result = await core.onApiMessage( @@ -185,7 +185,7 @@ class DeviceRepository { // TODO not implemented yet const core = this._deviceServer.getCore(deviceID); if (!core) { - return null; + throw new HttpError('Could not get device for ID', 404); } const result = await core.onApiMessage( @@ -206,17 +206,17 @@ class DeviceRepository { publicKey: string, ): Promise<*> => { if (!deviceID) { - throw new Error('No deviceID provided'); + throw new HttpError('No deviceID provided'); } try { const createdKey = ursa.createPublicKey(publicKey); if (!publicKey || !ursa.isPublicKey(createdKey)) { - throw new Error('No key provided'); + throw new HttpError('No key provided'); } - } catch (exception) { - logger.error('error while parsing publicKey', exception); - throw new Error(`Key error ${exception}`); + } catch (error) { + logger.error('error while parsing publicKey', error); + throw new HttpError(`Key error ${error}`); } this._deviceKeyRepository.update(deviceID, publicKey); const existingAttributes = this._deviceAttributeRepository.getById( @@ -243,7 +243,7 @@ class DeviceRepository { const attributes = await this._deviceAttributeRepository.getById(deviceID, userID); if (!attributes) { - throw new Error('No device found'); + throw new HttpError('No device found', 404); } const attributesToSave = { diff --git a/src/lib/repository/UserFileRepository.js b/src/lib/repository/UserFileRepository.js index 0ac4be0f..2cc42fe1 100644 --- a/src/lib/repository/UserFileRepository.js +++ b/src/lib/repository/UserFileRepository.js @@ -4,6 +4,7 @@ import type { TokenObject, User, UserCredentials } from '../../types'; import { JSONFileManager, uuid } from 'spark-protocol'; import PasswordHasher from '../PasswordHasher'; +import HttpError from '../HttpError'; class UserFileRepository { _fileManager: JSONFileManager; @@ -35,11 +36,11 @@ class UserFileRepository { }; create = (user: User): User => { - throw 'Not implemented'; + throw new HttpError('Not implemented'); }; update = (user: User): User => { - throw 'Not implemented'; + throw new HttpError('Not implemented'); }; getAll = (): Array => @@ -76,7 +77,7 @@ class UserFileRepository { ), ); - deleteAccessToken = (user: User, token: string): void => { + deleteAccessToken = (user: User, token: string) => { const userToSave = { ...user, accessTokens: user.accessTokens.filter( @@ -97,7 +98,7 @@ class UserFileRepository { user.username === username, ); - saveAccessToken = (userId: string, tokenObject: TokenObject): void => { + saveAccessToken = (userId: string, tokenObject: TokenObject) => { const user = this.getById(userId); const userToSave = { ...user, diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 1507f456..67ba6110 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -3,6 +3,7 @@ import type { Webhook } from '../../types'; import { JSONFileManager, uuid } from 'spark-protocol'; +import HttpError from '../HttpError'; class WebhookFileRepository { _fileManager: JSONFileManager; @@ -32,7 +33,7 @@ class WebhookFileRepository { this._fileManager.getFile(`${id}.json`); update = (model: Webhook): Webhook => { - throw 'Not implemented'; + throw new HttpError('Not implemented'); }; } diff --git a/test/UsersController.test.js b/test/UsersController.test.js index c2c36a09..e3dd865d 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -30,9 +30,8 @@ test.serial('should throw an error if username already in use', async t => { const response = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); - t.is(response.status, 400); - t.is(response.body.message, 'user with the username is already exist'); + t.is(response.body.error, 'user with the username is already exist'); }); test.serial('should login the user', async t => { diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index 43168007..70c41f5c 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -68,7 +68,7 @@ test('should throw an error if event isn\'t provided', async t => { }); t.is(response.status, 400); - t.is(response.body.message, 'no event name provided'); + t.is(response.body.error, 'no event name provided'); }); test('should throw an error if url isn\'t provided', async t => { @@ -81,7 +81,7 @@ test('should throw an error if url isn\'t provided', async t => { }); t.is(response.status, 400); - t.is(response.body.message, 'no url provided'); + t.is(response.body.error, 'no url provided'); }); test('should throw an error if requestType isn\'t provided', async t => { @@ -94,7 +94,7 @@ test('should throw an error if requestType isn\'t provided', async t => { }); t.is(response.status, 400); - t.is(response.body.message, 'no requestType provided'); + t.is(response.body.error, 'no requestType provided'); }); test('should throw an error if requestType is wrong', async t => { @@ -108,7 +108,7 @@ test('should throw an error if requestType is wrong', async t => { }); t.is(response.status, 400); - t.is(response.body.message, 'wrong requestType'); + t.is(response.body.error, 'wrong requestType'); }); test.serial('should return all webhooks', async t => { From 7a7e2598ee3e8c7e14767771adef3d5def00968c Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 14 Dec 2016 07:12:27 -0800 Subject: [PATCH 166/504] Fixing flow errors. --- src/lib/repository/UserFileRepository.js | 40 +++++++++++++-------- src/lib/repository/WebhookFileRepository.js | 12 +++---- src/types.js | 17 ++++----- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/lib/repository/UserFileRepository.js b/src/lib/repository/UserFileRepository.js index 0ac4be0f..e240701c 100644 --- a/src/lib/repository/UserFileRepository.js +++ b/src/lib/repository/UserFileRepository.js @@ -34,26 +34,28 @@ class UserFileRepository { return modelToSave; }; - create = (user: User): User => { + create = (user: User): Promise => { throw 'Not implemented'; }; - update = (user: User): User => { + update = (user: User): Promise => { throw 'Not implemented'; }; - getAll = (): Array => + getAll = (): Promise> => this._fileManager.getAllData(); - getById = (id: string): User => + getById = (id: string): Promise => this._fileManager.getFile(`${id}.json`); - getByUsername = (username: string): ?User => - this.getAll().find((user: User): boolean => user.username === username); + getByUsername = async (username: string): Promise => + (await this.getAll()).find( + (user: User): boolean => user.username === username, + ); validateLogin = async (username: string, password: string): Promise => { try { - const user = this.getByUsername(username); + const user = await this.getByUsername(username); if (!user) { throw new Error('User doesn\'t exist'); } @@ -69,8 +71,8 @@ class UserFileRepository { } }; - getByAccessToken = (accessToken: string): ?User => - this.getAll().find((user: User): boolean => + getByAccessToken = async (accessToken: string): Promise => + (await this.getAll()).find((user: User): boolean => user.accessTokens.some((tokenObject: TokenObject): boolean => tokenObject.accessToken === accessToken, ), @@ -88,23 +90,31 @@ class UserFileRepository { this._fileManager.writeFile(`${user.id}.json`, userToSave); }; - deleteById = (id: string): void => + deleteById = (id: string): Promise => this._fileManager.deleteFile(`${id}.json`); - isUserNameInUse = (username: string): boolean => - this.getAll().some((user: User): boolean => + isUserNameInUse = async (username: string): Promise => + (await this.getAll()).some((user: User): boolean => user.username === username, ); - saveAccessToken = (userId: string, tokenObject: TokenObject): void => { - const user = this.getById(userId); + saveAccessToken = async ( + userID: string, + tokenObject: TokenObject, + ): Promise<*> => { + const user = await this.getById(userID); + + if (!user) { + throw new HttpError('Could not find user for user ID'); + } + const userToSave = { ...user, accessTokens: [...user.accessTokens, tokenObject], }; - this._fileManager.writeFile(`${userId}.json`, userToSave); + this._fileManager.writeFile(`${userID}.json`, userToSave); } } diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 1507f456..697541ef 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -11,7 +11,7 @@ class WebhookFileRepository { this._fileManager = new JSONFileManager(path); } - create = (model: Webhook): Webhook => { + create = (model: Webhook): Promise => { const modelToSave = { ...model, created_at: new Date(), @@ -19,19 +19,19 @@ class WebhookFileRepository { }; this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); - return modelToSave; + return Promise.resolve(modelToSave); }; - deleteById = (id: string): void => + deleteById = (id: string): Promise => this._fileManager.deleteFile(`${id}.json`); - getAll = (): Array => + getAll = (): Promise> => this._fileManager.getAllData(); - getById = (id: string): Webhook => + getById = (id: string): Promise => this._fileManager.getFile(`${id}.json`); - update = (model: Webhook): Webhook => { + update = (model: Webhook): Promise => { throw 'Not implemented'; }; } diff --git a/src/types.js b/src/types.js index f107b872..e0753c38 100644 --- a/src/types.js +++ b/src/types.js @@ -38,6 +38,7 @@ export type DeviceAttributes = { deviceID: string, ip: string, name: string, + ownerID: ?string, particleProductId: number, productFirmwareVersion: string, registrar: string, @@ -80,19 +81,19 @@ export type Device = DeviceAttributes & { }; export type Repository = { - create: (model: TModel) => TModel, - deleteById: (id: string) => void, - getAll: () => Array, - getById: (id: string) => TModel, - update: (model: TModel) => TModel, + create: (model: TModel) => Promise, + deleteById: (id: string) => Promise, + getAll: () => Promise>, + getById: (id: string) => Promise, + update: (model: TModel) => Promise, }; export type UserRepository = Repository & { createWithCredentials(credentials: UserCredentials): Promise, deleteAccessToken(user: User, accessToken: string): void, - getByAccessToken(accessToken: string): ?User, - getByUsername(username: string): ?User, - isUserNameInUse(username: string): boolean, + getByAccessToken(accessToken: string): Promise, + getByUsername(username: string): Promise, + isUserNameInUse(username: string): Promise, saveAccessToken(userId: string, tokenObject: TokenObject): void, validateLogin(username: string, password: string): Promise, }; From ce0105b7ab0ec49969357d9341907517c2d34069 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 14 Dec 2016 07:53:50 -0800 Subject: [PATCH 167/504] Fixing flow errors --- src/lib/OAuthModel.js | 4 ++-- src/lib/controllers/DevicesController.js | 5 ++++- src/lib/repository/DeviceRepository.js | 13 ++++++++++--- src/lib/repository/UserFileRepository.js | 2 +- src/lib/repository/WebhookFileRepository.js | 8 ++++---- src/types.js | 6 +++--- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/lib/OAuthModel.js b/src/lib/OAuthModel.js index 01457242..18b9b69e 100644 --- a/src/lib/OAuthModel.js +++ b/src/lib/OAuthModel.js @@ -16,8 +16,8 @@ class OauthModel { this._userRepository = userRepository; } - getAccessToken = (bearerToken: string): ?Object => { - const user = this._userRepository.getByAccessToken(bearerToken); + getAccessToken = async (bearerToken: string): ?Object => { + const user = await this._userRepository.getByAccessToken(bearerToken); if (!user) { return null; } diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index b6cd9b66..21dba98f 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -119,7 +119,10 @@ class DevicesController extends Controller { ); // TODO add userID checking - const device = await this._deviceRepository.getByID(deviceID); + const device = await this._deviceRepository.getByID( + deviceID, + this.user.id, + ); return this.ok(deviceToAPI(device, result)); } catch (error) { if (error.indexOf('Unknown Function') >= 0) { diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index 20a6591d..d4bec12a 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -65,7 +65,14 @@ class DeviceRepository { }; getByID = async (deviceID: string, userID: string): Promise => { - const attributes = await this._deviceAttributeRepository.getById(deviceID, userID); + const attributes = await this._deviceAttributeRepository.getById( + deviceID, + userID, + ); + if (!attributes) { + throw new HttpError('No device found', 404); + } + const core = this._deviceServer.getCore(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ @@ -218,8 +225,8 @@ class DeviceRepository { logger.error('error while parsing publicKey', error); throw new HttpError(`Key error ${error}`); } - this._deviceKeyRepository.update(deviceID, publicKey); - const existingAttributes = this._deviceAttributeRepository.getById( + await this._deviceKeyRepository.update(deviceID, publicKey); + const existingAttributes = await this._deviceAttributeRepository.getById( deviceID, ); const attributes = { diff --git a/src/lib/repository/UserFileRepository.js b/src/lib/repository/UserFileRepository.js index 5ac5fac7..f4a87253 100644 --- a/src/lib/repository/UserFileRepository.js +++ b/src/lib/repository/UserFileRepository.js @@ -79,7 +79,7 @@ class UserFileRepository { ), ); - deleteAccessToken = (user: User, token: string) => { + deleteAccessToken = async (user: User, token: string): Promise<*> => { const userToSave = { ...user, accessTokens: user.accessTokens.filter( diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index 359191b4..dcb33573 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -23,14 +23,14 @@ class WebhookFileRepository { return Promise.resolve(modelToSave); }; - deleteById = (id: string): Promise => + deleteById = async (id: string): Promise => this._fileManager.deleteFile(`${id}.json`); getAll = (): Promise> => - this._fileManager.getAllData(); + Promise.resolve(this._fileManager.getAllData()); - getById = (id: string): Promise => - this._fileManager.getFile(`${id}.json`); + getById = (id: string): Promise => + Promise.resolve(this._fileManager.getFile(`${id}.json`)); update = (model: Webhook): Promise => { throw new HttpError('Not implemented'); diff --git a/src/types.js b/src/types.js index 97d15792..1abda1d2 100644 --- a/src/types.js +++ b/src/types.js @@ -40,7 +40,7 @@ export type DeviceAttributes = { name: string, ownerID: ?string, particleProductId: number, - productFirmwareVersion: string, + productFirmwareVersion: number, registrar: string, timestamp: Date, }; @@ -90,11 +90,11 @@ export type Repository = { export type UserRepository = Repository & { createWithCredentials(credentials: UserCredentials): Promise, - deleteAccessToken(user: User, accessToken: string): void, + deleteAccessToken(user: User, accessToken: string): Promise, getByAccessToken(accessToken: string): Promise, getByUsername(username: string): Promise, isUserNameInUse(username: string): Promise, - saveAccessToken(userId: string, tokenObject: TokenObject): void, + saveAccessToken(userId: string, tokenObject: TokenObject): Promise, validateLogin(username: string, password: string): Promise, }; From 8ebd9c0139e2ea31a81294973c11f89279175434 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 14:33:59 +0200 Subject: [PATCH 168/504] add userID checking for getDevice --- src/lib/controllers/DevicesController.js | 4 ++-- src/lib/repository/DeviceRepository.js | 27 ++++++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index 21dba98f..cd50d706 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -57,8 +57,8 @@ class DevicesController extends Controller { @httpVerb('get') @route('/v1/devices/:deviceID') async getDevice(deviceID: string): Promise<*> { - // TODO add userID checking - const device = await this._deviceRepository.getDetailsByID(deviceID); + const userID = this.user.id; + const device = await this._deviceRepository.getDetailsByID(deviceID, userID); return this.ok(deviceToAPI(device)); } diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index d4bec12a..801a8662 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -94,26 +94,31 @@ class DeviceRepository { }; }; - getDetailsByID = async (deviceID: string): Promise => { + getDetailsByID = async (deviceID: string, userID: string): Promise => { const core = this._deviceServer.getCore(deviceID); if (!core) { - throw new HttpError('Could not get device for ID', 404); + throw new HttpError('No device found', 404); } return Promise.all([ - this._deviceAttributeRepository.getById(deviceID), + this._deviceAttributeRepository.getById(deviceID, userID), core.onApiMessage( deviceID, { cmd: 'Describe' }, ), - ]).then(([attributes, description]): Device => ({ - ...attributes, - connected: true, - functions: description.f, - lastFlashedAppName: null, - lastHeard: new Date(), - variables: description.v, - })); + ]).then(([attributes, description]): Device => { + if (!attributes) { + throw new HttpError('No device found', 404); + } + return ({ + ...attributes, + connected: true, + functions: description.f, + lastFlashedAppName: null, + lastHeard: new Date(), + variables: description.v, + }); + }); }; getAll = async (userID: string): Promise> => { From 1a317d0da32a48f506f3369821f2c8f5b28348cd Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 15:12:30 +0200 Subject: [PATCH 169/504] add userID check for callFunction() --- src/lib/controllers/DevicesController.js | 5 +++-- src/lib/repository/DeviceRepository.js | 7 +++++++ src/types.js | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index cd50d706..51e66a8a 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -112,20 +112,21 @@ class DevicesController extends Controller { postBody: Object, ): Promise<*> { try { + const userID = this.user.id; const result = await this._deviceRepository.callFunction( deviceID, + userID, functionName, postBody, ); - // TODO add userID checking const device = await this._deviceRepository.getByID( deviceID, this.user.id, ); return this.ok(deviceToAPI(device, result)); } catch (error) { - if (error.indexOf('Unknown Function') >= 0) { + if (error.indexOf && error.indexOf('Unknown Function') >= 0) { throw new HttpError('Function not found', 404); } throw new HttpError(error.message); diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index 801a8662..cb11af7d 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -150,9 +150,16 @@ class DeviceRepository { callFunction= async ( deviceID: string, + userID: string, functionName: string, functionArguments: Object, ): Promise<*> => { + const doesCoreBelongsToUser = + await this._deviceAttributeRepository.getById(deviceID, userID); + if (!doesCoreBelongsToUser) { + throw new HttpError('No device found', 404); + } + const core = this._deviceServer.getCore(deviceID); if (!core) { throw new HttpError('Could not get device for ID', 404); diff --git a/src/types.js b/src/types.js index 1abda1d2..3089c4f0 100644 --- a/src/types.js +++ b/src/types.js @@ -123,6 +123,7 @@ export type Settings = { export type DeviceRepository = { callFunction( deviceID: string, + userID: string, functionName: string, functionArguments: Object, ): Promise<*>, @@ -131,7 +132,7 @@ export type DeviceRepository = { flashKnownApp(deviceID: string, app: string): Promise<*>, getAll(userID: string): Promise>, getByID(deviceID: string, userID: string): Promise, - getDetailsByID(deviceID: string): Promise<*>, + getDetailsByID(deviceID: string, userID: string): Promise<*>, provision(deviceID: string, userID: string, publicKey: string): Promise<*>, renameDevice(deviceID: string, userID: string, name: string): Promise, unclaimDevice(deviceID: string, userID: string): Promise, From f281d3c333c702b367c1e148e582bc35af795dd2 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 15:39:57 +0200 Subject: [PATCH 170/504] add compileSources stub --- src/lib/controllers/DevicesController.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index 51e66a8a..c7bb4f6e 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -30,6 +30,12 @@ class DevicesController extends Controller { return this.ok({ ok: true }); } + @httpVerb('post') + @route('/v1/binaries') + compileSources() { + throw new HttpError('not supported in the current server version'); + } + @httpVerb('delete') @route('/v1/devices/:deviceID') async unclaimDevice(deviceID: string): Promise<*> { From 50c2643c20411d255ee3998c1634bdd5c59f72cd Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 18:19:34 +0200 Subject: [PATCH 171/504] fix tests --- src/lib/controllers/UsersController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/controllers/UsersController.js b/src/lib/controllers/UsersController.js index ab0c615f..10506954 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/lib/controllers/UsersController.js @@ -26,7 +26,7 @@ class UsersController extends Controller { async createUser(userCredentials: UserCredentials): Promise<*> { try { const isUserNameInUse = - this._userRepository.isUserNameInUse(userCredentials.username); + await this._userRepository.isUserNameInUse(userCredentials.username); if (isUserNameInUse) { throw new HttpError('user with the username is already exist'); From b1ad2f86b8ec6e0025862d334c2521059fe1d3b9 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 14 Dec 2016 08:40:02 -0800 Subject: [PATCH 172/504] Removed superset -- added supertest --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d53f5dd..c15a8244 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "flow-bin": "^0.36.0", "nodemon": "^1.11.0", "rimraf": "^2.5.4", - "superset": "^1.0.1", + "supertest": "^2.0.1", "supertest-as-promised": "^4.0.2" } } From f20cc700c04ad485d7ee16c1f30264e5f7b3522a Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 19:40:41 +0200 Subject: [PATCH 173/504] fix nits and some formatting --- src/lib/controllers/DevicesController.js | 22 ++++++-------- src/lib/repository/DeviceRepository.js | 33 ++++++++++----------- src/lib/repository/WebhookFileRepository.js | 12 ++++---- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index c7bb4f6e..60ee7a5e 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -24,8 +24,7 @@ class DevicesController extends Controller { @route('/v1/devices') async claimDevice(postBody: { id: string }): Promise<*> { const deviceID = postBody.id; - const userID = this.user.id; - await this._deviceRepository.claimDevice(deviceID, userID); + await this._deviceRepository.claimDevice(deviceID, this.user.id); return this.ok({ ok: true }); } @@ -39,9 +38,7 @@ class DevicesController extends Controller { @httpVerb('delete') @route('/v1/devices/:deviceID') async unclaimDevice(deviceID: string): Promise<*> { - const userID = this.user.id; - await this._deviceRepository.unclaimDevice(deviceID, userID); - + await this._deviceRepository.unclaimDevice(deviceID, this.user.id); return this.ok({ ok: true }); } @@ -49,8 +46,7 @@ class DevicesController extends Controller { @route('/v1/devices') async getDevices(): Promise<*> { try { - const userID = this.user.id; - const devices = await this._deviceRepository.getAll(userID); + const devices = await this._deviceRepository.getAll(this.user.id); return this.ok(devices.map((device: Device): DeviceAPIType => deviceToAPI(device)), ); @@ -63,8 +59,10 @@ class DevicesController extends Controller { @httpVerb('get') @route('/v1/devices/:deviceID') async getDevice(deviceID: string): Promise<*> { - const userID = this.user.id; - const device = await this._deviceRepository.getDetailsByID(deviceID, userID); + const device = await this._deviceRepository.getDetailsByID( + deviceID, + this.user.id, + ); return this.ok(deviceToAPI(device)); } @@ -75,12 +73,11 @@ class DevicesController extends Controller { deviceID: string, postBody: { app_id?: string, name?: string, file_type?: 'binary' }, ): Promise<*> { - const userID = this.user.id; // 1 rename device if (postBody.name) { const updatedAttributes = await this._deviceRepository.renameDevice( deviceID, - userID, + this.user.id, postBody.name, ); @@ -118,10 +115,9 @@ class DevicesController extends Controller { postBody: Object, ): Promise<*> { try { - const userID = this.user.id; const result = await this._deviceRepository.callFunction( deviceID, - userID, + this.user.id, functionName, postBody, ); diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index cb11af7d..d5700b3a 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -100,24 +100,25 @@ class DeviceRepository { throw new HttpError('No device found', 404); } - return Promise.all([ + const [ attributes, description ] = await Promise.all([ this._deviceAttributeRepository.getById(deviceID, userID), core.onApiMessage( deviceID, { cmd: 'Describe' }, ), - ]).then(([attributes, description]): Device => { - if (!attributes) { - throw new HttpError('No device found', 404); - } - return ({ - ...attributes, - connected: true, - functions: description.f, - lastFlashedAppName: null, - lastHeard: new Date(), - variables: description.v, - }); + ]); + + if (!attributes) { + throw new HttpError('No device found', 404); + } + + return ({ + ...attributes, + connected: true, + functions: description.f, + lastFlashedAppName: null, + lastHeard: new Date(), + variables: description.v, }); }; @@ -148,15 +149,13 @@ class DeviceRepository { return Promise.all(devicePromises); }; - callFunction= async ( + callFunction = async ( deviceID: string, userID: string, functionName: string, functionArguments: Object, ): Promise<*> => { - const doesCoreBelongsToUser = - await this._deviceAttributeRepository.getById(deviceID, userID); - if (!doesCoreBelongsToUser) { + if (!this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { throw new HttpError('No device found', 404); } diff --git a/src/lib/repository/WebhookFileRepository.js b/src/lib/repository/WebhookFileRepository.js index dcb33573..e8bbf1fd 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/lib/repository/WebhookFileRepository.js @@ -12,7 +12,7 @@ class WebhookFileRepository { this._fileManager = new JSONFileManager(path); } - create = (model: Webhook): Promise => { + create = async (model: Webhook): Promise => { const modelToSave = { ...model, created_at: new Date(), @@ -20,17 +20,17 @@ class WebhookFileRepository { }; this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); - return Promise.resolve(modelToSave); + return modelToSave; }; deleteById = async (id: string): Promise => this._fileManager.deleteFile(`${id}.json`); - getAll = (): Promise> => - Promise.resolve(this._fileManager.getAllData()); + getAll = async (): Promise> => + this._fileManager.getAllData(); - getById = (id: string): Promise => - Promise.resolve(this._fileManager.getFile(`${id}.json`)); + getById = async (id: string): Promise => + this._fileManager.getFile(`${id}.json`); update = (model: Webhook): Promise => { throw new HttpError('Not implemented'); From e08e4b6e7acc66fce957a823a7a23df2e719c394 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 20:56:32 +0200 Subject: [PATCH 174/504] add precommit hook --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 9d53f5dd..c6e7cafd 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "test": "ava", "test:watch": "ava --watch" }, + "pre-commit": "test", "ava": { "verbose": true, "babel": "inherit", @@ -84,6 +85,7 @@ "eslint-plugin-sorting": "^0.3.0", "flow-bin": "^0.36.0", "nodemon": "^1.11.0", + "pre-commit": "^1.2.2", "rimraf": "^2.5.4", "superset": "^1.0.1", "supertest-as-promised": "^4.0.2" From e30e87f3e5a5708a49b382ca99db3db6870f13c5 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 20:59:20 +0200 Subject: [PATCH 175/504] add __test_data__ to git ignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7b1be020..7236f6da 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ node_modules core_keys users webhooks -test/__test_data__/* +__test_data__ *.der *.pem *.pub.pem From 5b38257dbaf1a4837f734e38a7f020a3068dfbfb Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 14 Dec 2016 21:31:58 +0200 Subject: [PATCH 176/504] init devicesController test file, add compile source test --- src/lib/RouteConfig.js | 4 +-- test/DevicesController.test.js | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/DevicesController.test.js diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 0ed78655..ab1f05b9 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -109,7 +109,7 @@ export default ( response.status(result.status).json(result.data); }) .catch((error: HttpError) => { - response.status(error.status).json({ + response.status(error.status || 400).json({ error: error.message, ok: false, }); @@ -118,7 +118,7 @@ export default ( response.status(functionResult.status).json(functionResult.data); } } catch (error) { - response.status(error.status).json({ + response.status(error.status || 400).json({ error: error.message, ok: false, }); diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js new file mode 100644 index 00000000..738b95fe --- /dev/null +++ b/test/DevicesController.test.js @@ -0,0 +1,47 @@ +import test from 'ava'; +import request from 'supertest-as-promised'; +import ouathClients from '../src/oauthClients.json'; +import app from './testApp'; +import settings from './settings'; + +const USER_CREDENTIALS = { + password: 'password', + username: 'deviceTestUser@test.com', +}; + +let testUser; +let userToken; + +test.before(async () => { + const userResponse = await request(app) + .post('/v1/users') + .send(USER_CREDENTIALS); + + testUser = userResponse.body; + + const tokenResponse = await request(app) + .post('/oauth/token') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + client_id: ouathClients[0].clientId, + client_secret: ouathClients[0].clientSecret, + grant_type: 'password', + password: USER_CREDENTIALS.password, + username: USER_CREDENTIALS.username, + }); + + userToken = tokenResponse.body.access_token; +}); + +test.serial('should throw an error for compile source code endpoint', async t => { + const response = await request(app) + .post('/v1/binaries') + .query({ access_token: userToken }); + + t.is(response.status, 400); + t.is(response.body.error, 'not supported in the current server version'); +}); + +test.after.always(() => { + settings.usersRepository.deleteById(testUser.id); +}); From 2bd4f8f0993df913dcfaab47d619b90ac07c3e1d Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 15 Dec 2016 02:09:47 +0200 Subject: [PATCH 177/504] init deviceServer/sparkServer mocks, write some deviceController tests --- src/lib/RouteConfig.js | 4 +- test/DeviceServerMock.js | 11 ++++ test/DevicesController.test.js | 94 +++++++++++++++++++++++++++++++++- test/SparkCoreMock.js | 9 ++++ test/settings.js | 7 +++ test/testApp.js | 8 +-- 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 test/DeviceServerMock.js create mode 100644 test/SparkCoreMock.js diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index ab1f05b9..0ed78655 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -109,7 +109,7 @@ export default ( response.status(result.status).json(result.data); }) .catch((error: HttpError) => { - response.status(error.status || 400).json({ + response.status(error.status).json({ error: error.message, ok: false, }); @@ -118,7 +118,7 @@ export default ( response.status(functionResult.status).json(functionResult.data); } } catch (error) { - response.status(error.status || 400).json({ + response.status(error.status).json({ error: error.message, ok: false, }); diff --git a/test/DeviceServerMock.js b/test/DeviceServerMock.js new file mode 100644 index 00000000..7a915f5f --- /dev/null +++ b/test/DeviceServerMock.js @@ -0,0 +1,11 @@ +// @flow + +import SparkCoreMock from './SparkCoreMock'; + +class DeviceServerMock { + getCore(): SparkCoreMock { + return new SparkCoreMock(); + } +} + +export default DeviceServerMock; diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 738b95fe..2bfa2dba 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -9,8 +9,18 @@ const USER_CREDENTIALS = { username: 'deviceTestUser@test.com', }; +const DEVICE_ID = '350023001951353337343732'; +const TEST_PUBLIC_KEY = + '-----BEGIN PUBLIC KEY-----\n' + + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsxJFqlUOxK5bsEfTtBCe9sXBa' + + '43q9QoSPFXEG5qY/+udOpf2SKacgfUVdUbK4WOkLou7FQ+DffpwztBk5fWM9qfzF' + + 'EQRVMS8xwS4JqqD7slXwuPWFpS9SGy9kLNy/pl1dtGm556wVX431Dg7UBKiXuNGR' + + '7E8d2hfgeyiTtsWfUQIDAQAB\n' + + '-----END PUBLIC KEY-----\n'; + let testUser; let userToken; +let deviceToApiAttributes; test.before(async () => { const userResponse = await request(app) @@ -33,7 +43,20 @@ test.before(async () => { userToken = tokenResponse.body.access_token; }); -test.serial('should throw an error for compile source code endpoint', async t => { +// TODO come up better test name +test.serial('provision test', async t => { + const response = await request(app) + .post(`/v1/provisioning/${DEVICE_ID}`) + .query({ access_token: userToken }) + .send({ publicKey: TEST_PUBLIC_KEY }); + + deviceToApiAttributes = response.body; + t.is(response.status, 200); + t.is(response.body.id, DEVICE_ID); +}); +// TODO write test for checking the error if publicKey is in wrong format. + +test('should throw an error for compile source code endpoint', async t => { const response = await request(app) .post('/v1/binaries') .query({ access_token: userToken }); @@ -42,6 +65,75 @@ test.serial('should throw an error for compile source code endpoint', async t => t.is(response.body.error, 'not supported in the current server version'); }); +test.serial('should return device details', async t => { + const response = await request(app) + .get(`/v1/devices/${DEVICE_ID}`) + .query({ access_token: userToken }); + + t.is(response.status, 200); + t.is(response.body.id, deviceToApiAttributes.id); + t.is(response.body.name, deviceToApiAttributes.name); + t.is(response.body.ownerID, deviceToApiAttributes.ownerID); +}); + +test.serial('should throw an error if device not found', async t => { + const response = await request(app) + .get(`/v1/devices/${DEVICE_ID}123`) + .query({ access_token: userToken }); + + t.is(response.status, 404); + t.is(response.body.error, 'No device found'); +}); + +test.serial('should return all devices', async t => { + const response = await request(app) + .get('/v1/devices/') + .query({ access_token: userToken }); + + const devices = response.body; + + t.is(response.status, 200); + t.truthy(Array.isArray(devices) && devices.length > 0); +}); + +test.serial('should unclaim device', async t => { + const unclaimResponse = await request(app) + .delete(`/v1/devices/${DEVICE_ID}`) + .query({ access_token: userToken }); + + t.is(unclaimResponse.status, 200); + t.is(unclaimResponse.body.ok, true); + + const getDeviceResponse = await request(app) + .get(`/v1/devices/${DEVICE_ID}`) + .query({ access_token: userToken }); + + t.is(getDeviceResponse.status, 404); +}); + +test.serial('should claim device', async t => { + const claimDeviceResponse = await request(app) + .post('/v1/devices') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + id: DEVICE_ID, + }); + + t.is(claimDeviceResponse.status, 200); + t.is(claimDeviceResponse.body.ok, true); + + const getDeviceResponse = await request(app) + .get(`/v1/devices/${DEVICE_ID}`) + .query({ access_token: userToken }); + + t.is(getDeviceResponse.status, 200); +}); +// TODO write test for checking the error if device belongs to somebody else +// TODO write tests for updateDevice & callFunction + test.after.always(() => { settings.usersRepository.deleteById(testUser.id); + settings.deviceAttributeRepository.deleteById(DEVICE_ID); + settings.deviceKeyFileRepository.delete(DEVICE_ID); }); diff --git a/test/SparkCoreMock.js b/test/SparkCoreMock.js new file mode 100644 index 00000000..6abf6201 --- /dev/null +++ b/test/SparkCoreMock.js @@ -0,0 +1,9 @@ +// @flow + +class SparkCoreMock { + onApiMessage() { + return true; + } +} + +export default SparkCoreMock; diff --git a/test/settings.js b/test/settings.js index 1d1cbd56..e67bb7d8 100644 --- a/test/settings.js +++ b/test/settings.js @@ -3,6 +3,7 @@ import path from 'path'; import WebhookFileRepository from '../src/lib/repository/WebhookFileRepository'; import UsersFileRepository from '../src/lib/repository/UserFileRepository'; +import { DeviceAttributeFileRepository, DeviceKeyFileRepository } from 'spark-protocol'; export default { accessTokenLifetime: 7776000, // 90 days, @@ -16,6 +17,12 @@ export default { logRequests: false, maxHooksPerDevice: 10, maxHooksPerUser: 20, + deviceAttributeRepository: new DeviceAttributeFileRepository( + path.join(__dirname, '__test_data__/core_keys'), + ), + deviceKeyFileRepository: new DeviceKeyFileRepository( + path.join(__dirname, '__test_data__/core_keys'), + ), usersRepository: new UsersFileRepository( path.join(__dirname, '__test_data__/users'), ), diff --git a/test/testApp.js b/test/testApp.js index e3c4cf7b..e1f1efba 100644 --- a/test/testApp.js +++ b/test/testApp.js @@ -2,7 +2,9 @@ import createApp from '../src/app'; import settings from './settings'; +import DeviceServerMock from './DeviceServerMock'; -// TODO: mock the server or create a bootstrapper so there is only one instance -// of the device server -export default createApp(settings, {}); +const deviceServer = new DeviceServerMock(); +const app = createApp(settings, deviceServer); + +export default app; From c5a16ef10592dc938ec5e18ccf811bba5fe45bb5 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 15 Dec 2016 02:28:08 +0200 Subject: [PATCH 178/504] move test setup files to separte dir --- test/DevicesController.test.js | 4 ++-- test/UsersController.test.js | 4 ++-- test/WebhooksController.test.js | 4 ++-- test/{ => setup}/DeviceServerMock.js | 0 test/{ => setup}/SparkCoreMock.js | 0 test/{ => setup}/settings.js | 14 +++++++------- test/{ => setup}/testApp.js | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) rename test/{ => setup}/DeviceServerMock.js (100%) rename test/{ => setup}/SparkCoreMock.js (100%) rename test/{ => setup}/settings.js (65%) rename test/{ => setup}/testApp.js (84%) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 2bfa2dba..9a8d1270 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -1,8 +1,8 @@ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; -import app from './testApp'; -import settings from './settings'; +import app from './setup/testApp'; +import settings from './setup/settings'; const USER_CREDENTIALS = { password: 'password', diff --git a/test/UsersController.test.js b/test/UsersController.test.js index e3dd865d..ff49243a 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -3,8 +3,8 @@ import type { TokenObject, UserCredentials } from '../src/types'; import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; -import app from './testApp'; -import settings from './settings'; +import app from './setup/testApp'; +import settings from './setup/settings'; const USER_CREDENTIALS: UserCredentials = { password: 'password', diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index 70c41f5c..de5d5a61 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -3,8 +3,8 @@ import type { Webhook, WebhookMutator } from '../src/types'; import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; -import app from './testApp'; -import settings from './settings'; +import app from './setup/testApp'; +import settings from './setup/settings'; const USER_CREDENTIALS = { password: 'password', diff --git a/test/DeviceServerMock.js b/test/setup/DeviceServerMock.js similarity index 100% rename from test/DeviceServerMock.js rename to test/setup/DeviceServerMock.js diff --git a/test/SparkCoreMock.js b/test/setup/SparkCoreMock.js similarity index 100% rename from test/SparkCoreMock.js rename to test/setup/SparkCoreMock.js diff --git a/test/settings.js b/test/setup/settings.js similarity index 65% rename from test/settings.js rename to test/setup/settings.js index e67bb7d8..2441817d 100644 --- a/test/settings.js +++ b/test/setup/settings.js @@ -1,15 +1,15 @@ // @flow import path from 'path'; -import WebhookFileRepository from '../src/lib/repository/WebhookFileRepository'; -import UsersFileRepository from '../src/lib/repository/UserFileRepository'; +import WebhookFileRepository from '../../src/lib/repository/WebhookFileRepository'; +import UsersFileRepository from '../../src/lib/repository/UserFileRepository'; import { DeviceAttributeFileRepository, DeviceKeyFileRepository } from 'spark-protocol'; export default { accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, - coreKeysDir: path.join(__dirname, '__test_data__/core_keys'), + coreKeysDir: path.join(__dirname, '../__test_data__/core_keys'), coreRequestTimeout: 30000, coreSignalTimeout: 30000, isCoreOnlineTimeout: 2000, @@ -18,16 +18,16 @@ export default { maxHooksPerDevice: 10, maxHooksPerUser: 20, deviceAttributeRepository: new DeviceAttributeFileRepository( - path.join(__dirname, '__test_data__/core_keys'), + path.join(__dirname, '../__test_data__/core_keys'), ), deviceKeyFileRepository: new DeviceKeyFileRepository( - path.join(__dirname, '__test_data__/core_keys'), + path.join(__dirname, '../__test_data__/core_keys'), ), usersRepository: new UsersFileRepository( - path.join(__dirname, '__test_data__/users'), + path.join(__dirname, '../__test_data__/users'), ), webhookRepository: new WebhookFileRepository( - path.join(__dirname, '__test_data__/webhooks'), + path.join(__dirname, '../__test_data__/webhooks'), ), /** diff --git a/test/testApp.js b/test/setup/testApp.js similarity index 84% rename from test/testApp.js rename to test/setup/testApp.js index e1f1efba..5443eae4 100644 --- a/test/testApp.js +++ b/test/setup/testApp.js @@ -1,6 +1,6 @@ // @flow -import createApp from '../src/app'; +import createApp from '../../src/app'; import settings from './settings'; import DeviceServerMock from './DeviceServerMock'; From ad4495d7757af18a3b7c919f5b2029ff6b44c742 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 14 Dec 2016 21:58:09 -0800 Subject: [PATCH 179/504] Working on flasher --- src/lib/controllers/DevicesController.js | 7 +++---- src/lib/repository/DeviceRepository.js | 4 ++-- src/types.js | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index 60ee7a5e..dec46aa0 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -94,10 +94,9 @@ class DevicesController extends Controller { return this.ok({ id: deviceID, status: 'Update started' }); } - // TODO not implemented yet - // 3 flash device with precompiled binary - if (postBody.file_type === 'binary' && this.request.files.file) { - await this._deviceRepository.flashBinary(deviceID, this.request.files.file); + const file = this.request.files.file[0]; + if (file && file.originalname.endsWith('.bin')) { + await this._deviceRepository.flashBinary(deviceID, file); return this.ok({ id: deviceID, status: 'Update started' }); } } catch (error) { diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index d5700b3a..dea72ce8 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -177,7 +177,7 @@ class DeviceRepository { flashBinary = async ( deviceID: string, - files: Array, + file: File, ) => { const core = this._deviceServer.getCore(deviceID); if (!core) { @@ -186,7 +186,7 @@ class DeviceRepository { const result = await core.onApiMessage( deviceID, - { cmd: 'UFlash', args: { data: files[0].buffer } }, + { cmd: 'UFlash', args: { data: file.buffer } }, ); if (result.error) { diff --git a/src/types.js b/src/types.js index 3089c4f0..343ca2f4 100644 --- a/src/types.js +++ b/src/types.js @@ -128,7 +128,7 @@ export type DeviceRepository = { functionArguments: Object, ): Promise<*>, claimDevice(deviceID: string, userID: string): Promise, - flashBinary(deviceID: string, files: Array<$File>): Promise<*>, + flashBinary(deviceID: string, files: File): Promise<*>, flashKnownApp(deviceID: string, app: string): Promise<*>, getAll(userID: string): Promise>, getByID(deviceID: string, userID: string): Promise, From ab9bedfcb36297abca98fc1baf1a140cb1acb62a Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 15 Dec 2016 17:46:53 +0200 Subject: [PATCH 180/504] merge from master/ update flow-bin to 0.37 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9ff2e45..66478213 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "eslint-plugin-flowtype": "^2.28.2", "eslint-plugin-import": "^2.2.0", "eslint-plugin-sorting": "^0.3.0", - "flow-bin": "^0.36.0", + "flow-bin": "^0.37.0", "nodemon": "^1.11.0", "pre-commit": "^1.2.2", "rimraf": "^2.5.4", From e4ccf3a4351f6d017810e4c51e39743516cbc827 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Thu, 15 Dec 2016 18:29:14 +0200 Subject: [PATCH 181/504] throw error if test user creation fails in before hooks --- test/DevicesController.test.js | 4 ++++ test/WebhooksController.test.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 9a8d1270..73bed6a8 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -41,6 +41,10 @@ test.before(async () => { }); userToken = tokenResponse.body.access_token; + + if(!userToken) { + throw new Error('test user creation fails'); + } }); // TODO come up better test name diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index de5d5a61..bc9fedb5 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -40,6 +40,10 @@ test.before(async () => { }); userToken = tokenResponse.body.access_token; + + if(!userToken) { + throw new Error('test user creation fails'); + } }); test.serial('should create a new webhook object', async t => { From c4424fab05c5366370ba5ca8be7a0ef27d4818eb Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 00:11:59 +0200 Subject: [PATCH 182/504] fix provision errors throws. --- src/lib/controllers/ProvisioningController.js | 5 +++++ src/lib/repository/DeviceRepository.js | 10 +++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib/controllers/ProvisioningController.js b/src/lib/controllers/ProvisioningController.js index 532cbc0f..8af08eee 100644 --- a/src/lib/controllers/ProvisioningController.js +++ b/src/lib/controllers/ProvisioningController.js @@ -6,6 +6,7 @@ import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; import deviceToAPI from '../deviceToAPI'; +import HttpError from '../HttpError'; class ProvisioningController extends Controller { _deviceRepository: DeviceRepository; @@ -22,6 +23,10 @@ class ProvisioningController extends Controller { coreID: string, postBody: { publicKey: string }, ): Promise<*> { + if (!postBody.publicKey) { + throw new HttpError('No key provided'); + } + const device = await this._deviceRepository.provision( coreID, this.user.id, diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index dea72ce8..a3609fe2 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -223,19 +223,15 @@ class DeviceRepository { userID: string, publicKey: string, ): Promise<*> => { - if (!deviceID) { - throw new HttpError('No deviceID provided'); - } - try { const createdKey = ursa.createPublicKey(publicKey); - if (!publicKey || !ursa.isPublicKey(createdKey)) { - throw new HttpError('No key provided'); + if (!ursa.isPublicKey(createdKey)) { + throw new HttpError('Not a public key'); } } catch (error) { - logger.error('error while parsing publicKey', error); throw new HttpError(`Key error ${error}`); } + await this._deviceKeyRepository.update(deviceID, publicKey); const existingAttributes = await this._deviceAttributeRepository.getById( deviceID, From 40fd77f543c947337671e057990a8be802c7a90c Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 00:12:54 +0200 Subject: [PATCH 183/504] move provision tests to separate file --- test/DevicesController.test.js | 16 +++--- test/ProvisioningController.test.js | 82 +++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 test/ProvisioningController.test.js diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 73bed6a8..18e929df 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -42,23 +42,21 @@ test.before(async () => { userToken = tokenResponse.body.access_token; - if(!userToken) { + if (!userToken) { throw new Error('test user creation fails'); } -}); -// TODO come up better test name -test.serial('provision test', async t => { - const response = await request(app) + const provisionResponse = await request(app) .post(`/v1/provisioning/${DEVICE_ID}`) .query({ access_token: userToken }) .send({ publicKey: TEST_PUBLIC_KEY }); - deviceToApiAttributes = response.body; - t.is(response.status, 200); - t.is(response.body.id, DEVICE_ID); + deviceToApiAttributes = provisionResponse.body; + + if (!deviceToApiAttributes.id) { + throw new Error('test device creation fails'); + } }); -// TODO write test for checking the error if publicKey is in wrong format. test('should throw an error for compile source code endpoint', async t => { const response = await request(app) diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js new file mode 100644 index 00000000..84ee3aef --- /dev/null +++ b/test/ProvisioningController.test.js @@ -0,0 +1,82 @@ +import test from 'ava'; +import request from 'supertest-as-promised'; +import ouathClients from '../src/oauthClients.json'; +import app from './setup/testApp'; +import settings from './setup/settings'; + +const USER_CREDENTIALS = { + password: 'password', + username: 'provisionTestUser@test.com', +}; + +const DEVICE_ID = '350023001951353337343733'; +const TEST_PUBLIC_KEY = + '-----BEGIN PUBLIC KEY-----\n' + + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsxJFqlUOxK5bsEfTtBCe9sXBa' + + '43q9QoSPFXEG5qY/+udOpf2SKacgfUVdUbK4WOkLou7FQ+DffpwztBk5fWM9qfzF' + + 'EQRVMS8xwS4JqqD7slXwuPWFpS9SGy9kLNy/pl1dtGm556wVX431Dg7UBKiXuNGR' + + '7E8d2hfgeyiTtsWfUQIDAQAB\n' + + '-----END PUBLIC KEY-----\n'; + +let testUser; +let userToken; + +test.before(async () => { + const userResponse = await request(app) + .post('/v1/users') + .send(USER_CREDENTIALS); + + testUser = userResponse.body; + + const tokenResponse = await request(app) + .post('/oauth/token') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + client_id: ouathClients[0].clientId, + client_secret: ouathClients[0].clientSecret, + grant_type: 'password', + password: USER_CREDENTIALS.password, + username: USER_CREDENTIALS.username, + }); + + userToken = tokenResponse.body.access_token; + + if (!userToken) { + throw new Error('test user creation fails'); + } +}); + +test('provision and add keys for a device.', async t => { + const response = await request(app) + .post(`/v1/provisioning/${DEVICE_ID}`) + .query({ access_token: userToken }) + .send({ publicKey: TEST_PUBLIC_KEY }); + + t.is(response.status, 200); + t.is(response.body.id, DEVICE_ID); +}); + +test('should throw an error if public key has wrong format', async t => { + const response = await request(app) + .post(`/v1/provisioning/${DEVICE_ID}`) + .query({ access_token: userToken }) + .send({ publicKey: `dsfsdf13${TEST_PUBLIC_KEY}` }); + + t.is(response.status, 400); + t.truthy(response.body.error); +}); + +test('should throw an error if public key is not provided', async t => { + const response = await request(app) + .post(`/v1/provisioning/${DEVICE_ID}`) + .query({ access_token: userToken }); + + t.is(response.status, 400); + t.is(response.body.error, 'No key provided'); +}); + +test.after.always(() => { + settings.usersRepository.deleteById(testUser.id); + settings.deviceAttributeRepository.deleteById(DEVICE_ID); + settings.deviceKeyFileRepository.delete(DEVICE_ID); +}); From 794a1fb53227aaea596ded1863386b3ffc718f4e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 00:15:07 +0200 Subject: [PATCH 184/504] temporary add --serial to test run scripts , because sometimes random tests fail with 503 error in parallel testing. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c9ff2e45..9724d43e 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", "start:prod": "npm run build && node ./build/main.js", - "test": "ava", - "test:watch": "ava --watch" + "test": "ava --serial", + "test:watch": "ava --watch --serial" }, "pre-commit": "test", "ava": { From 013112243006cd0a33a582d3a93f4e5218eec51d Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 01:20:07 +0200 Subject: [PATCH 185/504] decorators flow annotations --- src/lib/decorators/allowUpload.js | 26 ++++++++++++++++---------- src/lib/decorators/anonymous.js | 9 +++++---- src/lib/decorators/httpVerb.js | 17 ++++++++++------- src/lib/decorators/route.js | 17 +++++++++++------ src/lib/decorators/types.js | 15 +++++++++++---- 5 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/lib/decorators/allowUpload.js b/src/lib/decorators/allowUpload.js index 4df92724..121aa113 100644 --- a/src/lib/decorators/allowUpload.js +++ b/src/lib/decorators/allowUpload.js @@ -1,14 +1,20 @@ -import type { Decorator, HttpVerb } from './types'; +// @flow +import type { Decorator, Descriptor } from './types'; +import type Controller from '../controllers/Controller'; + +/* eslint-disable no-param-reassign */ export default ( fileName: string, maxCount: number = 0, -): Decorator => (target, name, descriptor): Object => { - const allowedUploads = target[name].allowedUploads || []; - allowedUploads.push({ - maxCount: maxCount ? maxCount : undefined, - name: fileName, - }); - target[name].allowedUploads = allowedUploads; - return descriptor; -}; +): Decorator => + (target: Controller, name: string, descriptor: Descriptor): Descriptor => { + const allowedUploads = target[name].allowedUploads || []; + allowedUploads.push({ + maxCount, + name: fileName, + }); + + target[name].allowedUploads = allowedUploads; + return descriptor; + }; diff --git a/src/lib/decorators/anonymous.js b/src/lib/decorators/anonymous.js index baae5fc9..c459541c 100644 --- a/src/lib/decorators/anonymous.js +++ b/src/lib/decorators/anonymous.js @@ -1,10 +1,11 @@ // @flow -import type { Decorator } from './types'; +import type { Decorator, Descriptor } from './types'; +import type Controller from '../controllers/Controller'; -export default (): Decorator => - (target, name, descriptor): Object => { - // eslint-disable-next-line no-param-reassign +/* eslint-disable no-param-reassign */ +export default (): Decorator => + (target: Controller, name: string, descriptor: Descriptor): Descriptor => { target[name].anonymous = true; return descriptor; }; diff --git a/src/lib/decorators/httpVerb.js b/src/lib/decorators/httpVerb.js index ef16cf62..74efc366 100644 --- a/src/lib/decorators/httpVerb.js +++ b/src/lib/decorators/httpVerb.js @@ -1,8 +1,11 @@ -import type { Decorator, HttpVerb } from './types'; +// @flow -export default ( - httpVerb: HttpVerb, -): Decorator => (target, name, descriptor): Object => { - target[name].httpVerb = httpVerb; - return descriptor; -}; +import type { Decorator, Descriptor } from './types'; +import type Controller from '../controllers/Controller'; + +/* eslint-disable no-param-reassign */ +export default (httpVerb: HttpVerb): Decorator => + (target: Controller, name: string, descriptor: Descriptor): Descriptor => { + target[name].httpVerb = httpVerb; + return descriptor; + }; diff --git a/src/lib/decorators/route.js b/src/lib/decorators/route.js index 761eff11..551eec58 100644 --- a/src/lib/decorators/route.js +++ b/src/lib/decorators/route.js @@ -1,6 +1,11 @@ -export default ( - route: string, -): Decorator => (target, name, descriptor): Object => { - target[name].route = route; - return descriptor; -}; +// @flow + +import type { Decorator, Descriptor } from './types'; +import type Controller from '../controllers/Controller'; + +/* eslint-disable no-param-reassign */ +export default (route: string): Decorator => + (target: Controller, name: string, descriptor: Descriptor): Descriptor => { + target[name].route = route; + return descriptor; + }; diff --git a/src/lib/decorators/types.js b/src/lib/decorators/types.js index e8ebeb26..11dbc147 100644 --- a/src/lib/decorators/types.js +++ b/src/lib/decorators/types.js @@ -1,10 +1,17 @@ // @flow -export type Decorator = ( - target: Object, +export type Decorator = ( + target: TType, name: string, - descriptor: Object, -) => Object; + descriptor: Descriptor, +) => Descriptor; + +export type Descriptor = { + configurable: boolean, + enumerable: boolean, + value: Function, + writeable: boolean, +}; export type HttpVerb = 'checkout' | From f33ef969d123fd9b0859affa23ac2f56a6700c52 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 15 Dec 2016 22:11:23 -0800 Subject: [PATCH 186/504] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f3a07e08..3c5a78c9 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ This will create a new profile to point to your server and switching back to the 4) We will now point over to the local cloud using ``` -particle config profile_name +particle config {profile_name} ``` 5) On a separate CMD from the one running the server, type @@ -65,7 +65,9 @@ This will create an account on the local cloud Perform CTRL + C once you logon with Particle-CLI asking you to send Wifi-credentials etc... -6) Put your core into listening mode, and run `spark identify` to get your core id. You'll need this id later +6) Put your core into listening mode, and run +```particle identify``` +to get your core id. You'll need this id later 7) `mkdir ..\temp` and `cd ..\temp` - A bunch of keys will be generated in the next steps. From ba0fa52e1d60d30f062b0624331fa63dfffc2cd0 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 15 Dec 2016 22:11:46 -0800 Subject: [PATCH 187/504] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c5a78c9..63ba532d 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,9 @@ This will create an account on the local cloud Perform CTRL + C once you logon with Particle-CLI asking you to send Wifi-credentials etc... 6) Put your core into listening mode, and run -```particle identify``` +``` +particle identify +``` to get your core id. You'll need this id later 7) `mkdir ..\temp` and `cd ..\temp` - A bunch of keys will be generated in the next steps. From e66642122c5a52fa08d50a570d0495e203a5aaad Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 15 Dec 2016 22:15:30 -0800 Subject: [PATCH 188/504] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 63ba532d..78575385 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,14 @@ particle identify ``` to get your core id. You'll need this id later -7) `mkdir ..\temp` and `cd ..\temp` - A bunch of keys will be generated in the next steps. +7) The next steps will generate a bunch of keys for your device. I recommend `mkdir ..\temp` and `cd ..\temp` 8) Change server keys to local cloud key + IP Address ``` particle keys server ..\spark-server\default_key.pub.pem IP_ADDRESS ``` +**Note** You can go back to using the particle server by [downlading the public key here](https://s3.amazonaws.com/spark-website/cloud_public.der) 9) Create and provision access on your local cloud with the keys doctor: @@ -85,6 +86,10 @@ particle keys server ..\spark-server\default_key.pub.pem IP_ADDRESS particle keys doctor your_core_id ``` +*** + +At this point you should be able to run normal cloud commands and flash binaries. You can add any webhooks you need, call functions, or get variable values. + What kind of project is this? ====================================== From d4322ca8b4838b8e753dbeeb5289cb298c6cab9c Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 15 Dec 2016 22:16:57 -0800 Subject: [PATCH 189/504] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78575385..7c146570 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ This will create a new profile to point to your server and switching back to the 4) We will now point over to the local cloud using ``` -particle config {profile_name} +particle config profile_name ``` 5) On a separate CMD from the one running the server, type @@ -78,7 +78,7 @@ to get your core id. You'll need this id later ``` particle keys server ..\spark-server\default_key.pub.pem IP_ADDRESS ``` -**Note** You can go back to using the particle server by [downlading the public key here](https://s3.amazonaws.com/spark-website/cloud_public.der) +**Note You can go back to using the particle cloud by [downlading the public key here](https://s3.amazonaws.com/spark-website/cloud_public.der)** You'll need to run `particle config particle`, `particle keys server cloud_public.der`, and `particle keys doctor your_core_id` while your device is in DFU mode. 9) Create and provision access on your local cloud with the keys doctor: From f2e550e7b3f511c0fe2f35a16403385bff3f98bd Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 15 Dec 2016 22:17:24 -0800 Subject: [PATCH 190/504] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c146570..81eb539f 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,8 @@ to get your core id. You'll need this id later ``` particle keys server ..\spark-server\default_key.pub.pem IP_ADDRESS ``` -**Note You can go back to using the particle cloud by [downlading the public key here](https://s3.amazonaws.com/spark-website/cloud_public.der)** You'll need to run `particle config particle`, `particle keys server cloud_public.der`, and `particle keys doctor your_core_id` while your device is in DFU mode. +**Note You can go back to using the particle cloud by [downlading the public key here](https://s3.amazonaws.com/spark-website/cloud_public.der).** +You'll need to run `particle config particle`, `particle keys server cloud_public.der`, and `particle keys doctor your_core_id` while your device is in DFU mode. 9) Create and provision access on your local cloud with the keys doctor: From d7341cd51918d48b238f5f526b0274e4c11e9270 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 15:38:22 +0200 Subject: [PATCH 191/504] update readme --- README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 81eb539f..ae9e11a8 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,18 @@ The spark-server module aims to provide a HTTP rest interface that is API compat programs you write to run against the Spark Cloud should also work on the Local Cloud. Some features aren't here yet, but may be coming down the road, right now the endpoints exposed are: +Claim Core + +`POST /v1/devices` + +Release Core + +`DELETE /v1/devices/:coreid` + +Provision Core and save Core's keys. + +`/v1/provisioning/:coreID` + List devices `GET /v1/devices` @@ -138,8 +150,6 @@ Get all Events GET /v1/events/:event_name ``` - - Get all my Events ``` @@ -161,12 +171,6 @@ Publish an event What features will be added soon? ==================================== -- Release a Core - DELETE /v1/devices/:coreid - -- Claim a core - POST /v1/devices - - per-user / per-core ownership and access restrictions. Right now ANY user on your local cloud can access ANY device. - remote compiling, however flashing a binary will still work From ebc60b834c40c2bb7ac66f9c924dce194d3c490e Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 16 Dec 2016 08:52:27 -0800 Subject: [PATCH 192/504] Simplify RouteConfig.js --- src/lib/RouteConfig.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index 0ed78655..f46907ac 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -77,7 +77,7 @@ export default ( allowedUploads ? injectFilesMiddleware.fields(allowedUploads) : defaultMiddleware, - (request: $Request, response: $Response) => { + async (request: $Request, response: $Response) => { const argumentNames = (route.match(/:[\w]*/g) || []).map( (argumentName: string): string => argumentName.replace(':', ''), ); @@ -104,16 +104,8 @@ export default ( ); if (functionResult.then) { - functionResult - .then((result: Object) => { - response.status(result.status).json(result.data); - }) - .catch((error: HttpError) => { - response.status(error.status).json({ - error: error.message, - ok: false, - }); - }); + const result = await functionResult; + response.status(result.status).json(result.data); } else { response.status(functionResult.status).json(functionResult.data); } From 7e04719f1a560967cc4e23b3759ef211d4a2aaa3 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 15:11:18 +0200 Subject: [PATCH 193/504] add getVar, fix some flow --- src/lib/controllers/DevicesController.js | 22 +++++++++++++ src/lib/repository/DeviceRepository.js | 39 +++++++++++++++++++++--- src/types.js | 5 +++ 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index dec46aa0..a3083add 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -66,6 +66,28 @@ class DevicesController extends Controller { return this.ok(deviceToAPI(device)); } + @httpVerb('get') + @route('/v1/devices/:deviceID/:varName/') + async getVariableValue( + deviceID: string, + varName: string, + ): Promise<*> { + try { + const varValue = await this._deviceRepository.getVariableValue( + deviceID, + this.user.id, + varName, + ); + + return this.ok({ result: varValue }); + } catch (error) { + if (error.indexOf && error.indexOf('Variable not found') >= 0) { + throw new HttpError('Variable not found', 404); + } + throw error; + } + } + @httpVerb('put') @route('/v1/devices/:deviceID') @allowUpload('file', 1) diff --git a/src/lib/repository/DeviceRepository.js b/src/lib/repository/DeviceRepository.js index a3609fe2..d9828213 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/lib/repository/DeviceRepository.js @@ -2,22 +2,26 @@ import type { File } from 'express'; import type { DeviceServer } from 'spark-protocol'; -import type { Device, DeviceAttributes, Repository } from '../../types'; +import type { + Device, + DeviceAttributes, + DeviceAttributeRepository, + Repository, + } from '../../types'; import Moniker from 'moniker'; import ursa from 'ursa'; -import logger from '../logger'; import HttpError from '../HttpError'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); class DeviceRepository { - _deviceAttributeRepository: Repository; + _deviceAttributeRepository: DeviceAttributeRepository; _deviceKeyRepository: Repository; _deviceServer: DeviceServer; constructor( - deviceAttributeRepository: Repository, + deviceAttributeRepository: DeviceAttributeRepository, deviceKeyRepository: Repository, deviceServer: DeviceServer, ) { @@ -100,7 +104,7 @@ class DeviceRepository { throw new HttpError('No device found', 404); } - const [ attributes, description ] = await Promise.all([ + const [attributes, description] = await Promise.all([ this._deviceAttributeRepository.getById(deviceID, userID), core.onApiMessage( deviceID, @@ -175,6 +179,31 @@ class DeviceRepository { return result.result; }; + getVariableValue = async ( + deviceID: string, + userID: string, + varName: string, + ): Promise => { + if (!this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + throw new HttpError('No device found', 404); + } + + const core = this._deviceServer.getCore(deviceID); + if (!core) { + throw new HttpError('Could not get device for ID', 404); + } + const result = await core.onApiMessage( + deviceID, + { cmd: 'GetVar', name: varName }, + ); + + if (result.error) { + throw result.error; + } + + return result; + }; + flashBinary = async ( deviceID: string, file: File, diff --git a/src/types.js b/src/types.js index 343ca2f4..d170eb54 100644 --- a/src/types.js +++ b/src/types.js @@ -120,6 +120,10 @@ export type Settings = { webhookRepository: Repository<*>, }; +export type DeviceAttributeRepository = Repository & { + doesUserHaveAccess(deviceID: string, userID: string): Promise, +}; + export type DeviceRepository = { callFunction( deviceID: string, @@ -133,6 +137,7 @@ export type DeviceRepository = { getAll(userID: string): Promise>, getByID(deviceID: string, userID: string): Promise, getDetailsByID(deviceID: string, userID: string): Promise<*>, + getVariableValue(deviceID: string, userID: string, varName: string): Promise, provision(deviceID: string, userID: string, publicKey: string): Promise<*>, renameDevice(deviceID: string, userID: string, name: string): Promise, unclaimDevice(deviceID: string, userID: string): Promise, From 7000efeef45182eeff8980025984ddd99a683f83 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 23:11:13 +0200 Subject: [PATCH 194/504] httpError fix --- src/lib/HttpError.js | 13 +++++++--- src/lib/RouteConfig.js | 11 +++++---- src/lib/controllers/DevicesController.js | 30 ++++++++++-------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/lib/HttpError.js b/src/lib/HttpError.js index 91269c22..bb4cb74b 100644 --- a/src/lib/HttpError.js +++ b/src/lib/HttpError.js @@ -3,9 +3,16 @@ class HttpError extends Error { status: number; - constructor(message: string, status?: number = 400) { - super(message); - this.status = status; + constructor( + error: string | Error | HttpError, + status?: number = 400, + ) { + super(error.message || error); + if (typeof error.status === 'number') { + this.status = error.status; + } else { + this.status = status; + } } } diff --git a/src/lib/RouteConfig.js b/src/lib/RouteConfig.js index f46907ac..ca9893b6 100644 --- a/src/lib/RouteConfig.js +++ b/src/lib/RouteConfig.js @@ -9,11 +9,11 @@ import type { } from 'express'; import type { Settings } from '../types'; import type Controller from './controllers/Controller'; -import type HttpError from './HttpError'; -import OAuthModel from './OAuthModel'; import OAuthServer from 'express-oauth-server'; import multer from 'multer'; +import OAuthModel from './OAuthModel'; +import HttpError from './HttpError'; // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => @@ -77,7 +77,7 @@ export default ( allowedUploads ? injectFilesMiddleware.fields(allowedUploads) : defaultMiddleware, - async (request: $Request, response: $Response) => { + async (request: $Request, response: $Response): Promise => { const argumentNames = (route.match(/:[\w]*/g) || []).map( (argumentName: string): string => argumentName.replace(':', ''), ); @@ -110,8 +110,9 @@ export default ( response.status(functionResult.status).json(functionResult.data); } } catch (error) { - response.status(error.status).json({ - error: error.message, + const httpError = new HttpError(error); + response.status(httpError.status).json({ + error: httpError.message, ok: false, }); } diff --git a/src/lib/controllers/DevicesController.js b/src/lib/controllers/DevicesController.js index a3083add..9ae34805 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/lib/controllers/DevicesController.js @@ -31,7 +31,7 @@ class DevicesController extends Controller { @httpVerb('post') @route('/v1/binaries') - compileSources() { + compileSources() { // eslint-disable-line class-methods-use-this throw new HttpError('not supported in the current server version'); } @@ -107,22 +107,18 @@ class DevicesController extends Controller { } // TODO not implemented yet // 2 flash device with known app - try { - if (postBody.app_id) { - this._deviceRepository.flashKnownApp( - deviceID, - postBody.app_id, - ); - return this.ok({ id: deviceID, status: 'Update started' }); - } + if (postBody.app_id) { + this._deviceRepository.flashKnownApp( + deviceID, + postBody.app_id, + ); + return this.ok({ id: deviceID, status: 'Update started' }); + } - const file = this.request.files.file[0]; - if (file && file.originalname.endsWith('.bin')) { - await this._deviceRepository.flashBinary(deviceID, file); - return this.ok({ id: deviceID, status: 'Update started' }); - } - } catch (error) { - throw new HttpError(error.message); + const file = this.request.files.file[0]; + if (file && file.originalname.endsWith('.bin')) { + await this._deviceRepository.flashBinary(deviceID, file); + return this.ok({ id: deviceID, status: 'Update started' }); } throw new HttpError('Did not update device'); @@ -152,7 +148,7 @@ class DevicesController extends Controller { if (error.indexOf && error.indexOf('Unknown Function') >= 0) { throw new HttpError('Function not found', 404); } - throw new HttpError(error.message); + throw error; } } } From 631a5bbb933d9111fd7464137b0a39260c1cdd9f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Fri, 16 Dec 2016 23:35:49 +0200 Subject: [PATCH 195/504] move files from lib --- src/{lib => }/OAuthModel.js | 4 ++-- src/{lib => }/RouteConfig.js | 4 ++-- src/app.js | 12 ++++++------ src/{lib => }/controllers/Controller.js | 2 +- src/{lib => }/controllers/DevicesController.js | 8 ++++---- src/{lib => }/controllers/EventsController.js | 6 +++++- src/{lib => }/controllers/ProvisioningController.js | 6 +++--- src/{lib => }/controllers/UsersController.js | 4 ++-- src/{lib => }/controllers/WebhooksController.js | 4 ++-- src/{lib => }/controllers/types.js | 1 - src/{lib => }/decorators/allowUpload.js | 0 src/{lib => }/decorators/anonymous.js | 0 src/{lib => }/decorators/httpVerb.js | 2 +- src/{lib => }/decorators/route.js | 0 src/{lib => }/decorators/types.js | 0 src/{lib => }/repository/DeviceRepository.js | 5 ++--- src/{lib => }/repository/UserFileRepository.js | 6 +++--- src/{lib => }/repository/WebhookFileRepository.js | 4 ++-- src/settings.js | 4 ++-- test/setup/settings.js | 4 ++-- 20 files changed, 39 insertions(+), 37 deletions(-) rename src/{lib => }/OAuthModel.js (95%) rename src/{lib => }/RouteConfig.js (98%) rename src/{lib => }/controllers/Controller.js (91%) rename src/{lib => }/controllers/DevicesController.js (95%) rename src/{lib => }/controllers/EventsController.js (61%) rename src/{lib => }/controllers/ProvisioningController.js (85%) rename src/{lib => }/controllers/UsersController.js (96%) rename src/{lib => }/controllers/WebhooksController.js (97%) rename src/{lib => }/controllers/types.js (99%) rename src/{lib => }/decorators/allowUpload.js (100%) rename src/{lib => }/decorators/anonymous.js (100%) rename src/{lib => }/decorators/httpVerb.js (83%) rename src/{lib => }/decorators/route.js (100%) rename src/{lib => }/decorators/types.js (100%) rename src/{lib => }/repository/DeviceRepository.js (98%) rename src/{lib => }/repository/UserFileRepository.js (94%) rename src/{lib => }/repository/WebhookFileRepository.js (91%) diff --git a/src/lib/OAuthModel.js b/src/OAuthModel.js similarity index 95% rename from src/lib/OAuthModel.js rename to src/OAuthModel.js index 18b9b69e..2921e7df 100644 --- a/src/lib/OAuthModel.js +++ b/src/OAuthModel.js @@ -5,9 +5,9 @@ import type { TokenObject, User, UserRepository, -} from '../types'; +} from './types'; -import ouathClients from '../oauthClients.json'; +import ouathClients from './oauthClients.json'; class OauthModel { _userRepository: UserRepository; diff --git a/src/lib/RouteConfig.js b/src/RouteConfig.js similarity index 98% rename from src/lib/RouteConfig.js rename to src/RouteConfig.js index ca9893b6..5d375c49 100644 --- a/src/lib/RouteConfig.js +++ b/src/RouteConfig.js @@ -7,13 +7,13 @@ import type { Middleware, NextFunction, } from 'express'; -import type { Settings } from '../types'; +import type { Settings } from './types'; import type Controller from './controllers/Controller'; import OAuthServer from 'express-oauth-server'; import multer from 'multer'; import OAuthModel from './OAuthModel'; -import HttpError from './HttpError'; +import HttpError from './lib/HttpError'; // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => diff --git a/src/app.js b/src/app.js index e872d4d8..042f8c99 100644 --- a/src/app.js +++ b/src/app.js @@ -17,18 +17,18 @@ import api from './views/api_v1'; import eventsV1 from './views/EventViews001'; // Repositories -import DeviceRepository from './lib/repository/DeviceRepository'; +import DeviceRepository from './repository/DeviceRepository'; import { DeviceAttributeFileRepository, DeviceKeyFileRepository, } from 'spark-protocol'; // Routing -import routeConfig from './lib/RouteConfig'; -import DevicesController from './lib/controllers/DevicesController'; -import ProvisioningController from './lib/controllers/ProvisioningController'; -import UsersController from './lib/controllers/UsersController'; -import WebhooksController from './lib/controllers/WebhooksController'; +import routeConfig from './RouteConfig'; +import DevicesController from './controllers/DevicesController'; +import ProvisioningController from './controllers/ProvisioningController'; +import UsersController from './controllers/UsersController'; +import WebhooksController from './controllers/WebhooksController'; export default (settings: Settings, deviceServer: Object): $Application => { const app = express(); diff --git a/src/lib/controllers/Controller.js b/src/controllers/Controller.js similarity index 91% rename from src/lib/controllers/Controller.js rename to src/controllers/Controller.js index c8c65b41..77294b81 100644 --- a/src/lib/controllers/Controller.js +++ b/src/controllers/Controller.js @@ -1,7 +1,7 @@ // @flow import type { $Request, $Response } from 'express'; -import type { User } from '../../types'; +import type { User } from '../types'; import type { HttpResult } from './types'; export default class Controller { diff --git a/src/lib/controllers/DevicesController.js b/src/controllers/DevicesController.js similarity index 95% rename from src/lib/controllers/DevicesController.js rename to src/controllers/DevicesController.js index 9ae34805..4c8e5f12 100644 --- a/src/lib/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -1,15 +1,15 @@ // @flow -import type { Device, DeviceRepository } from '../../types'; -import type { DeviceAPIType } from '../deviceToAPI'; +import type { Device, DeviceRepository } from '../types'; +import type { DeviceAPIType } from '../lib/deviceToAPI'; import Controller from './Controller'; -import HttpError from '../HttpError'; +import HttpError from '../lib/HttpError'; import allowUpload from '../decorators/allowUpload'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; -import deviceToAPI from '../deviceToAPI'; +import deviceToAPI from '../lib/deviceToAPI'; class DevicesController extends Controller { _deviceRepository: DeviceRepository; diff --git a/src/lib/controllers/EventsController.js b/src/controllers/EventsController.js similarity index 61% rename from src/lib/controllers/EventsController.js rename to src/controllers/EventsController.js index 8d9cce1f..71f4401c 100644 --- a/src/lib/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -1,5 +1,9 @@ +// @flow + +import Controller from './Controller'; + class EventsController extends Controller { - + } export default EventsController; diff --git a/src/lib/controllers/ProvisioningController.js b/src/controllers/ProvisioningController.js similarity index 85% rename from src/lib/controllers/ProvisioningController.js rename to src/controllers/ProvisioningController.js index 8af08eee..0de9d43b 100644 --- a/src/lib/controllers/ProvisioningController.js +++ b/src/controllers/ProvisioningController.js @@ -1,12 +1,12 @@ // @flow -import type { DeviceRepository } from '../../types'; +import type { DeviceRepository } from '../types'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; -import deviceToAPI from '../deviceToAPI'; -import HttpError from '../HttpError'; +import deviceToAPI from '../lib/deviceToAPI'; +import HttpError from '../lib/HttpError'; class ProvisioningController extends Controller { _deviceRepository: DeviceRepository; diff --git a/src/lib/controllers/UsersController.js b/src/controllers/UsersController.js similarity index 96% rename from src/lib/controllers/UsersController.js rename to src/controllers/UsersController.js index 10506954..848bc6f2 100644 --- a/src/lib/controllers/UsersController.js +++ b/src/controllers/UsersController.js @@ -3,11 +3,11 @@ import type { UserCredentials, UserRepository, -} from '../../types'; +} from '../types'; import basicAuthParser from 'basic-auth-parser'; import Controller from './Controller'; -import HttpError from '../HttpError'; +import HttpError from '../lib/HttpError'; import anonymous from '../decorators/anonymous'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; diff --git a/src/lib/controllers/WebhooksController.js b/src/controllers/WebhooksController.js similarity index 97% rename from src/lib/controllers/WebhooksController.js rename to src/controllers/WebhooksController.js index 82a9548e..996d7b0c 100644 --- a/src/lib/controllers/WebhooksController.js +++ b/src/controllers/WebhooksController.js @@ -5,10 +5,10 @@ import type { RequestType, Webhook, WebhookMutator, -} from '../../types'; +} from '../types'; import Controller from './Controller'; -import HttpError from '../HttpError'; +import HttpError from '../lib/HttpError'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; diff --git a/src/lib/controllers/types.js b/src/controllers/types.js similarity index 99% rename from src/lib/controllers/types.js rename to src/controllers/types.js index e4e2b066..c32a21e3 100644 --- a/src/lib/controllers/types.js +++ b/src/controllers/types.js @@ -1,6 +1,5 @@ // @flow - export type HttpResult = { data: ?TType, status: number, diff --git a/src/lib/decorators/allowUpload.js b/src/decorators/allowUpload.js similarity index 100% rename from src/lib/decorators/allowUpload.js rename to src/decorators/allowUpload.js diff --git a/src/lib/decorators/anonymous.js b/src/decorators/anonymous.js similarity index 100% rename from src/lib/decorators/anonymous.js rename to src/decorators/anonymous.js diff --git a/src/lib/decorators/httpVerb.js b/src/decorators/httpVerb.js similarity index 83% rename from src/lib/decorators/httpVerb.js rename to src/decorators/httpVerb.js index 74efc366..b7aadfc1 100644 --- a/src/lib/decorators/httpVerb.js +++ b/src/decorators/httpVerb.js @@ -1,6 +1,6 @@ // @flow -import type { Decorator, Descriptor } from './types'; +import type { Decorator, Descriptor, HttpVerb } from './types'; import type Controller from '../controllers/Controller'; /* eslint-disable no-param-reassign */ diff --git a/src/lib/decorators/route.js b/src/decorators/route.js similarity index 100% rename from src/lib/decorators/route.js rename to src/decorators/route.js diff --git a/src/lib/decorators/types.js b/src/decorators/types.js similarity index 100% rename from src/lib/decorators/types.js rename to src/decorators/types.js diff --git a/src/lib/repository/DeviceRepository.js b/src/repository/DeviceRepository.js similarity index 98% rename from src/lib/repository/DeviceRepository.js rename to src/repository/DeviceRepository.js index d9828213..865d6fac 100644 --- a/src/lib/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -5,13 +5,12 @@ import type { DeviceServer } from 'spark-protocol'; import type { Device, DeviceAttributes, - DeviceAttributeRepository, Repository, - } from '../../types'; +} from '../types'; import Moniker from 'moniker'; import ursa from 'ursa'; -import HttpError from '../HttpError'; +import HttpError from '../lib/HttpError'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); diff --git a/src/lib/repository/UserFileRepository.js b/src/repository/UserFileRepository.js similarity index 94% rename from src/lib/repository/UserFileRepository.js rename to src/repository/UserFileRepository.js index f4a87253..9b995033 100644 --- a/src/lib/repository/UserFileRepository.js +++ b/src/repository/UserFileRepository.js @@ -1,10 +1,10 @@ // @flow -import type { TokenObject, User, UserCredentials } from '../../types'; +import type { TokenObject, User, UserCredentials } from '../types'; import { JSONFileManager, uuid } from 'spark-protocol'; -import PasswordHasher from '../PasswordHasher'; -import HttpError from '../HttpError'; +import PasswordHasher from '../lib/PasswordHasher'; +import HttpError from '../lib/HttpError'; class UserFileRepository { _fileManager: JSONFileManager; diff --git a/src/lib/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js similarity index 91% rename from src/lib/repository/WebhookFileRepository.js rename to src/repository/WebhookFileRepository.js index e8bbf1fd..9c1f54c7 100644 --- a/src/lib/repository/WebhookFileRepository.js +++ b/src/repository/WebhookFileRepository.js @@ -1,9 +1,9 @@ // @flow -import type { Webhook } from '../../types'; +import type { Webhook } from '../types'; import { JSONFileManager, uuid } from 'spark-protocol'; -import HttpError from '../HttpError'; +import HttpError from '../lib/HttpError'; class WebhookFileRepository { _fileManager: JSONFileManager; diff --git a/src/settings.js b/src/settings.js index 6f8642e6..e5250486 100644 --- a/src/settings.js +++ b/src/settings.js @@ -20,8 +20,8 @@ */ import path from 'path'; -import WebhookFileRepository from './lib/repository/WebhookFileRepository'; -import UsersFileRepository from './lib/repository/UserFileRepository'; +import WebhookFileRepository from './repository/WebhookFileRepository'; +import UsersFileRepository from './repository/UserFileRepository'; export default { accessTokenLifetime: 7776000, // 90 days, diff --git a/test/setup/settings.js b/test/setup/settings.js index 2441817d..25e69536 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -1,8 +1,8 @@ // @flow import path from 'path'; -import WebhookFileRepository from '../../src/lib/repository/WebhookFileRepository'; -import UsersFileRepository from '../../src/lib/repository/UserFileRepository'; +import WebhookFileRepository from '../../src/repository/WebhookFileRepository'; +import UsersFileRepository from '../../src/repository/UserFileRepository'; import { DeviceAttributeFileRepository, DeviceKeyFileRepository } from 'spark-protocol'; export default { From 1047fec5e4f68d43f14bb9461d28e1459f5c4f4f Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Sat, 17 Dec 2016 00:57:15 +0200 Subject: [PATCH 196/504] fix errors format --- src/controllers/DevicesController.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 4c8e5f12..b631def1 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -81,7 +81,8 @@ class DevicesController extends Controller { return this.ok({ result: varValue }); } catch (error) { - if (error.indexOf && error.indexOf('Variable not found') >= 0) { + const errorMessage = error.message; + if (errorMessage.indexOf('Variable not found') >= 0) { throw new HttpError('Variable not found', 404); } throw error; @@ -145,7 +146,8 @@ class DevicesController extends Controller { ); return this.ok(deviceToAPI(device, result)); } catch (error) { - if (error.indexOf && error.indexOf('Unknown Function') >= 0) { + const errorMessage = error.message; + if (errorMessage.indexOf('Unknown Function') >= 0) { throw new HttpError('Function not found', 404); } throw error; From 1f1695d15882f45f54463f0e4ca1828793b707b2 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 18 Dec 2016 12:40:42 -0800 Subject: [PATCH 197/504] Added multer to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3a2fee1f..07ebe482 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "moment": "*", "moniker": "^0.1.2", "morgan": "^1.7.0", + "multer": "^1.2.1", "request": "*", "spark-protocol": "../spark-protocol", "ursa": "*", From 4193beb74cf23c9f64b6bc0e81853ea1b4bcb7cb Mon Sep 17 00:00:00 2001 From: John Date: Sun, 18 Dec 2016 20:48:24 -0800 Subject: [PATCH 198/504] Flow fixes --- src/decorators/allowUpload.js | 6 +++--- src/decorators/anonymous.js | 2 +- src/decorators/httpVerb.js | 4 ++-- src/decorators/route.js | 2 +- src/decorators/types.js | 2 +- src/repository/DeviceRepository.js | 1 + 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/decorators/allowUpload.js b/src/decorators/allowUpload.js index 121aa113..cff27734 100644 --- a/src/decorators/allowUpload.js +++ b/src/decorators/allowUpload.js @@ -8,13 +8,13 @@ export default ( fileName: string, maxCount: number = 0, ): Decorator => - (target: Controller, name: string, descriptor: Descriptor): Descriptor => { - const allowedUploads = target[name].allowedUploads || []; + (target: Controller, name: $Keys, descriptor: Descriptor): Descriptor => { + const allowedUploads = (target: any)[name].allowedUploads || []; allowedUploads.push({ maxCount, name: fileName, }); - target[name].allowedUploads = allowedUploads; + (target: any)[name].allowedUploads = allowedUploads; return descriptor; }; diff --git a/src/decorators/anonymous.js b/src/decorators/anonymous.js index c459541c..a66c468a 100644 --- a/src/decorators/anonymous.js +++ b/src/decorators/anonymous.js @@ -6,6 +6,6 @@ import type Controller from '../controllers/Controller'; /* eslint-disable no-param-reassign */ export default (): Decorator => (target: Controller, name: string, descriptor: Descriptor): Descriptor => { - target[name].anonymous = true; + (target: any)[name].anonymous = true; return descriptor; }; diff --git a/src/decorators/httpVerb.js b/src/decorators/httpVerb.js index b7aadfc1..bc1df0bb 100644 --- a/src/decorators/httpVerb.js +++ b/src/decorators/httpVerb.js @@ -5,7 +5,7 @@ import type Controller from '../controllers/Controller'; /* eslint-disable no-param-reassign */ export default (httpVerb: HttpVerb): Decorator => - (target: Controller, name: string, descriptor: Descriptor): Descriptor => { - target[name].httpVerb = httpVerb; + (target: Controller, name: $Keys, descriptor: Descriptor): Descriptor => { + (target: any)[name].httpVerb = httpVerb; return descriptor; }; diff --git a/src/decorators/route.js b/src/decorators/route.js index 551eec58..2cb850ce 100644 --- a/src/decorators/route.js +++ b/src/decorators/route.js @@ -6,6 +6,6 @@ import type Controller from '../controllers/Controller'; /* eslint-disable no-param-reassign */ export default (route: string): Decorator => (target: Controller, name: string, descriptor: Descriptor): Descriptor => { - target[name].route = route; + (target: any)[name].route = route; return descriptor; }; diff --git a/src/decorators/types.js b/src/decorators/types.js index 11dbc147..0fd09f2e 100644 --- a/src/decorators/types.js +++ b/src/decorators/types.js @@ -2,7 +2,7 @@ export type Decorator = ( target: TType, - name: string, + name: $Keys, descriptor: Descriptor, ) => Descriptor; diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 865d6fac..1b6b6d25 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -4,6 +4,7 @@ import type { File } from 'express'; import type { DeviceServer } from 'spark-protocol'; import type { Device, + DeviceAttributeRepository, DeviceAttributes, Repository, } from '../types'; From 40f181e4b9700a4c299fed6b914145fa543ef17c Mon Sep 17 00:00:00 2001 From: John Date: Sun, 18 Dec 2016 22:18:53 -0800 Subject: [PATCH 199/504] Working on claims flow --- src/app.js | 2 ++ src/controllers/DeviceClaimsController.js | 36 +++++++++++++++++++++++ src/repository/DeviceRepository.js | 10 ++++++- src/types.js | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/controllers/DeviceClaimsController.js diff --git a/src/app.js b/src/app.js index 042f8c99..c17cd4d8 100644 --- a/src/app.js +++ b/src/app.js @@ -25,6 +25,7 @@ import { // Routing import routeConfig from './RouteConfig'; +import DeviceClaimsController from './controllers/DeviceClaimsController'; import DevicesController from './controllers/DevicesController'; import ProvisioningController from './controllers/ProvisioningController'; import UsersController from './controllers/UsersController'; @@ -73,6 +74,7 @@ export default (settings: Settings, deviceServer: Object): $Application => { routeConfig( app, [ + new DeviceClaimsController(deviceRepository), new DevicesController(deviceRepository), new ProvisioningController(deviceRepository), new UsersController(settings.usersRepository), diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js new file mode 100644 index 00000000..70120e12 --- /dev/null +++ b/src/controllers/DeviceClaimsController.js @@ -0,0 +1,36 @@ +// @flow + +import type { Device, DeviceRepository } from '../types'; +import type { DeviceAPIType } from '../lib/deviceToAPI'; + + +import Controller from './Controller'; +import HttpError from '../lib/HttpError'; +import allowUpload from '../decorators/allowUpload'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; +import deviceToAPI from '../lib/deviceToAPI'; + +class DeviceClaimsController extends Controller { + _deviceRepository: DeviceRepository; + + constructor(deviceRepository: DeviceRepository) { + super(); + this._deviceRepository = deviceRepository; + } + + @httpVerb('post') + @route('/v1/device_claims') + async claimDevice(postBody: { id: string }): Promise<*> { + const claimCode = await this._deviceRepository.generateClaimCode( + this.user.id, + ); + const devices = await this._deviceRepository.getAll(this.user.id); + const deviceIDs = devices.map( + device => device.deviceID, + ); + return this.ok({claim_code: claimCode, device_ids: deviceIDs}); + } +} + +export default DeviceClaimsController; diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 1b6b6d25..98d97a60 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -9,6 +9,7 @@ import type { Repository, } from '../types'; +import crypto from 'crypto'; import Moniker from 'moniker'; import ursa from 'ursa'; import HttpError from '../lib/HttpError'; @@ -50,6 +51,12 @@ class DeviceRepository { return await this._deviceAttributeRepository.update(attributesToSave); }; + generateClaimCode = async (userID: string): Promise => { + // TODO - we should probably save this to a repository so we can use it in + // subsequent requests + return crypto.randomBytes(63).toString(); + }; + unclaimDevice = async ( deviceID: string, userID: string, @@ -127,7 +134,8 @@ class DeviceRepository { }; getAll = async (userID: string): Promise> => { - const devicesAttributes = await this._deviceAttributeRepository.getAll(userID); + const devicesAttributes = + await this._deviceAttributeRepository.getAll(userID); const devicePromises = devicesAttributes.map(async attributes => { const core = this._deviceServer.getCore(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent diff --git a/src/types.js b/src/types.js index d170eb54..30899435 100644 --- a/src/types.js +++ b/src/types.js @@ -132,6 +132,7 @@ export type DeviceRepository = { functionArguments: Object, ): Promise<*>, claimDevice(deviceID: string, userID: string): Promise, + generateClaimCode(userID: string): Promise, flashBinary(deviceID: string, files: File): Promise<*>, flashKnownApp(deviceID: string, app: string): Promise<*>, getAll(userID: string): Promise>, From b99f3d5436b55341bd3ea6646e7ee4e07b7829a5 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 20 Dec 2016 02:19:10 +0200 Subject: [PATCH 200/504] implement known_apps --- .gitignore | 1 + package.json | 2 +- src/app.js | 1 + src/controllers/DevicesController.js | 5 +++-- .../DeviceFirmwareFileRepository.js | 16 +++++++++++++++ src/repository/DeviceRepository.js | 20 ++++++++++++++++--- src/settings.js | 4 ++++ src/types.js | 2 ++ test/setup/settings.js | 4 ++++ 9 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 src/repository/DeviceFirmwareFileRepository.js diff --git a/.gitignore b/.gitignore index 7236f6da..43982fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ build/Release node_modules # Spark generated files/directories which contain secrets +known_apps core_keys users webhooks diff --git a/package.json b/package.json index 07ebe482..e616bec6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "build:clean": "rimraf ./build", "lint": "eslint -- .", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks --ignore known_apps", "start:prod": "npm run build && node ./build/main.js", "test": "ava --serial", "test:watch": "ava --watch --serial" diff --git a/src/app.js b/src/app.js index c17cd4d8..62893445 100644 --- a/src/app.js +++ b/src/app.js @@ -67,6 +67,7 @@ export default (settings: Settings, deviceServer: Object): $Application => { const deviceRepository = new DeviceRepository( deviceAttributeRepository, + settings.deviceFirmwareRepository, new DeviceKeyFileRepository(settings.coreKeysDir), deviceServer, ); diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index b631def1..05c800b9 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -106,13 +106,14 @@ class DevicesController extends Controller { return this.ok({ name: updatedAttributes.name, ok: true }); } - // TODO not implemented yet // 2 flash device with known app if (postBody.app_id) { - this._deviceRepository.flashKnownApp( + await this._deviceRepository.flashKnownApp( deviceID, + this.user.id, postBody.app_id, ); + return this.ok({ id: deviceID, status: 'Update started' }); } diff --git a/src/repository/DeviceFirmwareFileRepository.js b/src/repository/DeviceFirmwareFileRepository.js new file mode 100644 index 00000000..7da4deac --- /dev/null +++ b/src/repository/DeviceFirmwareFileRepository.js @@ -0,0 +1,16 @@ +// @flow + +import { FileManager } from 'spark-protocol'; + +class DeviceFirmwareFileRepository { + _fileManager: FileManager; + + constructor(path: string) { + this._fileManager = new FileManager(path, false); + } + + getByName = (appName: string): ?Buffer => + this._fileManager.getFileBuffer(`${appName}.bin`); +} + +export default DeviceFirmwareFileRepository; diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 98d97a60..0f1783ca 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -8,6 +8,7 @@ import type { DeviceAttributes, Repository, } from '../types'; +import type DeviceFirmwareRepository from './DeviceFirmwareFileRepository'; import crypto from 'crypto'; import Moniker from 'moniker'; @@ -18,15 +19,18 @@ const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); class DeviceRepository { _deviceAttributeRepository: DeviceAttributeRepository; + _deviceFirmwareRepository: DeviceFirmwareRepository; _deviceKeyRepository: Repository; _deviceServer: DeviceServer; constructor( deviceAttributeRepository: DeviceAttributeRepository, + deviceFirmwareRepository: DeviceFirmwareRepository, deviceKeyRepository: Repository, deviceServer: DeviceServer, ) { this._deviceAttributeRepository = deviceAttributeRepository; + this._deviceFirmwareRepository = deviceFirmwareRepository; this._deviceKeyRepository = deviceKeyRepository; this._deviceServer = deviceServer; } @@ -235,9 +239,19 @@ class DeviceRepository { flashKnownApp = async ( deviceID: string, - app: string, + userID: string, + appName: string, ) => { - // TODO not implemented yet + if (!this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + throw new HttpError('No device found', 404); + } + + const knownFirmware = this._deviceFirmwareRepository.getByName(appName); + + if (!knownFirmware) { + throw new HttpError(`No firmware ${appName} found`); + } + const core = this._deviceServer.getCore(deviceID); if (!core) { throw new HttpError('Could not get device for ID', 404); @@ -245,7 +259,7 @@ class DeviceRepository { const result = await core.onApiMessage( deviceID, - { cmd: 'FlashKnown', app }, + { cmd: 'UFlash', args: { data: knownFirmware } }, ); if (result.error) { diff --git a/src/settings.js b/src/settings.js index e5250486..e4b49b55 100644 --- a/src/settings.js +++ b/src/settings.js @@ -20,6 +20,7 @@ */ import path from 'path'; +import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository'; import WebhookFileRepository from './repository/WebhookFileRepository'; import UsersFileRepository from './repository/UserFileRepository'; @@ -35,6 +36,9 @@ export default { logRequests: true, maxHooksPerDevice: 10, maxHooksPerUser: 20, + deviceFirmwareRepository: new DeviceFirmwareFileRepository( + path.join(__dirname, 'known_apps'), + ), webhookRepository: new WebhookFileRepository( path.join(__dirname, 'webhooks'), ), diff --git a/src/types.js b/src/types.js index 30899435..2a3172ce 100644 --- a/src/types.js +++ b/src/types.js @@ -1,6 +1,7 @@ // @flow import type { File } from 'express'; +import type DeviceFirmwareRepository from './repository/DeviceFirmwareFileRepository'; export type Webhook = WebhookMutator & { created_at: Date, @@ -106,6 +107,7 @@ export type Settings = { coreRequestTimeout: number, coreSignalTimeout: number, cryptoSalt: string, + deviceFirmwareRepository: DeviceFirmwareRepository, HOST: string, isCoreOnlineTimeout: number, loginRoute: string, diff --git a/test/setup/settings.js b/test/setup/settings.js index 25e69536..d9a8262a 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -1,6 +1,7 @@ // @flow import path from 'path'; +import DeviceFirmwareFileRepository from '../../src/repository/DeviceFirmwareFileRepository'; import WebhookFileRepository from '../../src/repository/WebhookFileRepository'; import UsersFileRepository from '../../src/repository/UserFileRepository'; import { DeviceAttributeFileRepository, DeviceKeyFileRepository } from 'spark-protocol'; @@ -20,6 +21,9 @@ export default { deviceAttributeRepository: new DeviceAttributeFileRepository( path.join(__dirname, '../__test_data__/core_keys'), ), + deviceFirmwareRepository: new DeviceFirmwareFileRepository( + path.join(__dirname, 'known_apps'), + ), deviceKeyFileRepository: new DeviceKeyFileRepository( path.join(__dirname, '../__test_data__/core_keys'), ), From 7bf25b296ea75060ed7b86d1f850c3698a55bb24 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 23 Dec 2016 02:16:06 +0200 Subject: [PATCH 201/504] working on events. partly move eventViews to controller with the same implementation. --- src/RouteConfig.js | 5 + src/app.js | 5 +- src/controllers/EventsController.js | 168 ++++++++++++++++++++++++++++ src/views/EventViews001.js | 10 +- src/views/api_v1.js | 3 + 5 files changed, 183 insertions(+), 8 deletions(-) diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 5d375c49..6bc38335 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -15,6 +15,8 @@ import multer from 'multer'; import OAuthModel from './OAuthModel'; import HttpError from './lib/HttpError'; +import eventsV1 from './views/EventViews001'; + // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => (request: $Request, response: $Response, next: NextFunction) => { @@ -54,6 +56,9 @@ export default ( app.all('/v1/devices*', oauth.authenticate()); app.all('/v1/provisioning*', oauth.authenticate()); app.all('/v1/events*', oauth.authenticate()); + + eventsV1.loadViews(app); + // end temporary const injectFilesMiddleware = multer(); diff --git a/src/app.js b/src/app.js index 62893445..416a9135 100644 --- a/src/app.js +++ b/src/app.js @@ -27,6 +27,7 @@ import { import routeConfig from './RouteConfig'; import DeviceClaimsController from './controllers/DeviceClaimsController'; import DevicesController from './controllers/DevicesController'; +import EventsController from './controllers/EventsController'; import ProvisioningController from './controllers/ProvisioningController'; import UsersController from './controllers/UsersController'; import WebhooksController from './controllers/WebhooksController'; @@ -77,6 +78,7 @@ export default (settings: Settings, deviceServer: Object): $Application => { [ new DeviceClaimsController(deviceRepository), new DevicesController(deviceRepository), + new EventsController(), new ProvisioningController(deviceRepository), new UsersController(settings.usersRepository), new WebhooksController(settings.webhookRepository), @@ -84,8 +86,5 @@ export default (settings: Settings, deviceServer: Object): $Application => { settings, ); - eventsV1.loadViews(app); - api.loadViews(app); - return app; }; diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index 71f4401c..3a72e472 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -1,9 +1,177 @@ // @flow +import moment from 'moment'; import Controller from './Controller'; +import CoreController from '../lib/CoreController'; +import route from '../decorators/route'; +import httpVerb from '../decorators/httpVerb'; + +import v1Api from '../views/api_v1'; +import logger from '../lib/logger'; class EventsController extends Controller { + _aliveInterval: ?string; + _lastMessage: ?Date; + _socket: ?Object; + + _cleanup() { + try { + if (this._socket) { + this._socket.close(); + this._socket = null; + } + + if (this.response && this.response.socket) { + this.response.socket.end(); + this.response.end(); + } + + if (this._aliveInterval) { + clearInterval(this._aliveInterval); + this._aliveInterval = null; + } + } catch (error) { + logger.error(`pipeEvents cleanup error: ${error}`); + } + } + + _keepAlive() { + if (((new Date()) - this._lastMessage) >= 9000) { + this._lastMessage = new Date(); + this.response.write('\n'); + this._checkSocket(); + } + } + + _checkSocket() { + try { + if (!this._socket) { + this._cleanup(); + return false; + } + + if (this.response.socket.destroyed) { + logger.log('Socket destroyed, cleaning up Event listener'); + this._cleanup(); + return false; + } + + return true; + } catch (error) { + logger.error(`pipeEvents - error checking socket ${error}`); + return false; + } + } + + _writeEventGen() { + return ( + name: string, + data: ?Object, + ttl: ?number, + publishedAt: ?Date, + coreid: ? string, + ) => { + // if (filterCoreId && (filterCoreId !== coreid)) { + // return; + // } + if (!this._checkSocket()) { + return; + } + + try { + this._lastMessage = new Date(); + + const eventData = { + coreid: coreid || null, + data: data || null, + published_at: publishedAt || null, + ttl: ttl || null, + }; + + this.response.write(`event: ${name} \n`); + this.response.write(`data: ${JSON.stringify(eventData)} \n\n`); + } catch (error) { + logger.error(`pipeEvents - write error: ${error}`); + } + }; + } + + _pipeEvents() { + try { + this.request.socket.setNoDelay(); + console.log('headers sent', this.response.headersSent); + this.response.writeHead(200, { + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + }); + + console.log('headers sent', this.response.headersSent); + + this.response.write(':ok\n\n'); + + this._aliveInterval = setInterval(this._keepAlive, 3000); + + if (this._socket) { + this._socket.on('public', this._writeEventGen(true)); + this._socket.on('private', this._writeEventGen(false)); + } + + this.request.on('close', this._cleanup); + this.request.on('end', this._cleanup); + this.response.on('close', this._cleanup); + this.response.on('finish', this._cleanup); + } catch(error) { + console.log('pipeEvents error', error.message); + throw error; + } + } + + @httpVerb('get') + @route('/v1/events/:eventPrefix?') + async getEvents(eventPrefix: string) { + try { + // todo if eventPrefix doesn't exist, in getEvents args it becomes empty body object + // need to fix this in routeConfig somehow, or do 2 endpoints + const prefix = Object.keys(eventPrefix).length === 0 ? '' : eventPrefix; + + this._socket = new CoreController(v1Api.getSocketID(this.user.id)); + this._socket.subscribe(false, prefix, this.user.id); + + await this._pipeEvents(); + // return this.ok(); + } catch (error) { + throw error; + } + } + + @httpVerb('post') + @route('/v1/devices/events') + async sendEvent(postBody: { + name: string, + data: Object, + private: boolean, + ttl: number, + }): Promise<*> { + const { data, name, ttl } = postBody; + + this._socket = new CoreController(v1Api.getSocketID(this.user.id)); + + const success = this._socket.sendEvent( + postBody.private, + name, + this.user.id, + data, + ttl, + moment().toISOString(), + // todo according to sendEvent() it should be core id, but they pass userID here + // actually seems in current implementation it doesn't affect anything anyways. + this.user.id, + ); + this._socket.close(); + return this.ok({ ok: success }); + } } export default EventsController; diff --git a/src/views/EventViews001.js b/src/views/EventViews001.js index 697f31a7..cb13eb67 100644 --- a/src/views/EventViews001.js +++ b/src/views/EventViews001.js @@ -36,12 +36,12 @@ var EventsApi = { // GET /v1/devices/events[/:event_name] // GET /v1/devices/:device_id/events[/:event_name] - app.get('/v1/events', EventsApi.get_events); - app.get('/v1/events/:event_name', EventsApi.get_events); + //app.get('/v1/events', EventsApi.get_events); + //app.get('/v1/events/:event_name', EventsApi.get_events); - app.get('/v1/devices/events', EventsApi.get_my_events); - app.post('/v1/devices/events', EventsApi.send_an_event); - app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); + // app.get('/v1/devices/events', EventsApi.get_my_events); + // app.post('/v1/devices/events', EventsApi.send_an_event); + // app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); diff --git a/src/views/api_v1.js b/src/views/api_v1.js index da9bc32e..7a313059 100644 --- a/src/views/api_v1.js +++ b/src/views/api_v1.js @@ -70,6 +70,9 @@ var Api = { }, getUserID: function (res) { + if(!res.locals) { + return null; + } if (!res.locals.oauth) { logger.log("User obj was empty"); return null; From 90998cb26dd37f7c297347b56fbce1e75b3ca49a Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 22 Dec 2016 17:28:17 -0800 Subject: [PATCH 202/504] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ae9e11a8..841b435d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ npm install node main.js ``` +**Windows Setup** +You'll need to install Python 2.7 and OpenSSL 1.0.2 or older. +*The newer version doesn't have the lib files needed to build the project*. +[Python Download](https://www.python.org/downloads/) +[OpenSSL Download](http://slproweb.com/products/Win32OpenSSL.html) + [Raspberry pi Quick Install](doc/raspberryPi.md) From ec836d96d1b5b1370b85e5508af89fb525358547 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 24 Dec 2016 03:49:04 +0200 Subject: [PATCH 203/504] working on events. start doing EventManager. --- src/app.js | 5 +- src/controllers/EventsController.js | 213 ++++++++++------------------ src/lib/CoreController.js | 18 +-- src/managers/EventManager.js | 46 ++++++ src/types.js | 7 + 5 files changed, 134 insertions(+), 155 deletions(-) create mode 100644 src/managers/EventManager.js diff --git a/src/app.js b/src/app.js index 416a9135..519bfe6c 100644 --- a/src/app.js +++ b/src/app.js @@ -23,6 +23,9 @@ import { DeviceKeyFileRepository, } from 'spark-protocol'; +// Managers +import EventManager from './managers/EventManager'; + // Routing import routeConfig from './RouteConfig'; import DeviceClaimsController from './controllers/DeviceClaimsController'; @@ -78,7 +81,7 @@ export default (settings: Settings, deviceServer: Object): $Application => { [ new DeviceClaimsController(deviceRepository), new DevicesController(deviceRepository), - new EventsController(), + new EventsController(new EventManager()), new ProvisioningController(deviceRepository), new UsersController(settings.usersRepository), new WebhooksController(settings.webhookRepository), diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index 3a72e472..c315cc42 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -1,176 +1,107 @@ // @flow -import moment from 'moment'; +import type { Event } from '../types'; +import type EventManager from '../managers/EventManager'; + import Controller from './Controller'; -import CoreController from '../lib/CoreController'; import route from '../decorators/route'; import httpVerb from '../decorators/httpVerb'; - -import v1Api from '../views/api_v1'; import logger from '../lib/logger'; class EventsController extends Controller { - _aliveInterval: ?string; + _aliveInterval: ?number; + _eventManager: EventManager; _lastMessage: ?Date; - _socket: ?Object; - _cleanup() { - try { - if (this._socket) { - this._socket.close(); - this._socket = null; - } - - if (this.response && this.response.socket) { - this.response.socket.end(); - this.response.end(); - } - - if (this._aliveInterval) { - clearInterval(this._aliveInterval); - this._aliveInterval = null; - } - } catch (error) { - logger.error(`pipeEvents cleanup error: ${error}`); - } + constructor(eventManager: EventManager) { + super(); + + this._eventManager = eventManager; } _keepAlive() { if (((new Date()) - this._lastMessage) >= 9000) { this._lastMessage = new Date(); this.response.write('\n'); - this._checkSocket(); } } - _checkSocket() { - try { - if (!this._socket) { - this._cleanup(); - return false; - } - - if (this.response.socket.destroyed) { - logger.log('Socket destroyed, cleaning up Event listener'); - this._cleanup(); - return false; - } - - return true; - } catch (error) { - logger.error(`pipeEvents - error checking socket ${error}`); - return false; - } - } - - _writeEventGen() { - return ( - name: string, - data: ?Object, - ttl: ?number, - publishedAt: ?Date, - coreid: ? string, - ) => { - // if (filterCoreId && (filterCoreId !== coreid)) { - // return; - // } - if (!this._checkSocket()) { - return; - } - - try { - this._lastMessage = new Date(); - - const eventData = { - coreid: coreid || null, - data: data || null, - published_at: publishedAt || null, - ttl: ttl || null, - }; - - this.response.write(`event: ${name} \n`); - this.response.write(`data: ${JSON.stringify(eventData)} \n\n`); - } catch (error) { - logger.error(`pipeEvents - write error: ${error}`); - } - }; - } - - _pipeEvents() { + _pipeEvent = (response) => ( + isPublic: boolean, + name: string, + data: ?Object, + ttl: ?number, + publishedAt: ?Date, + coreId: ?string, + ) => { try { - this.request.socket.setNoDelay(); - console.log('headers sent', this.response.headersSent); - this.response.writeHead(200, { - 'Cache-Control': 'no-cache', - Connection: 'keep-alive', - 'Content-Type': 'text/event-stream', - }); - - console.log('headers sent', this.response.headersSent); - - this.response.write(':ok\n\n'); - - this._aliveInterval = setInterval(this._keepAlive, 3000); + this._lastMessage = new Date(); - if (this._socket) { - this._socket.on('public', this._writeEventGen(true)); - this._socket.on('private', this._writeEventGen(false)); - } + const eventData = { + coreid: coreId || null, + data: data || null, + published_at: publishedAt || null, + ttl: ttl || null, + }; - this.request.on('close', this._cleanup); - this.request.on('end', this._cleanup); - this.response.on('close', this._cleanup); - this.response.on('finish', this._cleanup); - } catch(error) { - console.log('pipeEvents error', error.message); + response.write(`event: ${name} \n`); + response.write(`data: ${JSON.stringify(eventData)}\n`); + } catch (error) { + logger.error(`pipeEvents - write error: ${error}`); throw error; } - } + }; @httpVerb('get') - @route('/v1/events/:eventPrefix?') - async getEvents(eventPrefix: string) { - try { - // todo if eventPrefix doesn't exist, in getEvents args it becomes empty body object - // need to fix this in routeConfig somehow, or do 2 endpoints - const prefix = Object.keys(eventPrefix).length === 0 ? '' : eventPrefix; + @route('/v1/events/:eventName?') + async getEvents(eventName: string) { + this.response.set({ + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + }); + + this._aliveInterval = setInterval(this._keepAlive, 3000); + + // todo if eventName doesn't exist, in getEvents args it becomes empty body object + // need to fix this in routeConfig somehow, or do 2 endpoints + const evName = Object.keys(eventName).length === 0 ? '' : eventName; + + this._eventManager.subscribe( + evName, + this.user.id, + null, + this._pipeEvent(this.response), + ); - this._socket = new CoreController(v1Api.getSocketID(this.user.id)); - this._socket.subscribe(false, prefix, this.user.id); + const closeStream = new Promise((resolve: () => void) => { + this.request.on('close', () => { + this._eventManager.unsubscribe(evName, this.user.id, null); + resolve(); + }); + this.request.on('end', () => { + this._eventManager.unsubscribe(evName, this.user.id, null); + resolve(); + }); + this.response.on('finish', () => { + this._eventManager.unsubscribe(evName, this.user.id, null); + resolve(); + }); + this.response.on('end', () => { + this._eventManager.unsubscribe(evName, this.user.id, null); + resolve(); + }); + }); - await this._pipeEvents(); - // return this.ok(); - } catch (error) { - throw error; - } + await closeStream; + return this.ok(); } @httpVerb('post') @route('/v1/devices/events') - async sendEvent(postBody: { - name: string, - data: Object, - private: boolean, - ttl: number, - }): Promise<*> { - const { data, name, ttl } = postBody; - - this._socket = new CoreController(v1Api.getSocketID(this.user.id)); - - const success = this._socket.sendEvent( - postBody.private, - name, - this.user.id, - data, - ttl, - moment().toISOString(), - // todo according to sendEvent() it should be core id, but they pass userID here - // actually seems in current implementation it doesn't affect anything anyways. - this.user.id, - ); - - this._socket.close(); - return this.ok({ ok: success }); + async sendEvent(event: Event): Promise<*> { + await this._eventManager.sendEvent(this.user.id, event); + return this.ok({ ok: true }); } } diff --git a/src/lib/CoreController.js b/src/lib/CoreController.js index d2b952b3..0b2cf7c3 100644 --- a/src/lib/CoreController.js +++ b/src/lib/CoreController.js @@ -131,20 +131,12 @@ CoreController.prototype = { core.on(that.socketID, handler); }, - subscribe: function (isPublic, name, userid,coreid) { - global.publisher.subscribe(name, userid, coreid, this); + subscribe: function (name, userid, coreid, eventHandler) { + global.publisher.subscribe(name, userid, coreid, this, eventHandler); }, - unsubscribe: function (isPublic, name, userid) { - if (userid && (userid !== "")) { - name = userid + "/" + name; - } - -// if (!sock) { -// return; -// } - - global.publisher.unsubscribe(name, this); + unsubscribe: function (name, userId, coreId) { + global.publisher.unsubscribe(name, userId, coreId, this); }, //isPublic, obj.name, obj.userid, obj.data, obj.ttl, obj.published_at @@ -174,7 +166,7 @@ CoreController.prototype = { }, close: function () { - + global.publisher.close(); } }; diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js new file mode 100644 index 00000000..ca32caba --- /dev/null +++ b/src/managers/EventManager.js @@ -0,0 +1,46 @@ +// @flow + +import type { Event } from '../types'; + +import moment from 'moment'; +import CoreController from '../lib/CoreController'; + +class EventManager { + _socket: ?Object; + + subscribe = (eventName: string, userID: string, coreID: string, callback): void => { + this._socket = new CoreController(); + this._socket.subscribe(eventName, userID, coreID, callback); + }; + + unsubscribe = (eventName: string, userID: string, coreID: string) => { + if (!this._socket) { + return; + } + this._socket.unsubscribe(eventName, userID, coreID); + this._socket.close(); + this._socket = null; + }; + + sendEvent = async (userID: string, event: Event): Promise => { + const socket = new CoreController(); + + // todo make sendEvent() async + const result = await socket.sendEvent( + event.private, + event.name, + userID, + event.data, + event.ttl, + moment().toISOString(), + // todo according to sendEvent() it should be core id, but they pass userID here + // actually seems in current implementation it doesn't affect anything anyways. + userID, + ); + // todo send event errors handling + // todo make close() async + await socket.close(); + }; +} + +export default EventManager; diff --git a/src/types.js b/src/types.js index 2a3172ce..e721977f 100644 --- a/src/types.js +++ b/src/types.js @@ -46,6 +46,13 @@ export type DeviceAttributes = { timestamp: Date, }; +export type Event = { + data: Object, + name: string, + private: boolean, + ttl: number, +}; + export type GrantType = 'bearer_token'| 'password'| From 97e3b271b8b726f160ad3e6a34fb95fd6d67f982 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 02:55:25 +0200 Subject: [PATCH 204/504] add Event/ EventData types --- src/types.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/types.js b/src/types.js index e721977f..e950bca2 100644 --- a/src/types.js +++ b/src/types.js @@ -46,10 +46,15 @@ export type DeviceAttributes = { timestamp: Date, }; -export type Event = { - data: Object, +export type Event = EventData & { + publishedAt: Date, +}; + +export type EventData = { + data: ?Object, + deviceID?: ?string, + isPublic: boolean, name: string, - private: boolean, ttl: number, }; From d3c7536eaa63b88c091420fca101f11c20c1451e Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 28 Dec 2016 02:58:51 +0200 Subject: [PATCH 205/504] create ServerSentEvents decorator/ middleware; fix optional params for routes. --- flow-typed/npm/express_v4.x.x.js | 2 +- src/RouteConfig.js | 43 +++++++++++++++++++----------- src/decorators/serverSentEvents.js | 11 ++++++++ 3 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 src/decorators/serverSentEvents.js diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 5d49c478..52b2ac9b 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -78,7 +78,7 @@ declare type express$SendFileOptions = { dotfiles?: 'allow' | 'deny' | 'ignore' }; -declare class express$Response extends http$ClientRequest mixins express$RequestResponseBase { +declare class express$Response extends http$ServerResponse mixins express$RequestResponseBase { headersSent: boolean; locals: {[name: string]: mixed}; append(field: string, value?: string): this; diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 6bc38335..f300a750 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -15,8 +15,6 @@ import multer from 'multer'; import OAuthModel from './OAuthModel'; import HttpError from './lib/HttpError'; -import eventsV1 from './views/EventViews001'; - // TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => (request: $Request, response: $Response, next: NextFunction) => { @@ -37,6 +35,23 @@ const injectUserMiddleware = (): Middleware => } next(); }; + + +const serverSentEventsMiddleware = (): Middleware => + (request: $Request, response: $Response, next: NextFunction) => { + request.socket.setNoDelay(); + response.writeHead( + 200, + { + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + }, + ); + + next(); + }; + const defaultMiddleware = (request: $Request, response: $Response, next: NextFunction): mixed => next(); @@ -50,17 +65,6 @@ export default ( allowBearerTokensInQueryString: true, model: new OAuthModel(settings.usersRepository), }); - - // TODO this is temporary authentication for api_v1 and events routes - // until we move them in our controllers: - app.all('/v1/devices*', oauth.authenticate()); - app.all('/v1/provisioning*', oauth.authenticate()); - app.all('/v1/events*', oauth.authenticate()); - - eventsV1.loadViews(app); - - // end temporary - const injectFilesMiddleware = multer(); app.post(settings.loginRoute, oauth.token()); @@ -70,7 +74,14 @@ export default ( (Object.getPrototypeOf(controller): any), ).forEach((functionName: string) => { const mappedFunction = (controller: any)[functionName]; - const { allowedUploads, anonymous, httpVerb, route } = mappedFunction; + const { + allowedUploads, + anonymous, + httpVerb, + route, + serverSentEvents, + } = mappedFunction; + if (!httpVerb) { return; } @@ -78,6 +89,7 @@ export default ( (app: any)[httpVerb]( route, maybe(oauth.authenticate(), !anonymous), + maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), allowedUploads ? injectFilesMiddleware.fields(allowedUploads) @@ -87,8 +99,7 @@ export default ( (argumentName: string): string => argumentName.replace(':', ''), ); const values = argumentNames - .map((argument: string): string => request.params[argument]) - .filter((value: ?Object): boolean => value !== undefined); + .map((argument: string): string => request.params[argument]); const controllerContext = Object.create(controller); controllerContext.request = request; diff --git a/src/decorators/serverSentEvents.js b/src/decorators/serverSentEvents.js new file mode 100644 index 00000000..22a9ae92 --- /dev/null +++ b/src/decorators/serverSentEvents.js @@ -0,0 +1,11 @@ +// @flow + +import type { Decorator, Descriptor } from './types'; +import type Controller from '../controllers/Controller'; + +/* eslint-disable no-param-reassign */ +export default (): Decorator => + (target: Controller, name: string, descriptor: Descriptor): Descriptor => { + (target: any)[name].serverSentEvents = true; + return descriptor; + }; From 01ee355ffa4462d0b32ff5d604c9891ae257b8bf Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 03:02:30 +0200 Subject: [PATCH 206/504] implement eventsController/ EventsManager --- src/app.js | 19 +++- src/controllers/EventsController.js | 139 +++++++++++++++------------- src/lib/eventToApi.js | 19 ++++ src/main.js | 36 ++++--- src/managers/EventManager.js | 92 ++++++++++-------- 5 files changed, 183 insertions(+), 122 deletions(-) create mode 100644 src/lib/eventToApi.js diff --git a/src/app.js b/src/app.js index 519bfe6c..cb62d703 100644 --- a/src/app.js +++ b/src/app.js @@ -7,15 +7,13 @@ import type { Middleware, NextFunction, } from 'express'; +import type { EventPublisher, DeviceServer } from 'spark-protocol'; import type { Settings } from './types'; import bodyParser from 'body-parser'; import express from 'express'; import morgan from 'morgan'; -import api from './views/api_v1'; -import eventsV1 from './views/EventViews001'; - // Repositories import DeviceRepository from './repository/DeviceRepository'; import { @@ -35,7 +33,11 @@ import ProvisioningController from './controllers/ProvisioningController'; import UsersController from './controllers/UsersController'; import WebhooksController from './controllers/WebhooksController'; -export default (settings: Settings, deviceServer: Object): $Application => { +export default ( + settings: Settings, + deviceServer: DeviceServer, + eventPublisher: EventPublisher, +): $Application => { const app = express(); const setCORSHeaders: Middleware = ( @@ -76,12 +78,19 @@ export default (settings: Settings, deviceServer: Object): $Application => { deviceServer, ); + const eventManager = new EventManager( + deviceAttributeRepository, + eventPublisher, + ); + + // to avoid routes collisions eventController should be placed + // before DevicesController routeConfig( app, [ new DeviceClaimsController(deviceRepository), + new EventsController(eventManager), new DevicesController(deviceRepository), - new EventsController(new EventManager()), new ProvisioningController(deviceRepository), new UsersController(settings.usersRepository), new WebhooksController(settings.webhookRepository), diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index c315cc42..7eee27e7 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -1,17 +1,17 @@ // @flow -import type { Event } from '../types'; +import type { Event, EventData } from '../types'; import type EventManager from '../managers/EventManager'; import Controller from './Controller'; import route from '../decorators/route'; import httpVerb from '../decorators/httpVerb'; +import serverSentEvents from '../decorators/serverSentEvents'; import logger from '../lib/logger'; +import eventToApi from '../lib/eventToApi'; class EventsController extends Controller { - _aliveInterval: ?number; _eventManager: EventManager; - _lastMessage: ?Date; constructor(eventManager: EventManager) { super(); @@ -19,90 +19,101 @@ class EventsController extends Controller { this._eventManager = eventManager; } - _keepAlive() { - if (((new Date()) - this._lastMessage) >= 9000) { - this._lastMessage = new Date(); - this.response.write('\n'); - } + _closeStream(subscriptionID: string): Promise { + return new Promise((resolve: () => void) => { + // TODO i'm not sure if we need all 4 listens here + this.request.on('close', () => { + this._eventManager.unsubscribe(subscriptionID); + resolve(); + }); + this.request.on('end', () => { + this._eventManager.unsubscribe(subscriptionID); + resolve(); + }); + this.response.on('finish', () => { + this._eventManager.unsubscribe(subscriptionID); + resolve(); + }); + this.response.on('end', () => { + this._eventManager.unsubscribe(subscriptionID); + resolve(); + }); + }); } - _pipeEvent = (response) => ( - isPublic: boolean, - name: string, - data: ?Object, - ttl: ?number, - publishedAt: ?Date, - coreId: ?string, - ) => { + _pipeEvent(event: Event) { try { - this._lastMessage = new Date(); - - const eventData = { - coreid: coreId || null, - data: data || null, - published_at: publishedAt || null, - ttl: ttl || null, - }; - - response.write(`event: ${name} \n`); - response.write(`data: ${JSON.stringify(eventData)}\n`); + this.response.write(`event: ${event.name} \n\n`); + this.response.write(`data: ${JSON.stringify(eventToApi(event))}\n\n`); } catch (error) { logger.error(`pipeEvents - write error: ${error}`); throw error; } - }; + } @httpVerb('get') @route('/v1/events/:eventName?') - async getEvents(eventName: string) { - this.response.set({ - 'Cache-Control': 'no-cache', - Connection: 'keep-alive', - 'Content-Type': 'text/event-stream', - }); - - this._aliveInterval = setInterval(this._keepAlive, 3000); + @serverSentEvents() + async getEvents(eventName: ?string): Promise<*> { + const subscriptionID = this._eventManager.subscribe( + eventName, + this._pipeEvent.bind(this), + ); - // todo if eventName doesn't exist, in getEvents args it becomes empty body object - // need to fix this in routeConfig somehow, or do 2 endpoints - const evName = Object.keys(eventName).length === 0 ? '' : eventName; + await this._closeStream(subscriptionID); + return this.ok(); + } - this._eventManager.subscribe( - evName, + @httpVerb('get') + @route('/v1/devices/events/:eventName?') + @serverSentEvents() + async getMyEvents(eventName: ?string): Promise<*> { + const subscriptionID = this._eventManager.subscribe( + eventName, + this._pipeEvent.bind(this), this.user.id, - null, - this._pipeEvent(this.response), ); - const closeStream = new Promise((resolve: () => void) => { - this.request.on('close', () => { - this._eventManager.unsubscribe(evName, this.user.id, null); - resolve(); - }); - this.request.on('end', () => { - this._eventManager.unsubscribe(evName, this.user.id, null); - resolve(); - }); - this.response.on('finish', () => { - this._eventManager.unsubscribe(evName, this.user.id, null); - resolve(); - }); - this.response.on('end', () => { - this._eventManager.unsubscribe(evName, this.user.id, null); - resolve(); - }); - }); + await this._closeStream(subscriptionID); + return this.ok(); + } + + @httpVerb('get') + @route('/v1/devices/:deviceID/events/:eventName?/') + @serverSentEvents() + async getDeviceEvents(deviceID: string, eventName: ?string): Promise<*> { + const subscriptionID = this._eventManager.subscribe( + eventName, + this._pipeEvent.bind(this), + this.user.id, + deviceID, + ); - await closeStream; + await this._closeStream(subscriptionID); return this.ok(); } @httpVerb('post') @route('/v1/devices/events') - async sendEvent(event: Event): Promise<*> { - await this._eventManager.sendEvent(this.user.id, event); + async publish(postBody: { + name: string, + data: ?Object, + private: boolean, + ttl: number, + }): Promise<*> { + const eventData: EventData = { + data: postBody.data, + isPublic: !postBody.private, + name: postBody.name, + ttl: postBody.ttl, + }; + + await this._eventManager.publish(eventData); return this.ok({ ok: true }); } } export default EventsController; + + +// todo TEST \ No newline at end of file diff --git a/src/lib/eventToApi.js b/src/lib/eventToApi.js new file mode 100644 index 00000000..043d3a2d --- /dev/null +++ b/src/lib/eventToApi.js @@ -0,0 +1,19 @@ +// @flow + +import type { Event } from '../types'; + +export type EventAPIType = {| + coreid: ?string, + data: ?Object, + published_at: Date, + ttl: number, + |}; + +const eventToApi = (event: Event): EventAPIType => ({ + coreid: event.deviceID || null, + data: event.data || null, + published_at: event.publishedAt, + ttl: event.ttl, +}); + +export default eventToApi; diff --git a/src/main.js b/src/main.js index 16c3f5ae..f687b902 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,7 @@ import { DeviceAttributeFileRepository, DeviceServer, + EventPublisher, ServerConfigFileRepository, } from 'spark-protocol'; import utilities from './lib/utilities'; @@ -26,25 +27,30 @@ process.on('uncaughtException', (exception: Error) => { logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); -const deviceServer = new DeviceServer({ - coreKeysDir: settings.coreKeysDir, - deviceAttributeRepository: new DeviceAttributeFileRepository( - settings.coreKeysDir, - ), - host: settings.HOST, - port: settings.PORT, - serverConfigRepository: new ServerConfigFileRepository( - settings.serverKeyFile, - ), - serverKeyFile: settings.serverKeyFile, - serverKeyPassEnvVar: settings.serverKeyPassEnvVar, - serverKeyPassFile: settings.serverKeyPassFile, -}); +const eventPublisher = new EventPublisher(); + +const deviceServer = new DeviceServer( + { + coreKeysDir: settings.coreKeysDir, + deviceAttributeRepository: new DeviceAttributeFileRepository( + settings.coreKeysDir, + ), + host: settings.HOST, + port: settings.PORT, + serverConfigRepository: new ServerConfigFileRepository( + settings.serverKeyFile, + ), + serverKeyFile: settings.serverKeyFile, + serverKeyPassEnvVar: settings.serverKeyPassEnvVar, + serverKeyPassFile: settings.serverKeyPassFile, + }, + eventPublisher, +); global.server = deviceServer; deviceServer.start(); -const app = createApp(settings, deviceServer); +const app = createApp(settings, deviceServer, eventPublisher); app.listen( NODE_PORT, diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js index ca32caba..db522d9b 100644 --- a/src/managers/EventManager.js +++ b/src/managers/EventManager.js @@ -1,46 +1,62 @@ // @flow -import type { Event } from '../types'; - -import moment from 'moment'; -import CoreController from '../lib/CoreController'; +import type { EventPublisher } from 'spark-protocol'; +import type { DeviceAttributeRepository, Event, EventData } from '../types'; class EventManager { - _socket: ?Object; - - subscribe = (eventName: string, userID: string, coreID: string, callback): void => { - this._socket = new CoreController(); - this._socket.subscribe(eventName, userID, coreID, callback); - }; - - unsubscribe = (eventName: string, userID: string, coreID: string) => { - if (!this._socket) { - return; - } - this._socket.unsubscribe(eventName, userID, coreID); - this._socket.close(); - this._socket = null; - }; - - sendEvent = async (userID: string, event: Event): Promise => { - const socket = new CoreController(); - - // todo make sendEvent() async - const result = await socket.sendEvent( - event.private, - event.name, - userID, - event.data, - event.ttl, - moment().toISOString(), - // todo according to sendEvent() it should be core id, but they pass userID here - // actually seems in current implementation it doesn't affect anything anyways. - userID, + _eventPublisher: EventPublisher; + _deviceAttributeRepository: DeviceAttributeRepository; + + constructor( + deviceAttributeRepository: DeviceAttributeRepository, + eventPublisher: EventPublisher, + ) { + this._deviceAttributeRepository = deviceAttributeRepository; + this._eventPublisher = eventPublisher; + } + + _filterEvents = ( + eventHandler: (event: Event) => void, + userID?: string, + deviceID?: string, + ): (event: Event) => Promise => + async (event: Event): Promise => { + if ( + event.deviceID && + userID && + !await this._deviceAttributeRepository.doesUserHaveAccess( + event.deviceID, + userID, + ) + ) { + return Promise.resolve(); + } + + if (deviceID && deviceID !== event.deviceID) { + return Promise.resolve(); + } + + eventHandler(event); + return Promise.resolve(); + }; + + subscribe = ( + eventName: ?string, + eventHandler: (event: Event) => void, + userID?: string, + deviceID?: string, + ): string => + this._eventPublisher.subscribe( + eventName, + this._filterEvents(eventHandler, userID, deviceID), + deviceID, ); - // todo send event errors handling - // todo make close() async - await socket.close(); - }; + + unsubscribe = (subscriptionID: string): void => + this._eventPublisher.unsubscribe(subscriptionID); + + publish = async (eventData: EventData): Promise => + await this._eventPublisher.publish(eventData); } export default EventManager; From 77c0d473d22b40738850873209ff78e80a7d59f9 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 03:03:54 +0200 Subject: [PATCH 207/504] remove old CoreController.js, api_v1.js, EventsViews001.js --- src/lib/CoreController.js | 202 ----------- src/views/EventViews001.js | 278 --------------- src/views/api_v1.js | 691 ------------------------------------- 3 files changed, 1171 deletions(-) delete mode 100644 src/lib/CoreController.js delete mode 100644 src/views/EventViews001.js delete mode 100644 src/views/api_v1.js diff --git a/src/lib/CoreController.js b/src/lib/CoreController.js deleted file mode 100644 index 0b2cf7c3..00000000 --- a/src/lib/CoreController.js +++ /dev/null @@ -1,202 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var fs = require('fs'); -var when = require('when'); -var extend = require('xtend'); -var EventEmitter = require('events').EventEmitter; - -var logger = require('./logger.js'); -import settings from '../settings'; -var utilities = require("./utilities.js"); - - -var CoreController = function (socketID) { - //this.coreID = coreID; - this.socketID = socketID; - EventEmitter.call(this); -}; - -CoreController.prototype = { - getCore: function (coreid) { - if (global.server) { - return global.server.getCore(coreid); - } - else { - logger.error("Spark-protocol server not running"); - } - }, - - - sendAndListenFor: function (recipient, msg, filter, callback, once) { - this.listenFor(recipient, filter, callback, once); - this.send(recipient, msg); - }, - - sendAndListenForDFD: function (recipient, msg, filter, failDelay, connectDelay) { - var result = when.defer(); - - failDelay = failDelay || settings.coreRequestTimeout; - var failTimer = setTimeout(function () { - result.reject("Request Timed Out"); - }, failDelay); - - var callback = function (sender, msg) { - clearTimeout(failTimer); - result.resolve([sender, msg]); - }; - - this.sendAndListenFor(recipient, msg, filter, callback, true); - return result.promise; - }, - - - /** - * send a message to a core - * @param recipient - * @param msg - */ - send: function (recipient, msg) { - var that = this; - var core = this.getCore(recipient); - if (!core || !core.onApiMessage) { - logger.error("Couldn't find that core ", recipient); - return false; - } - - process.nextTick(function () { - try { - console.log("sending message with socketID" + that.socketID, msg); - core.onApiMessage(that.socketID, msg); - } - catch (ex) { - logger.error("error during send: " + ex); - } - }); - return true; - }, - - /** - * starts listening for a message event with the given filter criteria - * @param filter - * @param callback - * @param once - removes the listener after we've heard back - */ - listenFor: function (recipient, filter, callback, once) { - var core = this.getCore(recipient); - if (!core || !core.on) { - logger.error("Couldn't find that core ", recipient); - return; - } - - var that = this, - handler = function (sender, msg) { - //logger.log('heard from ' + ((sender) ? sender.toString() : '(UNKNOWN)')); - - if (!utilities.leftHasRightFilter(msg, filter)) { - //logger.log('filters did not match'); - return; - } - - if (once) { - core.removeListener(that.socketID, handler); - } - - process.nextTick(function () { - try { - //logger.log('passing message to callback ', msg); - callback(sender, msg); - } - catch (ex) { - logger.error("listenFor error: " + ex, (ex) ? ex.stack : ''); - } - }); - }; - - core.on(that.socketID, handler); - }, - - subscribe: function (name, userid, coreid, eventHandler) { - global.publisher.subscribe(name, userid, coreid, this, eventHandler); - }, - - unsubscribe: function (name, userId, coreId) { - global.publisher.unsubscribe(name, userId, coreId, this); - }, - - //isPublic, obj.name, obj.userid, obj.data, obj.ttl, obj.published_at - sendEvent: function (isPublic, name, userid, data, ttl, published_at, coreid) { - - if (!global.publisher) { - logger.error("Spark-protocol server not running"); - return; - } - - try { - global.publisher.publish( - isPublic, - name, - userid, - data, - ttl, - published_at, - coreid - ); - } - catch (ex) { - logger.error("sendEvent Error: " + ex); - } - - return true; - }, - - close: function () { - global.publisher.close(); - } -}; - -///** -// * This should be made more efficient, this is too simplistic -// * @returns {{}} -// */ -//CoreController.listAllCores = function() { -// var files = fs.readdirSync(settings.coreKeysDir); -// var cores = []; -// -// -// -// -// -// var corelist = files.map(function(filename) { return utilities.filenameNoExt(filename); }); -// var cores = {}; -// for(var i=0;i. -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var settings = require('../settings.js'); -var CoreController = require('../lib/CoreController.js'); - -var Api = require('./api_v1.js'); -var utilities = require("../lib/utilities.js"); -var logger = require('../lib/logger.js'); - -var when = require('when'); -var sequence = require('when/sequence'); -var pipeline = require('when/pipeline'); - -var moment = require('moment'); - -var EventsApi = { - loadViews: function (app) { - - // GET /v1/events[/:event_name] - // GET /v1/devices/events[/:event_name] - // GET /v1/devices/:device_id/events[/:event_name] - - //app.get('/v1/events', EventsApi.get_events); - //app.get('/v1/events/:event_name', EventsApi.get_events); - - // app.get('/v1/devices/events', EventsApi.get_my_events); - // app.post('/v1/devices/events', EventsApi.send_an_event); - // app.get('/v1/devices/events/:event_name', EventsApi.get_my_events); - - app.get('/v1/devices/:coreid/events', EventsApi.get_core_events); - app.get('/v1/devices/:coreid/events/:event_name', EventsApi.get_core_events); - }, - - - //----------------------------------------------------------------- - - pipeEvents: function (socket, req, res, filterCoreId) { - var userid = Api.getUserID(req); - /* - Start SSE - */ - - req.socket.setNoDelay(); - - res.writeHead(200, { - "Connection": "keep-alive", - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache" - }); - res.write(":ok\n\n"); - - - var _idleTimer = null; - var _lastMessage = null; - var keepAlive = function() { - if (((new Date()) - _lastMessage) >= 9000) { - _lastMessage = new Date(); - res.write("asdf\n"); - checkSocket(); - } - }; - - //if nothing gets sent for 9 seconds, send a newline. - var aliveInterval = setInterval(keepAlive, 3000); - - var checkSocket = function () { - try { - if (!socket) { - cleanup(); - return false; - } - - if (res.socket.destroyed) { - logger.log("Socket destroyed, cleaning up Event listener"); - cleanup(); - return false; - } - } - catch (ex) { - logger.error("pipeEvents - error checking socket ", ex); - } - return true; - }; - - var cleanup = function () { - try { - if (socket) { - socket.close(); - socket = null; - } - } - catch (ex) { - logger.error("pipeEvents - event socket close err: ", ex); - } - - try { - if (res.socket) { - res.socket.end(); - } - res.end(); - } - catch (ex) { - logger.error("pipeEvents - response close err: ", ex); - } - - try { - if (aliveInterval) { - clearInterval(aliveInterval); - aliveInterval = null; - } - } - catch (ex) { - logger.error("pipeEvents - clear interval err: ", ex); - } - - }; - - - // http://www.whatwg.org/specs/web-apps/current-work/#server-sent-events - var writeEventGen = function (isPublic) { - return function (name, data, ttl, published_at, coreid) { - if (filterCoreId && (filterCoreId !== coreid)) { - return; - } - - if (!checkSocket()) { - return; - } - - try { - _lastMessage = new Date(); - - //TODO: if the user puts the userid elsewhere in the event name... it's gonna get removed. - name = (name) ? name.toString().replace(userid + "/", "") : null; - - - // NOTE: THIS IS THE ACTUAL DATA THAT GETS SENT TO THE CORE!!! - var obj = { - data: data ? data.toString() : null, - ttl: ttl ? ttl.toString() : null, - published_at: (published_at) ? published_at.toString() : null, - coreid: (coreid) ? coreid.toString() : null - }; - res.write("event: " + name + "\n"); - res.write("data: " + JSON.stringify(obj) + "\n\n"); //~100 ms for 100,000 stringifies - } - catch (ex) { - logger.error("pipeEvents - write error: " + ex); - } - - //OTHER HEADERS: - //retry: ? - //id: ? //if we want to support resuming - //TODO: escape newlines in message? - }; - }; - - socket.on('public', writeEventGen(true)); - socket.on('private', writeEventGen(false)); - - req.on("close", cleanup); - req.on("end", cleanup); - res.on("close", cleanup); - res.on("finish", cleanup); - //res.setTimeout(30 * 1000, cleanup); - }, - - - get_events: function (req, res) { - var name = req.param('event_name'); - name = name || ""; - var socket = new CoreController(); - - var userid = Api.getUserID(req); -// if (userid) { -// socket.authorize(userid); -// } - - //----------------------------------- - //get firehose and my private events. - //socket.subscribe(true, name); - socket.subscribe(false, name, userid); - - - //send it all through - EventsApi.pipeEvents(socket, req, res); - }, - get_my_events: function (req, res) { - var name = req.param('event_name'); - name = name || ""; - var socket = new CoreController(); - - var userid = Api.getUserID(req); -// if (userid) { -// socket.authorize(userid); -// } - - //----------------------------------- - //get my events: - //socket.subscribe(true, name); - socket.subscribe(true, name, userid); - socket.subscribe(false, name, userid); - - //don't filter by core id - EventsApi.pipeEvents(socket, req, res); - }, - get_core_events: function (req, res) { - var name = req.param('event_name'); - var socket = new CoreController(); - name = name || ""; - var coreid = req.coreID || req.param('coreid'); - - var userid = Api.getUserID(req); -// if (userid) { -// socket.authorize(userid); -// } - - - //----------------------------------- - //get core events - //socket.subscribe(true, name); - socket.subscribe(true, name, userid, coreid); - socket.subscribe(false, name, userid, coreid); - - //----------------------------------- - //filter to core id - EventsApi.pipeEvents(socket, req, res, coreid); - }, - - - send_an_event: function (req, res) { - var userid = Api.getUserID(req), - socketID = Api.getSocketID(userid), - eventName = req.body.name, - data = req.body.data, - ttl = req.body.ttl || 60, - private_str = req.body.private; - - var is_public = (!private_str || (private_str === "") || (private_str === "false")); - - var socket = new CoreController(socketID); - console.log('EventViews001 - send_and_event'); - var success = socket.sendEvent(is_public, - eventName, - userid, - data, - parseInt(ttl), - moment().toISOString(), - userid - ); - - var autoClose = setTimeout(function () { - socket.close(); - res.json({ok: success}); - }, 250); - }, - - _: null -}; - - -module.exports = EventsApi; diff --git a/src/views/api_v1.js b/src/views/api_v1.js deleted file mode 100644 index 7a313059..00000000 --- a/src/views/api_v1.js +++ /dev/null @@ -1,691 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -* You can download the source here: https://github.com/spark/spark-server -*/ - -var settings = require('../settings.js'); - -var CoreController = require('../lib/CoreController.js'); - -var sequence = require('when/sequence'); -var parallel = require('when/parallel'); -var pipeline = require('when/pipeline'); - -var logger = require('../lib/logger.js'); -var utilities = require("../lib/utilities.js"); - -var fs = require('fs'); -var when = require('when'); -var util = require('util'); -var path = require('path'); -var ursa = require('ursa'); -var moment = require('moment'); - -/* - * TODO: modularize duplicate code - * TODO: implement proper session handling / user authentication - * TODO: add cors handler without losing :params support - * - */ - -var Api = { - loadViews: function (app) { - - //our middleware - app.param("coreid", Api.loadCore); - - - //core functions / variables - //app.post('/v1/devices/:coreid/:func', Api.fn_call); - app.get('/v1/devices/:coreid/:var', Api.get_var); - - app.put('/v1/devices/:coreid', Api.set_core_attributes); - //app.get('/v1/devices/:coreid', Api.get_core_attributes); - - //doesn't need per-core permissions, only shows owned cores. - //app.get('/v1/devices', Api.list_devices); - - // app.post('/v1/provisioning/:coreid', Api.provision_core); - - //app.delete('/v1/devices/:coreid', Api.release_device); - app.post('/v1/devices', Api.claim_device); - - }, - - getSocketID: function (userID) { - return userID + "_" + global._socket_counter++; - }, - - getUserID: function (res) { - if(!res.locals) { - return null; - } - if (!res.locals.oauth) { - logger.log("User obj was empty"); - return null; - } - //req.user.id is set in authorise.validateAccessToken in the OAUTH code - return res.locals.oauth.token.user.id; - }, - - list_devices: function (req, res) { - var userid = Api.getUserID(res); - logger.log("ListDevices", { userID: userid }); - - //give me all the cores - - var allCoreIDs = global.server.getAllCoreIDs(), - devices = [], - connected_promises = []; - - allCoreIDs.forEach(coreid => { - if (!coreid) { - return; - } - - var core = global.server.getCoreAttributes(coreid); - - var device = { - id: coreid, - name: (core) ? core.name : null, - last_app: core ? core.last_flashed_app_name : null, - last_heard: null - }; - - if (utilities.check_requires_update(core, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - devices.push(device); - console.log(device.id); - connected_promises.push(Api.isDeviceOnline(userid, device.id)); - }); - - logger.log("ListDevices... waiting for connected state to settle ", { userID: userid }); - - //switched 'done' to 'then' - threw an exception with 'done' here. - when.settle(connected_promises).then(function (descriptors) { - for (var i = 0; i < descriptors.length; i++) { - var desc = descriptors[i]; - - devices[i].connected = ('rejected' !== desc.state); - devices[i].last_heard = (desc.value) ? desc.value.lastPing : null; - } - - res.status(200).json(devices); - }); - }, - - get_core_attributes: function (req, res) { - var userid = Api.getUserID(res); - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - socket = new CoreController(socketID); - - - logger.log("GetAttr", { coreID: coreID, userID: userid.toString() }); - - var objReady = parallel([ - function () { - return when.resolve(global.server.getCoreAttributes(coreID)); - }, - function () { - return utilities.alwaysResolve(socket.sendAndListenForDFD(coreID, { cmd: "Describe" }, { cmd: "DescribeReturn" })); - } - ]); - - //whatever we get back... - when(objReady).done(function (results) { - try { - - if (!results || (results.length !== 2)) { - logger.error("get_core_attributes results was the wrong length " + JSON.stringify(results)); - res.json(404, "Oops, I couldn't find that core"); - return; - } - - - //we're expecting descResult to be an array: [ sender, {} ] - var doc = results[0], - descResult = results[1], - coreState = null; - - if (!doc || !doc.deviceID) { - logger.error("get_core_attributes 404 error: " + JSON.stringify(doc)); - res.json(404, "Oops, I couldn't find that core"); - return; - } - - if (util.isArray(descResult) && (descResult.length > 1)) { - coreState = descResult[1].state || {}; - } - if (!coreState) { - logger.error("get_core_attributes didn't get description: " + JSON.stringify(descResult)); - } - - var device = { - id: doc.deviceID, - name: doc.name || null, - last_app: doc.last_flashed, - connected: !!coreState, - variables: (coreState) ? coreState.v : null, - functions: (coreState) ? coreState.f : null, - cc3000_patch_version: doc.cc3000_driver_version - }; - - if (utilities.check_requires_update(doc, settings.cc3000_driver_version)) { - device["requires_deep_update"] = true; - } - - res.json(device); - } - catch (ex) { - logger.error("get_core_attributes merge error: " + ex); - res.json(500, { Error: "get_core_attributes error: " + ex }); - } - }, null); - - //get_core_attribs - end - }, - - - set_core_attributes: function (req, res) { - var coreID = req.coreID; - var userid = Api.getUserID(res); - - var promises = []; - - logger.log("set_core_attributes", { coreID: coreID, userID: userid.toString() }); - - var coreName = req.body ? req.body.name : null; - if (coreName !== null) { - logger.log("SetAttr", { coreID: coreID, userID: userid.toString(), name: coreName }); - - global.server.setCoreAttribute(req.coreID, "name", coreName); - promises.push(when.resolve({ ok: true, name: coreName })); - } - - var hasFiles = req.files && req.files.file; - if (hasFiles) { - //oh hey, you want to flash firmware? - promises.push(Api.compile_and__or_flash_dfd(req)); - } - - var signal = req.body && req.body.signal; - if (signal) { - //get your hands up in the air! Or down. - promises.push(Api.core_signal_dfd(req)); - } - - var flashApp = req.body ? req.body.app : null; - if (flashApp) { - // It makes no sense to flash a known app and also - // either signal or flash a file sent with the request - if (!hasFiles && !signal) { - - // MUST sanitize app name here, before sending to Device Service - if (utilities.contains(settings.known_apps, flashApp)) { - promises.push(Api.flash_known_app_dfd(req)); - } - else { - promises.push(when.reject("Can't flash unknown app " + flashApp)); - } - } - } - - var app_id = req.body ? req.body.app_id : null; - if (app_id && !hasFiles && !signal && !flashApp) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_app_in_db_dfd(req)); - } - - var app_example_id = req.body ? req.body.app_example_id : null; - if (app_example_id && !hasFiles && !signal && !flashApp && !app_id) { - //we have an app id, and no files, and stuff - //we must be flashing from the db! - promises.push(Api.flash_example_app_in_db_dfd(req)); - } - - - if (promises.length >= 1) { - when.all(promises).done( - function (results) { - var aggregate = {}; - for (var i in results) { - for (var key in results[i]) { - aggregate[key] = results[i][key]; - } - } - res.json(aggregate); - }, - function (err) { - res.json({ ok: false, errors: [err] }); - } - ); - } - else { - logger.error("set_core_attributes - nothing to do?", { coreID: coreID, userID: userid.toString() }); - res.json({error: "Nothing to do?"}); - } - }, - - - isDeviceOnline: function (userID, coreID) { - var tmp = when.defer(); - - var socketID = Api.getSocketID(userID); - var socket = new CoreController(socketID); - - var failTimer = setTimeout(function () { - logger.log("isDeviceOnline: Ping timed out ", { coreID: coreID }); - socket.close(); - tmp.reject("Device is not connected"); - }, settings.isCoreOnlineTimeout); - - - //setup listener for response back from the device service - socket.listenFor(coreID, { cmd: "Pong" }, function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - logger.log("isDeviceOnline: Device service thinks it is online... ", { coreID: coreID }); - - if (msg && msg.connected) { - tmp.resolve(msg); - } - else { - tmp.reject(["Core isn't online", 404]); - } - - }, true); - - logger.log("isDeviceOnline: Pinging core... ", { coreID: coreID }); - - //send it along to the device service - if (!socket.send(coreID, { cmd: "Ping" })) { - tmp.reject("send failed"); - } - - return tmp.promise; - }, - - - claim_device: function (req, res) { - res.json({ ok: true }); - }, - - - loadCore: function (req, res, next) { - req.coreID = req.params.coreid || req.body.id; - - //load core info! - req.coreInfo = { - "last_app": "", - "last_heard": new Date(), - "connected": false, - "deviceID": req.coreID - }; - - //if that user doesn't own that coreID, maybe they sent us a core name - var userid = Api.getUserID(res); - var gotCore = utilities.deferredAny([ - function () { - var core = global.server.getCoreAttributes(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - }, - function () { - var core = global.server.getCoreByName(req.coreID); - if (core && core.coreID) { - return when.resolve(core); - } - else { - return when.reject(); - } - } - ]); - - when(gotCore).then( - function (core) { - if (core) { - req.coreID = core.coreID || req.coreID; - req.coreInfo = { - last_handshake_at: core.last_handshake_at - }; - } - - next(); - }, - function (err) { - //s`okay. - next(); - }) - }, - - get_var: function (req, res) { - var userid = Api.getUserID(res); - var socketID = Api.getSocketID(userid), - coreID = req.coreID, - varName = req.params.var, - format = req.params.format; - - logger.log("GetVar", {coreID: coreID, userID: userid.toString()}); - - - //send it along to the device service - //and listen for a response back from the device service - var socket = new CoreController(socketID); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "GetVar", name: varName }, - { cmd: "VarReturn", name: varName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then(function (arr) { - var msg = arr[1]; - if (msg.error) { - //at this point, either we didn't get a describe return, or that variable - //didn't exist, either way, 404 - return res.json(404, { - ok: false, - error: msg.error - }); - } - - //TODO: make me look like the spec. - msg.coreInfo = req.coreInfo; - msg.coreInfo.connected = true; - - if (format && (format === "raw")) { - return res.send("" + msg.result); - } - else { - return res.json(msg); - } - }, - function () { - res.json(408, {error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - }, - - fn_call: function (req, res) { - var user_id = Api.getUserID(res), - coreID = req.coreID, - funcName = req.params.func, - format = req.params.format; - - logger.log("FunCall", { coreID: coreID, user_id: user_id.toString() }); - - var socketID = Api.getSocketID(user_id); - var socket = new CoreController(socketID); - var core = socket.getCore(coreID); - - - var args = req.body; - delete args.access_token; - logger.log("FunCall - calling core ", { coreID: coreID, user_id: user_id.toString() }); - var coreResult = socket.sendAndListenForDFD(coreID, - { cmd: "CallFn", name: funcName, args: args }, - { cmd: "FnReturn", name: funcName }, - settings.coreRequestTimeout - ); - - //sendAndListenForDFD resolves arr to ==> [sender, msg] - when(coreResult) - .then( - function (arr) { - var sender = arr[0], msg = arr[1]; - - try { - //logger.log("FunCall - heard back ", { coreID: coreID, user_id: user_id.toString() }); - if (msg.error && (msg.error.indexOf("Unknown Function") >= 0)) { - res.json(404, { - ok: false, - error: "Function not found" - }); - } - else if (msg.error !== null) { - res.json(400, { - ok: false, - error: msg.error - }); - } - else { - if (format && (format === "raw")) { - res.send("" + msg.result); - } - else { - res.json({ - id: core.coreID, - name: core.name || null, - last_app: core.last_flashed_app_name || null, - connected: true, - return_value: msg.result - }); - } - } - } - catch (ex) { - logger.error("FunCall handling resp error " + ex); - res.json(500, { - ok: false, - error: "Error while api was rendering response" - }); - } - }, - function () { - res.json(408, {error: "Timed out."}); - } - ).ensure(function () { - socket.close(); - }); - - //socket.send(coreID, { cmd: "CallFn", name: funcName, args: args }); - - // send the function call along to the device service - }, - - /** - * Ask the core to start / stop the "RaiseYourHand" signal - * @param req - */ - core_signal_dfd: function (req) { - var tmp = when.defer(); - - var userid = Api.getUserID(res), - socketID = Api.getSocketID(userid), - coreID = req.coreID, - showSignal = parseInt(req.body.signal); - - logger.log("SignalCore", { coreID: coreID, userID: userid.toString()}); - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out, didn't hear back"}); - }, settings.coreSignalTimeout); - - //listen for a response back from the device service - socket.listenFor(coreID, { cmd: "RaiseHandReturn"}, - function () { - clearTimeout(failTimer); - socket.close(); - - tmp.resolve({ - id: coreID, - connected: true, - signaling: showSignal === 1 - }); - }, true); - - - //send it along to the core via the device service - socket.send(coreID, { cmd: "RaiseHand", args: { signal: showSignal } }); - - return tmp.promise; - }, - - compile_and__or_flash_dfd: function (req) { - var allDone = when.defer(); - var userid = Api.getUserID(res), - coreID = req.coreID; - - - // - // Did they pass us a source file or a binary file? - // - var hasSourceFiles = false; - var sourceExts = [".cpp", ".c", ".h", ".ino" ]; - if (req.files) { - for (var name in req.files) { - if (!req.files.hasOwnProperty(name)) { - continue; - } - - var ext = utilities.getFilenameExt(req.files[name].path); - if (utilities.contains(sourceExts, ext)) { - hasSourceFiles = true; - break; - } - } - } - - - if (hasSourceFiles) { - //TODO: federate? - allDone.reject("Not yet implemented"); - } - else { - //they sent a binary, just flash it! - var flashDone = Api.flash_core_dfd(req); - - //pipe rejection / resolution of flash to response - utilities.pipeDeferred(flashDone, allDone); - } - - return allDone.promise; - }, - - - /** - * Flashing firmware to the core, binary file! - * @param req - * @returns {promise|*|Function|Promise|when.promise} - */ - flash_core_dfd: function (req, res) { - var tmp = when.defer(); - - var userid = Api.getUserID(res), - socketID = Api.getSocketID(userid), - coreID = req.coreID; - - logger.log("FlashCore", {coreID: coreID, userID: userid.toString()}); - - var args = req.query; - delete args.coreid; - - if (req.files) { - args.data = fs.readFileSync(req.files.file.path); - } - - var socket = new CoreController(socketID); - var failTimer = setTimeout(function () { - socket.close(); - tmp.reject({error: "Timed out."}); - }, settings.coreFlashTimeout); - - //listen for the first response back from the device service - socket.listenFor(coreID, { cmd: "Event", name: "Update" }, - function (sender, msg) { - clearTimeout(failTimer); - socket.close(); - - var response = { id: coreID, status: msg.message }; - if ("Update started" === msg.message) { - tmp.resolve(response); - } - else { - logger.error("flash_core_dfd rejected ", response); - tmp.reject(response); - } - - }, true); - - //send it along to the device service - socket.send(coreID, { cmd: "UFlash", args: args }); - - return tmp.promise; - }, - - provision_core: function (req, res) { - //if we're here, the user should be allowed to provision cores. - - var done = Api.provision_core_dfd(req); - when(done).then( - function (result) { - res.json(result); - }, - function (err) { - //different status code here? - res.json(400, err); - }); - }, - - provision_core_dfd: function (req, res) { - var result = when.defer(), - userid = Api.getUserID(res), - deviceID = req.body.deviceID, - publicKey = req.body.publicKey; - - if (!deviceID) { - return when.reject({ error: "No deviceID provided" }); - } - - try { - var keyObj = ursa.createPublicKey(publicKey); - if (!publicKey || (!ursa.isPublicKey(keyObj))) { - return when.reject({ error: "No key provided" }); - } - } - catch (ex) { - logger.error("error while parsing publicKey " + ex); - return when.reject({ error: "Key error " + ex }); - } - - - global.server.addCoreKey(deviceID, publicKey); - global.server.setCoreAttribute(deviceID, "registrar", userid); - global.server.setCoreAttribute(deviceID, "timestamp", new Date()); - result.resolve("Success!"); - - return result.promise; - }, - - - _: null -}; - -exports = module.exports = Api; From e8392e01466916fbc3825135dbd2a759022b5a55 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 03:05:16 +0200 Subject: [PATCH 208/504] small deviceRepository fixes. --- src/repository/DeviceRepository.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 0f1783ca..a3585346 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -171,7 +171,7 @@ class DeviceRepository { functionName: string, functionArguments: Object, ): Promise<*> => { - if (!this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + if (await !this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { throw new HttpError('No device found', 404); } @@ -196,7 +196,7 @@ class DeviceRepository { userID: string, varName: string, ): Promise => { - if (!this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + if (!await this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { throw new HttpError('No device found', 404); } @@ -242,7 +242,7 @@ class DeviceRepository { userID: string, appName: string, ) => { - if (!this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + if (await !this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { throw new HttpError('No device found', 404); } From f5e24bfcd535c6c42728450da67091c661aafb67 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 03:13:07 +0200 Subject: [PATCH 209/504] remove comment --- src/controllers/EventsController.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index 7eee27e7..5edbecd9 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -114,6 +114,3 @@ class EventsController extends Controller { } export default EventsController; - - -// todo TEST \ No newline at end of file From 9d68cc5fb2ccda96d34fe95f5b3c03aa60b4c613 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 16:19:31 +0200 Subject: [PATCH 210/504] fix formatting in eventToApi.js --- src/lib/eventToApi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/eventToApi.js b/src/lib/eventToApi.js index 043d3a2d..a755c3f7 100644 --- a/src/lib/eventToApi.js +++ b/src/lib/eventToApi.js @@ -7,7 +7,7 @@ export type EventAPIType = {| data: ?Object, published_at: Date, ttl: number, - |}; +|}; const eventToApi = (event: Event): EventAPIType => ({ coreid: event.deviceID || null, From faeb4d0a877f1a7b9d9c549063ee7628ad1d0189 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 16:42:40 +0200 Subject: [PATCH 211/504] remove Promise.resolve() from _filterEvents() --- src/managers/EventManager.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js index db522d9b..9cb8836e 100644 --- a/src/managers/EventManager.js +++ b/src/managers/EventManager.js @@ -29,15 +29,14 @@ class EventManager { userID, ) ) { - return Promise.resolve(); + return; } if (deviceID && deviceID !== event.deviceID) { - return Promise.resolve(); + return; } eventHandler(event); - return Promise.resolve(); }; subscribe = ( From 760a69f4824967ff8196c1affe97520e81489dfd Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 19:48:52 +0200 Subject: [PATCH 212/504] add comments / make closeStreamHandler --- src/RouteConfig.js | 5 ++++- src/app.js | 4 ++-- src/controllers/EventsController.js | 22 +++++++--------------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/RouteConfig.js b/src/RouteConfig.js index f300a750..41df40f9 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -36,7 +36,10 @@ const injectUserMiddleware = (): Middleware => next(); }; - +// in old codebase there was _keepAlive() function in controllers , which +// prevents of closing server-sent-events stream if there aren't events for +// a long time, but according to the docs sse keep connection alive automatically. +// if there will be related issues in the future, we can return _keepAlive() back. const serverSentEventsMiddleware = (): Middleware => (request: $Request, response: $Response, next: NextFunction) => { request.socket.setNoDelay(); diff --git a/src/app.js b/src/app.js index cb62d703..e2c34bac 100644 --- a/src/app.js +++ b/src/app.js @@ -83,12 +83,12 @@ export default ( eventPublisher, ); - // to avoid routes collisions eventController should be placed - // before DevicesController routeConfig( app, [ new DeviceClaimsController(deviceRepository), + // to avoid routes collisions EventsController should be placed + // before DevicesController new EventsController(eventManager), new DevicesController(deviceRepository), new ProvisioningController(deviceRepository), diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index 5edbecd9..ec21be78 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -21,23 +21,15 @@ class EventsController extends Controller { _closeStream(subscriptionID: string): Promise { return new Promise((resolve: () => void) => { - // TODO i'm not sure if we need all 4 listens here - this.request.on('close', () => { + const closeStreamHandler = () => { this._eventManager.unsubscribe(subscriptionID); resolve(); - }); - this.request.on('end', () => { - this._eventManager.unsubscribe(subscriptionID); - resolve(); - }); - this.response.on('finish', () => { - this._eventManager.unsubscribe(subscriptionID); - resolve(); - }); - this.response.on('end', () => { - this._eventManager.unsubscribe(subscriptionID); - resolve(); - }); + }; + + this.request.on('close', closeStreamHandler); + this.request.on('end', closeStreamHandler); + this.response.on('finish', closeStreamHandler); + this.response.on('end', closeStreamHandler); }); } From bf39452057f4ef58f44f3038067276dae5020a05 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 28 Dec 2016 23:35:23 +0200 Subject: [PATCH 213/504] remove await from publish() --- src/controllers/EventsController.js | 2 +- src/managers/EventManager.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index ec21be78..134aaab6 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -100,7 +100,7 @@ class EventsController extends Controller { ttl: postBody.ttl, }; - await this._eventManager.publish(eventData); + this._eventManager.publish(eventData); return this.ok({ ok: true }); } } diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js index 9cb8836e..ebdf8262 100644 --- a/src/managers/EventManager.js +++ b/src/managers/EventManager.js @@ -54,8 +54,8 @@ class EventManager { unsubscribe = (subscriptionID: string): void => this._eventPublisher.unsubscribe(subscriptionID); - publish = async (eventData: EventData): Promise => - await this._eventPublisher.publish(eventData); + publish = (eventData: EventData): void => + this._eventPublisher.publish(eventData); } export default EventManager; From 91d14715d5209f9edf103808a1f030307f6d345f Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 29 Dec 2016 01:16:17 +0200 Subject: [PATCH 214/504] add userID to events, remove _devicesAttribuesRepo from EventManager --- src/app.js | 5 +---- src/controllers/EventsController.js | 1 + src/managers/EventManager.js | 19 +++++-------------- src/types.js | 1 + 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/app.js b/src/app.js index e2c34bac..9c55736a 100644 --- a/src/app.js +++ b/src/app.js @@ -78,10 +78,7 @@ export default ( deviceServer, ); - const eventManager = new EventManager( - deviceAttributeRepository, - eventPublisher, - ); + const eventManager = new EventManager(eventPublisher); routeConfig( app, diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index 134aaab6..fc1698e8 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -98,6 +98,7 @@ class EventsController extends Controller { isPublic: !postBody.private, name: postBody.name, ttl: postBody.ttl, + userID: this.user.id, }; this._eventManager.publish(eventData); diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js index ebdf8262..1ad00f0b 100644 --- a/src/managers/EventManager.js +++ b/src/managers/EventManager.js @@ -1,17 +1,12 @@ // @flow import type { EventPublisher } from 'spark-protocol'; -import type { DeviceAttributeRepository, Event, EventData } from '../types'; +import type { Event, EventData } from '../types'; class EventManager { _eventPublisher: EventPublisher; - _deviceAttributeRepository: DeviceAttributeRepository; - constructor( - deviceAttributeRepository: DeviceAttributeRepository, - eventPublisher: EventPublisher, - ) { - this._deviceAttributeRepository = deviceAttributeRepository; + constructor(eventPublisher: EventPublisher) { this._eventPublisher = eventPublisher; } @@ -19,15 +14,11 @@ class EventManager { eventHandler: (event: Event) => void, userID?: string, deviceID?: string, - ): (event: Event) => Promise => - async (event: Event): Promise => { + ): (event: Event) => void => + (event: Event) => { if ( event.deviceID && - userID && - !await this._deviceAttributeRepository.doesUserHaveAccess( - event.deviceID, - userID, - ) + userID && userID !== event.userID ) { return; } diff --git a/src/types.js b/src/types.js index e950bca2..1b1f7581 100644 --- a/src/types.js +++ b/src/types.js @@ -56,6 +56,7 @@ export type EventData = { isPublic: boolean, name: string, ttl: number, + userID?: ?string, }; export type GrantType = From 255097d8af4f484701452ffdd19cc6648a531a1b Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 29 Dec 2016 22:30:53 +0200 Subject: [PATCH 215/504] move events filtering to EventPublisher --- src/controllers/EventsController.js | 8 +++++--- src/managers/EventManager.js | 32 ++++++++--------------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index fc1698e8..b95d4be0 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -63,7 +63,7 @@ class EventsController extends Controller { const subscriptionID = this._eventManager.subscribe( eventName, this._pipeEvent.bind(this), - this.user.id, + { userID: this.user.id }, ); await this._closeStream(subscriptionID); @@ -77,8 +77,10 @@ class EventsController extends Controller { const subscriptionID = this._eventManager.subscribe( eventName, this._pipeEvent.bind(this), - this.user.id, - deviceID, + { + deviceID, + userID: this.user.id, + }, ); await this._closeStream(subscriptionID); diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js index 1ad00f0b..77ddc937 100644 --- a/src/managers/EventManager.js +++ b/src/managers/EventManager.js @@ -3,6 +3,11 @@ import type { EventPublisher } from 'spark-protocol'; import type { Event, EventData } from '../types'; +type FilterOptions = { + deviceID?: string, + userID?: string, +}; + class EventManager { _eventPublisher: EventPublisher; @@ -10,36 +15,15 @@ class EventManager { this._eventPublisher = eventPublisher; } - _filterEvents = ( - eventHandler: (event: Event) => void, - userID?: string, - deviceID?: string, - ): (event: Event) => void => - (event: Event) => { - if ( - event.deviceID && - userID && userID !== event.userID - ) { - return; - } - - if (deviceID && deviceID !== event.deviceID) { - return; - } - - eventHandler(event); - }; - subscribe = ( eventName: ?string, eventHandler: (event: Event) => void, - userID?: string, - deviceID?: string, + filterOptions?: FilterOptions, ): string => this._eventPublisher.subscribe( eventName, - this._filterEvents(eventHandler, userID, deviceID), - deviceID, + eventHandler, + filterOptions, ); unsubscribe = (subscriptionID: string): void => From 6cc333bf1660ac0bbeb93cf579294588f141fa44 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 30 Dec 2016 16:16:15 +0200 Subject: [PATCH 216/504] rename getCore to getDevice --- src/repository/DeviceRepository.js | 42 +++++++++++++++--------------- test/setup/DeviceServerMock.js | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index a3585346..f34f3e00 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -88,11 +88,11 @@ class DeviceRepository { throw new HttpError('No device found', 404); } - const core = this._deviceServer.getCore(attributes.deviceID); + const device = this._deviceServer.getDevice(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ - const response = core - ? await core.onApiMessage( + const response = device + ? await device.onApiMessage( attributes.deviceID, { cmd: 'Ping' }, ) @@ -110,14 +110,14 @@ class DeviceRepository { }; getDetailsByID = async (deviceID: string, userID: string): Promise => { - const core = this._deviceServer.getCore(deviceID); - if (!core) { + const device = this._deviceServer.getDevice(deviceID); + if (!device) { throw new HttpError('No device found', 404); } const [attributes, description] = await Promise.all([ this._deviceAttributeRepository.getById(deviceID, userID), - core.onApiMessage( + device.onApiMessage( deviceID, { cmd: 'Describe' }, ), @@ -141,11 +141,11 @@ class DeviceRepository { const devicesAttributes = await this._deviceAttributeRepository.getAll(userID); const devicePromises = devicesAttributes.map(async attributes => { - const core = this._deviceServer.getCore(attributes.deviceID); + const device = this._deviceServer.getDevice(attributes.deviceID); // TODO: Not sure if this should actually be the core ID that gets sent // but that's what the old source code does :/ - const response = core - ? await core.onApiMessage( + const response = device + ? await device.onApiMessage( attributes.deviceID, { cmd: 'Ping' }, ) @@ -175,11 +175,11 @@ class DeviceRepository { throw new HttpError('No device found', 404); } - const core = this._deviceServer.getCore(deviceID); - if (!core) { + const device = this._deviceServer.getDevice(deviceID); + if (!device) { throw new HttpError('Could not get device for ID', 404); } - const result = await core.onApiMessage( + const result = await device.onApiMessage( deviceID, { cmd: 'CallFn', name: functionName, args: functionArguments }, ); @@ -200,11 +200,11 @@ class DeviceRepository { throw new HttpError('No device found', 404); } - const core = this._deviceServer.getCore(deviceID); - if (!core) { + const device = this._deviceServer.getDevice(deviceID); + if (!device) { throw new HttpError('Could not get device for ID', 404); } - const result = await core.onApiMessage( + const result = await device.onApiMessage( deviceID, { cmd: 'GetVar', name: varName }, ); @@ -220,12 +220,12 @@ class DeviceRepository { deviceID: string, file: File, ) => { - const core = this._deviceServer.getCore(deviceID); - if (!core) { + const device = this._deviceServer.getDevice(deviceID); + if (!device) { throw new HttpError('Could not get device for ID', 404); } - const result = await core.onApiMessage( + const result = await device.onApiMessage( deviceID, { cmd: 'UFlash', args: { data: file.buffer } }, ); @@ -252,12 +252,12 @@ class DeviceRepository { throw new HttpError(`No firmware ${appName} found`); } - const core = this._deviceServer.getCore(deviceID); - if (!core) { + const device = this._deviceServer.getDevice(deviceID); + if (!device) { throw new HttpError('Could not get device for ID', 404); } - const result = await core.onApiMessage( + const result = await device.onApiMessage( deviceID, { cmd: 'UFlash', args: { data: knownFirmware } }, ); diff --git a/test/setup/DeviceServerMock.js b/test/setup/DeviceServerMock.js index 7a915f5f..c8ef8cae 100644 --- a/test/setup/DeviceServerMock.js +++ b/test/setup/DeviceServerMock.js @@ -3,7 +3,7 @@ import SparkCoreMock from './SparkCoreMock'; class DeviceServerMock { - getCore(): SparkCoreMock { + getDevice(): SparkCoreMock { return new SparkCoreMock(); } } From 701f64db6c90ce4bde103cf8580b7f119380c9b3 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 30 Dec 2016 09:45:43 -0800 Subject: [PATCH 217/504] Update README.md --- README.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 841b435d..a6159bde 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,15 @@ cd spark-server/ npm install node main.js ``` - -**Windows Setup** -You'll need to install Python 2.7 and OpenSSL 1.0.2 or older. -*The newer version doesn't have the lib files needed to build the project*. -[Python Download](https://www.python.org/downloads/) -[OpenSSL Download](http://slproweb.com/products/Win32OpenSSL.html) - -[Raspberry pi Quick Install](doc/raspberryPi.md) + + +> **Windows Setup** +> You'll need to install Python 2.7 and OpenSSL 1.0.2 or older. +> *The newer version doesn't have the lib files needed to build the project*. +> [Python Download](https://www.python.org/downloads/) +> [OpenSSL Download](http://slproweb.com/products/Win32OpenSSL.html) + +> [Raspberry pi Quick Install](doc/raspberryPi.md) How do I get started? @@ -193,7 +194,3 @@ Known Limitations ================== We worked hard to make our cloud services scalable and awesome, but that also presents a burden for new users. This release was designed to be easy to use, to understand, and to maintain, and not to discourage anyone who is new to running a server. This means some of the fancy stuff isn't here yet, but don't despair, we'll keep improving this project, and we hope you'll use it to build awesome things. - - -What features are coming -======================== From 45ae0605a89d9779a733294d3756af7ae2dbb53e Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 30 Dec 2016 09:46:05 -0800 Subject: [PATCH 218/504] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6159bde..8c103d0a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ node main.js > [Python Download](https://www.python.org/downloads/) > [OpenSSL Download](http://slproweb.com/products/Win32OpenSSL.html) -> [Raspberry pi Quick Install](doc/raspberryPi.md) +[Raspberry pi Quick Install](doc/raspberryPi.md) How do I get started? From c361fc10f8844c6fb56d278525481b0d1917bd34 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 30 Dec 2016 09:47:00 -0800 Subject: [PATCH 219/504] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c103d0a..cc06142f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ node main.js > [Python Download](https://www.python.org/downloads/) > [OpenSSL Download](http://slproweb.com/products/Win32OpenSSL.html) -[Raspberry pi Quick Install](doc/raspberryPi.md) +[Raspberry pi Quick Install](raspberryPi.md) How do I get started? From 04e406947b1d04de1ced946b6ef00e8cae171eeb Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sun, 1 Jan 2017 19:56:58 +0200 Subject: [PATCH 220/504] add babel-plugin-transform-runtime; remove babel-polyfill from package.json and from ava babel setup(no needs anymore) --- .babelrc | 3 ++- package.json | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.babelrc b/.babelrc index af6d1d59..70b5e181 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,8 @@ "transform-decorators-legacy", "transform-es2015-destructuring", "transform-es2015-spread", - "transform-flow-strip-types" + "transform-flow-strip-types", + "transform-runtime" ], "presets": ["es2015", "latest", "stage-0", "stage-1"] } diff --git a/package.json b/package.json index e616bec6..622492f8 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "!test/__test_data__/*" ], "require": [ - "babel-polyfill", "babel-register" ] }, @@ -73,7 +72,7 @@ "babel-plugin-transform-es2015-destructuring": "^6.19.0", "babel-plugin-transform-es2015-spread": "^6.8.0", "babel-plugin-transform-flow-strip-types": "^6.18.0", - "babel-polyfill": "^6.16.0", + "babel-plugin-transform-runtime": "^6.15.0", "babel-preset-es2015": "^6.18.0", "babel-preset-latest": "^6.16.0", "babel-preset-stage-0": "^6.16.0", From f9ef7226ea100b17d57fca8db9921a5a9ed25f71 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 3 Jan 2017 20:38:28 +0200 Subject: [PATCH 221/504] use device.getVariableValue() instead onApiMessage() --- src/controllers/DevicesController.js | 2 +- src/repository/DeviceRepository.js | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 05c800b9..23f8882b 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -82,7 +82,7 @@ class DevicesController extends Controller { return this.ok({ result: varValue }); } catch (error) { const errorMessage = error.message; - if (errorMessage.indexOf('Variable not found') >= 0) { + if (errorMessage.match('Variable not found')) { throw new HttpError('Variable not found', 404); } throw error; diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index f34f3e00..c86d9144 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -204,16 +204,8 @@ class DeviceRepository { if (!device) { throw new HttpError('Could not get device for ID', 404); } - const result = await device.onApiMessage( - deviceID, - { cmd: 'GetVar', name: varName }, - ); - - if (result.error) { - throw result.error; - } - return result; + return await device.getVariableValue(varName); }; flashBinary = async ( From 57e596bfa8190e53be8b1add3ea78adc0d9eb5ac Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 3 Jan 2017 21:03:02 +0200 Subject: [PATCH 222/504] use device.getDescription() instead onApiMessage() --- src/repository/DeviceRepository.js | 9 +++------ test/setup/SparkCoreMock.js | 11 +++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index c86d9144..0d77749d 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -117,10 +117,7 @@ class DeviceRepository { const [attributes, description] = await Promise.all([ this._deviceAttributeRepository.getById(deviceID, userID), - device.onApiMessage( - deviceID, - { cmd: 'Describe' }, - ), + device.getDescription(), ]); if (!attributes) { @@ -130,10 +127,10 @@ class DeviceRepository { return ({ ...attributes, connected: true, - functions: description.f, + functions: description.state.f, lastFlashedAppName: null, lastHeard: new Date(), - variables: description.v, + variables: description.state.v, }); }; diff --git a/test/setup/SparkCoreMock.js b/test/setup/SparkCoreMock.js index 6abf6201..50e12468 100644 --- a/test/setup/SparkCoreMock.js +++ b/test/setup/SparkCoreMock.js @@ -4,6 +4,17 @@ class SparkCoreMock { onApiMessage() { return true; } + + getVariableValue = (): Object => 0; + + getDescription = (): Object => ({ + firmware_version: '0.6.0', + product_id: '6', + state: { + f: null, + v: null, + }, + }); } export default SparkCoreMock; From 53069c0d0395c3de06de4a0b93a15f156426b0cc Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 3 Jan 2017 21:22:40 +0200 Subject: [PATCH 223/504] use device.callFunction() instead of onApiMessage() --- src/repository/DeviceRepository.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 0d77749d..8bd1985f 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -176,16 +176,11 @@ class DeviceRepository { if (!device) { throw new HttpError('Could not get device for ID', 404); } - const result = await device.onApiMessage( - deviceID, - { cmd: 'CallFn', name: functionName, args: functionArguments }, - ); - if (result.error) { - throw result.error; - } - - return result.result; + return await device.callFunction( + functionName, + functionArguments, + ); }; getVariableValue = async ( From ec56290b9219822e160eac0d459cf515e5f42cda Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 3 Jan 2017 21:38:57 +0200 Subject: [PATCH 224/504] use device.flash() instead of onApiMessage() --- src/controllers/DevicesController.js | 10 ++++++---- src/repository/DeviceRepository.js | 22 ++-------------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 23f8882b..4c6a7cfd 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -108,19 +108,21 @@ class DevicesController extends Controller { } // 2 flash device with known app if (postBody.app_id) { - await this._deviceRepository.flashKnownApp( + const flashStatus = await this._deviceRepository.flashKnownApp( deviceID, this.user.id, postBody.app_id, ); - return this.ok({ id: deviceID, status: 'Update started' }); + return this.ok({ id: deviceID, status: flashStatus }); } const file = this.request.files.file[0]; if (file && file.originalname.endsWith('.bin')) { - await this._deviceRepository.flashBinary(deviceID, file); - return this.ok({ id: deviceID, status: 'Update started' }); + const flashStatus = await this._deviceRepository + .flashBinary(deviceID, file); + + return this.ok({ id: deviceID, status: flashStatus }); } throw new HttpError('Did not update device'); diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 8bd1985f..1ded4170 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -209,16 +209,7 @@ class DeviceRepository { throw new HttpError('Could not get device for ID', 404); } - const result = await device.onApiMessage( - deviceID, - { cmd: 'UFlash', args: { data: file.buffer } }, - ); - - if (result.error) { - throw result.error; - } - - return result.result; + return await device.flash(file.buffer); }; flashKnownApp = async ( @@ -241,16 +232,7 @@ class DeviceRepository { throw new HttpError('Could not get device for ID', 404); } - const result = await device.onApiMessage( - deviceID, - { cmd: 'UFlash', args: { data: knownFirmware } }, - ); - - if (result.error) { - throw result.error; - } - - return result.result; + return await device.flash(knownFirmware); }; provision = async ( From 8c17f12964a971768fc4808e82b58dd3134cb1ad Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 4 Jan 2017 01:09:41 +0200 Subject: [PATCH 225/504] use device.ping() instead of onApiMessage() --- src/repository/DeviceRepository.js | 29 +++++++++++------------------ test/setup/SparkCoreMock.js | 5 +++++ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 1ded4170..9c6aa23f 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -84,18 +84,15 @@ class DeviceRepository { deviceID, userID, ); + if (!attributes) { throw new HttpError('No device found', 404); } const device = this._deviceServer.getDevice(attributes.deviceID); - // TODO: Not sure if this should actually be the core ID that gets sent - // but that's what the old source code does :/ - const response = device - ? await device.onApiMessage( - attributes.deviceID, - { cmd: 'Ping' }, - ) + + const pingResponse = device + ? device.ping() : { connected: false, lastPing: null, @@ -103,9 +100,9 @@ class DeviceRepository { return { ...attributes, - connected: response.connected, + connected: pingResponse.connected, lastFlashedAppName: null, - lastHeard: response.lastPing, + lastHeard: pingResponse.lastPing, }; }; @@ -139,13 +136,9 @@ class DeviceRepository { await this._deviceAttributeRepository.getAll(userID); const devicePromises = devicesAttributes.map(async attributes => { const device = this._deviceServer.getDevice(attributes.deviceID); - // TODO: Not sure if this should actually be the core ID that gets sent - // but that's what the old source code does :/ - const response = device - ? await device.onApiMessage( - attributes.deviceID, - { cmd: 'Ping' }, - ) + + const pingResponse = device + ? device.ping() : { connected: false, lastPing: null, @@ -153,9 +146,9 @@ class DeviceRepository { return { ...attributes, - connected: response.connected, + connected: pingResponse.connected, lastFlashedAppName: null, - lastHeard: response.lastPing, + lastHeard: pingResponse.lastPing, }; }); diff --git a/test/setup/SparkCoreMock.js b/test/setup/SparkCoreMock.js index 50e12468..590be515 100644 --- a/test/setup/SparkCoreMock.js +++ b/test/setup/SparkCoreMock.js @@ -15,6 +15,11 @@ class SparkCoreMock { v: null, }, }); + + ping = (): Object => ({ + connected: false, + lastPing: new Date(), + }); } export default SparkCoreMock; From eb1fe0d75cde523a6f0a886e20350c752b0cfce3 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 4 Jan 2017 07:43:36 -0800 Subject: [PATCH 226/504] Added support for `raiseYourHand` --- src/controllers/DevicesController.js | 23 +++++++++++++++++++++-- src/repository/DeviceRepository.js | 25 ++++++++++++++++++++++++- src/types.js | 3 ++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 4c6a7cfd..ec9c37fe 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -94,7 +94,12 @@ class DevicesController extends Controller { @allowUpload('file', 1) async updateDevice( deviceID: string, - postBody: { app_id?: string, name?: string, file_type?: 'binary' }, + postBody: { + app_id?: string, + name?: string, + file_type?: 'binary', + signal: boolean, + }, ): Promise<*> { // 1 rename device if (postBody.name) { @@ -117,7 +122,10 @@ class DevicesController extends Controller { return this.ok({ id: deviceID, status: flashStatus }); } - const file = this.request.files.file[0]; + const file = + this.request.files && + this.request.files.file[0]; + if (file && file.originalname.endsWith('.bin')) { const flashStatus = await this._deviceRepository .flashBinary(deviceID, file); @@ -125,6 +133,17 @@ class DevicesController extends Controller { return this.ok({ id: deviceID, status: flashStatus }); } + // If signal exists then we want to toggle nyan mode. This just makes the + // LED change colors. + if (!Number.isNaN(postBody.signal)) { + await this._deviceRepository.raiseYourHand( + deviceID, + this.user.id, + !!parseInt(postBody.signal, 10), + ); + return this.ok({id: deviceID, ok: true}); + } + throw new HttpError('Did not update device'); } diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 9c6aa23f..9a89a973 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -210,7 +210,10 @@ class DeviceRepository { userID: string, appName: string, ) => { - if (await !this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + if (await !this._deviceAttributeRepository.doesUserHaveAccess( + deviceID, + userID, + )) { throw new HttpError('No device found', 404); } @@ -259,6 +262,26 @@ class DeviceRepository { return await this.getByID(deviceID, userID); }; + raiseYourHand = async ( + deviceID: string, + userID: string, + shouldShowSignal: boolean, + ): Promise => { + if (await !this._deviceAttributeRepository.doesUserHaveAccess( + deviceID, + userID, + )) { + throw new HttpError('No device found', 404); + } + + const device = this._deviceServer.getDevice(deviceID); + if (!device) { + throw new HttpError('Could not get device for ID', 404); + } + + return await device.raiseYourHand(shouldShowSignal); + }; + renameDevice = async ( deviceID: string, userID: string, diff --git a/src/types.js b/src/types.js index 1b1f7581..cc7202fd 100644 --- a/src/types.js +++ b/src/types.js @@ -149,12 +149,13 @@ export type DeviceRepository = { claimDevice(deviceID: string, userID: string): Promise, generateClaimCode(userID: string): Promise, flashBinary(deviceID: string, files: File): Promise<*>, - flashKnownApp(deviceID: string, app: string): Promise<*>, + flashKnownApp(deviceID: string, userID: string, app: string): Promise<*>, getAll(userID: string): Promise>, getByID(deviceID: string, userID: string): Promise, getDetailsByID(deviceID: string, userID: string): Promise<*>, getVariableValue(deviceID: string, userID: string, varName: string): Promise, provision(deviceID: string, userID: string, publicKey: string): Promise<*>, + raiseYourHand(deviceID: string, userID: string, shouldShowSignal: boolean): Promise, renameDevice(deviceID: string, userID: string, name: string): Promise, unclaimDevice(deviceID: string, userID: string): Promise, }; From c6db3552ef304eb292a05953f5df05f9d24ee1ce Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 4 Jan 2017 07:45:16 -0800 Subject: [PATCH 227/504] Testing switching branch. --- src/controllers/DevicesController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index ec9c37fe..b0de8a7f 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -115,7 +115,7 @@ class DevicesController extends Controller { if (postBody.app_id) { const flashStatus = await this._deviceRepository.flashKnownApp( deviceID, - this.user.id, + this.user.id, postBody.app_id, ); From 7e07a9881962688b693a299d65bb564a88c4600b Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 5 Jan 2017 01:31:46 +0200 Subject: [PATCH 228/504] change spark-protocol nodemon watch folder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 622492f8..89e66d03 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "build:clean": "rimraf ./build", "lint": "eslint -- .", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/lib --ignore core_keys --ignore users --ignore webhooks --ignore known_apps", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore core_keys --ignore users --ignore webhooks --ignore known_apps", "start:prod": "npm run build && node ./build/main.js", "test": "ava --serial", "test:watch": "ava --watch --serial" From 1570930699c3f409847857f0f4a0d118b3d5a0a2 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 5 Jan 2017 15:09:35 +0200 Subject: [PATCH 229/504] add uuid package, use it in webhookFileRepo/userFileRepository, fix some Flow and add missing async --- package.json | 1 + src/controllers/WebhooksController.js | 7 ++----- src/repository/UserFileRepository.js | 15 ++++++++++----- src/repository/WebhookFileRepository.js | 16 +++++++++++----- test/DevicesController.test.js | 8 ++++---- test/ProvisioningController.test.js | 8 ++++---- test/UsersController.test.js | 4 ++-- test/WebhooksController.test.js | 6 +++--- 8 files changed, 37 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 622492f8..71f7d196 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "request": "*", "spark-protocol": "../spark-protocol", "ursa": "*", + "uuid": "^3.0.1", "when": "*", "xtend": "*" }, diff --git a/src/controllers/WebhooksController.js b/src/controllers/WebhooksController.js index 996d7b0c..44f18c48 100644 --- a/src/controllers/WebhooksController.js +++ b/src/controllers/WebhooksController.js @@ -62,11 +62,8 @@ class WebhooksController extends Controller { throw validateError; } - const newWebhook = await this._webhookRepository.create({ - ...model, - created_at: new Date(), - id: '', - }); + const newWebhook = await this._webhookRepository.create(model); + return this.ok({ created_at: newWebhook.created_at, event: newWebhook.event, diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js index 9b995033..f90106e0 100644 --- a/src/repository/UserFileRepository.js +++ b/src/repository/UserFileRepository.js @@ -2,7 +2,8 @@ import type { TokenObject, User, UserCredentials } from '../types'; -import { JSONFileManager, uuid } from 'spark-protocol'; +import uuid from 'uuid'; +import { JSONFileManager } from 'spark-protocol'; import PasswordHasher from '../lib/PasswordHasher'; import HttpError from '../lib/HttpError'; @@ -20,12 +21,16 @@ class UserFileRepository { const salt = await PasswordHasher.generateSalt(); const passwordHash = await PasswordHasher.hash(password, salt); + let id = uuid(); + while (await this.getById(id)) { + id = uuid(); + } const modelToSave = { accessTokens: [], created_at: new Date(), created_by: null, - id: uuid(), + id, passwordHash, salt, username, @@ -43,10 +48,10 @@ class UserFileRepository { throw new HttpError('Not implemented'); }; - getAll = (): Promise> => + getAll = async (): Promise> => this._fileManager.getAllData(); - getById = (id: string): Promise => + getById = async (id: string): Promise => this._fileManager.getFile(`${id}.json`); getByUsername = async (username: string): Promise => @@ -91,7 +96,7 @@ class UserFileRepository { this._fileManager.writeFile(`${user.id}.json`, userToSave); }; - deleteById = (id: string): Promise => + deleteById = async (id: string): Promise => this._fileManager.deleteFile(`${id}.json`); diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js index 9c1f54c7..035761d6 100644 --- a/src/repository/WebhookFileRepository.js +++ b/src/repository/WebhookFileRepository.js @@ -1,8 +1,9 @@ // @flow -import type { Webhook } from '../types'; +import type { Webhook, WebhookMutator } from '../types'; -import { JSONFileManager, uuid } from 'spark-protocol'; +import uuid from 'uuid'; +import { JSONFileManager } from 'spark-protocol'; import HttpError from '../lib/HttpError'; class WebhookFileRepository { @@ -12,11 +13,16 @@ class WebhookFileRepository { this._fileManager = new JSONFileManager(path); } - create = async (model: Webhook): Promise => { + create = async (model: WebhookMutator): Promise => { + let id = uuid(); + while (await this.getById(id)) { + id = uuid(); + } + const modelToSave = { ...model, created_at: new Date(), - id: uuid(), + id, }; this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); @@ -32,7 +38,7 @@ class WebhookFileRepository { getById = async (id: string): Promise => this._fileManager.getFile(`${id}.json`); - update = (model: Webhook): Promise => { + update = async (model: WebhookMutator): Promise => { throw new HttpError('Not implemented'); }; } diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 18e929df..c240992e 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -134,8 +134,8 @@ test.serial('should claim device', async t => { // TODO write test for checking the error if device belongs to somebody else // TODO write tests for updateDevice & callFunction -test.after.always(() => { - settings.usersRepository.deleteById(testUser.id); - settings.deviceAttributeRepository.deleteById(DEVICE_ID); - settings.deviceKeyFileRepository.delete(DEVICE_ID); +test.after.always(async (): Promise => { + await settings.usersRepository.deleteById(testUser.id); + await settings.deviceAttributeRepository.deleteById(DEVICE_ID); + await settings.deviceKeyFileRepository.delete(DEVICE_ID); }); diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js index 84ee3aef..920a1373 100644 --- a/test/ProvisioningController.test.js +++ b/test/ProvisioningController.test.js @@ -75,8 +75,8 @@ test('should throw an error if public key is not provided', async t => { t.is(response.body.error, 'No key provided'); }); -test.after.always(() => { - settings.usersRepository.deleteById(testUser.id); - settings.deviceAttributeRepository.deleteById(DEVICE_ID); - settings.deviceKeyFileRepository.delete(DEVICE_ID); +test.after.always(async (): Promise => { + await settings.usersRepository.deleteById(testUser.id); + await settings.deviceAttributeRepository.deleteById(DEVICE_ID); + await settings.deviceKeyFileRepository.delete(DEVICE_ID); }); diff --git a/test/UsersController.test.js b/test/UsersController.test.js index ff49243a..76c589d0 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -81,6 +81,6 @@ test.serial('should deleteById access token for the user', async t => { )); }); -test.after.always(() => { - settings.usersRepository.deleteById(user.id); +test.after.always(async (): Promise => { + await settings.usersRepository.deleteById(user.id); }); diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index bc9fedb5..ef7296b8 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -157,7 +157,7 @@ test.serial('should delete webhook', async t => { )); }); -test.after.always(() => { - settings.webhookRepository.deleteById(testWebhook.id); - settings.usersRepository.deleteById(testUser.id); +test.after.always(async (): Promise => { + await settings.webhookRepository.deleteById(testWebhook.id); + await settings.usersRepository.deleteById(testUser.id); }); From fffaddc63177459e60eaf56352c236a5e19b361c Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 5 Jan 2017 23:53:03 +0200 Subject: [PATCH 230/504] add deviceKeyRepository to deviceServerConfig --- src/main.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.js b/src/main.js index f687b902..1f99bb55 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import { DeviceAttributeFileRepository, + DeviceKeyFileRepository, DeviceServer, EventPublisher, ServerConfigFileRepository, @@ -35,6 +36,9 @@ const deviceServer = new DeviceServer( deviceAttributeRepository: new DeviceAttributeFileRepository( settings.coreKeysDir, ), + deviceKeyRepository: new DeviceKeyFileRepository( + settings.coreKeysDir, + ), host: settings.HOST, port: settings.PORT, serverConfigRepository: new ServerConfigFileRepository( From 91f1719646376f4135e386d04d7c7fda26615f3a Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 6 Jan 2017 21:26:45 +0200 Subject: [PATCH 231/504] add deviceKeyRepository/ change deviceServer arg. --- src/main.js | 31 ++++++++++++++++--------------- src/settings.js | 1 + 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/main.js b/src/main.js index 1f99bb55..16f81aaf 100644 --- a/src/main.js +++ b/src/main.js @@ -5,7 +5,7 @@ import { DeviceKeyFileRepository, DeviceServer, EventPublisher, - ServerConfigFileRepository, + ServerKeyFileRepository, } from 'spark-protocol'; import utilities from './lib/utilities'; import logger from './lib/logger'; @@ -28,27 +28,28 @@ process.on('uncaughtException', (exception: Error) => { logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); + +const deviceAttributeRepository = new DeviceAttributeFileRepository( + settings.coreKeysDir, +); +const deviceKeyRepository = new DeviceKeyFileRepository( + settings.coreKeysDir, +); +const serverKeyRepository = new ServerKeyFileRepository( + settings.serverKeysDir, + settings.serverKeyFile, +); const eventPublisher = new EventPublisher(); const deviceServer = new DeviceServer( + deviceAttributeRepository, + deviceKeyRepository, + serverKeyRepository, + eventPublisher, { - coreKeysDir: settings.coreKeysDir, - deviceAttributeRepository: new DeviceAttributeFileRepository( - settings.coreKeysDir, - ), - deviceKeyRepository: new DeviceKeyFileRepository( - settings.coreKeysDir, - ), host: settings.HOST, port: settings.PORT, - serverConfigRepository: new ServerConfigFileRepository( - settings.serverKeyFile, - ), - serverKeyFile: settings.serverKeyFile, - serverKeyPassEnvVar: settings.serverKeyPassEnvVar, - serverKeyPassFile: settings.serverKeyPassFile, }, - eventPublisher, ); global.server = deviceServer; diff --git a/src/settings.js b/src/settings.js index e4b49b55..3bf3bd83 100644 --- a/src/settings.js +++ b/src/settings.js @@ -51,6 +51,7 @@ export default { */ cryptoSalt: 'aes-128-cbc', serverKeyFile: "default_key.pem", + serverKeysDir: './', serverKeyPassFile: null, serverKeyPassEnvVar: null, From 515b2773ed51cc3a51b2f3077e11030043f283f7 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 7 Jan 2017 01:25:06 +0200 Subject: [PATCH 232/504] change server generated content dir paths. --- .gitignore | 5 +---- package.json | 2 +- src/app.js | 4 ++-- src/main.js | 4 ++-- src/settings.js | 12 ++++++------ src/types.js | 2 +- test/setup/settings.js | 8 ++++---- 7 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 43982fc8..99bbbcf4 100644 --- a/.gitignore +++ b/.gitignore @@ -28,10 +28,7 @@ build/Release node_modules # Spark generated files/directories which contain secrets -known_apps -core_keys -users -webhooks +data __test_data__ *.der *.pem diff --git a/package.json b/package.json index 678bdde9..26aa7849 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "build:clean": "rimraf ./build", "lint": "eslint -- .", "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore core_keys --ignore users --ignore webhooks --ignore known_apps", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", "start:prod": "npm run build && node ./build/main.js", "test": "ava --serial", "test:watch": "ava --watch --serial" diff --git a/src/app.js b/src/app.js index 9c55736a..277310ed 100644 --- a/src/app.js +++ b/src/app.js @@ -68,13 +68,13 @@ export default ( app.use(setCORSHeaders); const deviceAttributeRepository = new DeviceAttributeFileRepository( - settings.coreKeysDir, + settings.deviceKeysDir, ); const deviceRepository = new DeviceRepository( deviceAttributeRepository, settings.deviceFirmwareRepository, - new DeviceKeyFileRepository(settings.coreKeysDir), + new DeviceKeyFileRepository(settings.deviceKeysDir), deviceServer, ); diff --git a/src/main.js b/src/main.js index 16f81aaf..2f927748 100644 --- a/src/main.js +++ b/src/main.js @@ -30,10 +30,10 @@ process.on('uncaughtException', (exception: Error) => { const deviceAttributeRepository = new DeviceAttributeFileRepository( - settings.coreKeysDir, + settings.deviceKeysDir, ); const deviceKeyRepository = new DeviceKeyFileRepository( - settings.coreKeysDir, + settings.deviceKeysDir, ); const serverKeyRepository = new ServerKeyFileRepository( settings.serverKeysDir, diff --git a/src/settings.js b/src/settings.js index 3bf3bd83..e9d68f1c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -28,30 +28,30 @@ export default { accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, - coreKeysDir: path.join(__dirname, 'core_keys'), coreRequestTimeout: 30000, coreSignalTimeout: 30000, + deviceKeysDir: path.join(__dirname, './data/deviceKeys'), isCoreOnlineTimeout: 2000, loginRoute: '/oauth/token', logRequests: true, maxHooksPerDevice: 10, maxHooksPerUser: 20, deviceFirmwareRepository: new DeviceFirmwareFileRepository( - path.join(__dirname, 'known_apps'), + path.join(__dirname, './data/knownApps'), ), webhookRepository: new WebhookFileRepository( - path.join(__dirname, 'webhooks'), + path.join(__dirname, './data/webhooks'), ), usersRepository: new UsersFileRepository( - path.join(__dirname, 'users'), + path.join(__dirname, './data/users'), ), /** * Your server crypto keys! */ cryptoSalt: 'aes-128-cbc', - serverKeyFile: "default_key.pem", - serverKeysDir: './', + serverKeyFile: 'default_key.pem', + serverKeysDir: path.join(__dirname, './data'), serverKeyPassFile: null, serverKeyPassEnvVar: null, diff --git a/src/types.js b/src/types.js index cc7202fd..1532fc2a 100644 --- a/src/types.js +++ b/src/types.js @@ -116,11 +116,11 @@ export type Settings = { accessTokenLifetime: number, baseUrl: string, coreFlashTimeout: number, - coreKeysDir: string, coreRequestTimeout: number, coreSignalTimeout: number, cryptoSalt: string, deviceFirmwareRepository: DeviceFirmwareRepository, + deviceKeysDir: string, HOST: string, isCoreOnlineTimeout: number, loginRoute: string, diff --git a/test/setup/settings.js b/test/setup/settings.js index d9a8262a..3f3660b5 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -10,7 +10,7 @@ export default { accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, - coreKeysDir: path.join(__dirname, '../__test_data__/core_keys'), + deviceKeysDir: path.join(__dirname, '../__test_data__/deviceKeys'), coreRequestTimeout: 30000, coreSignalTimeout: 30000, isCoreOnlineTimeout: 2000, @@ -19,13 +19,13 @@ export default { maxHooksPerDevice: 10, maxHooksPerUser: 20, deviceAttributeRepository: new DeviceAttributeFileRepository( - path.join(__dirname, '../__test_data__/core_keys'), + path.join(__dirname, '../__test_data__/deviceKeys'), ), deviceFirmwareRepository: new DeviceFirmwareFileRepository( - path.join(__dirname, 'known_apps'), + path.join(__dirname, '../__test_data__/knownApp'), ), deviceKeyFileRepository: new DeviceKeyFileRepository( - path.join(__dirname, '../__test_data__/core_keys'), + path.join(__dirname, '../__test_data__/deviceKeys'), ), usersRepository: new UsersFileRepository( path.join(__dirname, '../__test_data__/users'), From a3ceaefaa7a3b0e9869e6e4c6fa3c9635e1bc16f Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 7 Jan 2017 08:52:18 -0800 Subject: [PATCH 233/504] Added constitute for IoC --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 622492f8..6638628e 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dependencies": { "basic-auth-parser": "0.0.2", "body-parser": "^1.15.2", + "constitute": "^1.6.2", "express": "^4.14.0", "express-oauth-server": "^2.0.0-b1", "moment": "*", From 74841fe2b8b85aa4f2bd5516d00b6f38f111a8d7 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 8 Jan 2017 10:31:40 -0800 Subject: [PATCH 234/504] Added constitute. --- src/RouteConfig.js | 9 ++- src/app.js | 30 +++------ src/controllers/UsersController.js | 2 +- src/defaultBindings.js | 98 +++++++++++++++++++++++++++++ src/interfaces.js | 22 +++++++ src/lib/DeviceConnection.js | 17 ----- src/main.js | 51 ++++++--------- src/settings.js | 22 +++---- src/types.js | 5 -- test/DevicesController.test.js | 9 +-- test/ProvisioningController.test.js | 9 +-- test/UsersController.test.js | 7 ++- test/WebhooksController.test.js | 5 +- test/setup/settings.js | 23 +++---- test/setup/testApp.js | 20 +++++- 15 files changed, 205 insertions(+), 124 deletions(-) create mode 100644 src/defaultBindings.js create mode 100644 src/interfaces.js delete mode 100644 src/lib/DeviceConnection.js diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 41df40f9..05287b52 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -7,6 +7,7 @@ import type { Middleware, NextFunction, } from 'express'; +import type {Container} from 'constitute'; import type { Settings } from './types'; import type Controller from './controllers/Controller'; @@ -60,19 +61,21 @@ const defaultMiddleware = export default ( app: $Application, - controllers: Array, + container: Container, + controllers: Array, settings: Settings, ) => { const oauth = new OAuthServer({ accessTokenLifetime: settings.accessTokenLifetime, allowBearerTokensInQueryString: true, - model: new OAuthModel(settings.usersRepository), + model: new OAuthModel(container.constitute('UserRepository')), }); const injectFilesMiddleware = multer(); app.post(settings.loginRoute, oauth.token()); - controllers.forEach((controller: Controller) => { + controllers.forEach((controllerName: string) => { + const controller = container.constitute(controllerName); Object.getOwnPropertyNames( (Object.getPrototypeOf(controller): any), ).forEach((functionName: string) => { diff --git a/src/app.js b/src/app.js index 277310ed..74793875 100644 --- a/src/app.js +++ b/src/app.js @@ -7,6 +7,7 @@ import type { Middleware, NextFunction, } from 'express'; +import type {Container} from 'constitute'; import type { EventPublisher, DeviceServer } from 'spark-protocol'; import type { Settings } from './types'; @@ -34,9 +35,8 @@ import UsersController from './controllers/UsersController'; import WebhooksController from './controllers/WebhooksController'; export default ( + container: Container, settings: Settings, - deviceServer: DeviceServer, - eventPublisher: EventPublisher, ): $Application => { const app = express(); @@ -67,30 +67,18 @@ export default ( app.use(bodyParser.urlencoded({ extended: true })); app.use(setCORSHeaders); - const deviceAttributeRepository = new DeviceAttributeFileRepository( - settings.deviceKeysDir, - ); - - const deviceRepository = new DeviceRepository( - deviceAttributeRepository, - settings.deviceFirmwareRepository, - new DeviceKeyFileRepository(settings.deviceKeysDir), - deviceServer, - ); - - const eventManager = new EventManager(eventPublisher); - routeConfig( app, + container, [ - new DeviceClaimsController(deviceRepository), + 'DeviceClaimsController', // to avoid routes collisions EventsController should be placed // before DevicesController - new EventsController(eventManager), - new DevicesController(deviceRepository), - new ProvisioningController(deviceRepository), - new UsersController(settings.usersRepository), - new WebhooksController(settings.webhookRepository), + 'EventsController', + 'DevicesController', + 'ProvisioningController', + 'UsersController', + 'WebhooksController', ], settings, ); diff --git a/src/controllers/UsersController.js b/src/controllers/UsersController.js index 848bc6f2..8817cc9c 100644 --- a/src/controllers/UsersController.js +++ b/src/controllers/UsersController.js @@ -29,7 +29,7 @@ class UsersController extends Controller { await this._userRepository.isUserNameInUse(userCredentials.username); if (isUserNameInUse) { - throw new HttpError('user with the username is already exist'); + throw new HttpError('user with the username already exists'); } const newUser = await this._userRepository.createWithCredentials( diff --git a/src/defaultBindings.js b/src/defaultBindings.js new file mode 100644 index 00000000..6d226078 --- /dev/null +++ b/src/defaultBindings.js @@ -0,0 +1,98 @@ +// @flow + +import type {Container} from 'constitute'; + +import { + defaultBindings, +} from 'spark-protocol'; +import DeviceClaimsController from './controllers/DeviceClaimsController'; +import DevicesController from './controllers/DevicesController'; +import EventsController from './controllers/EventsController'; +import ProvisioningController from './controllers/ProvisioningController'; +import UsersController from './controllers/UsersController'; +import WebhooksController from './controllers/WebhooksController'; +import EventManager from './managers/EventManager'; +import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository'; +import DeviceRepository from './repository/DeviceRepository'; +import UserFileRepository from './repository/UserFileRepository'; +import WebhookFileRepository from './repository/WebhookFileRepository'; +import settings from './settings'; + +export default (container: Container): void => { + // spark protocol container bindings + defaultBindings(container); + + // settings + container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); + container.bindValue('FIRMWARE_DIRECTORY', settings.FIRMWARE_DIRECTORY); + container.bindValue('SERVER_KEY_FILENAME', settings.SERVER_KEY_FILENAME); + container.bindValue('SERVER_KEYS_DIRECTORY', settings.SERVER_KEYS_DIRECTORY); + container.bindValue('USERS_DIRECTORY', settings.USERS_DIRECTORY); + container.bindValue('WEBHOOKS_DIRECTORY', settings.WEBHOOKS_DIRECTORY); + + // controllers + container.bindClass( + 'DeviceClaimsController', + DeviceClaimsController, + ['DeviceRepository'] + ); + container.bindClass( + 'DevicesController', + DevicesController, + ['DeviceRepository'] + ); + container.bindClass( + 'EventsController', + EventsController, + ['EventManager'] + ); + container.bindClass( + 'ProvisioningController', + ProvisioningController, + ['DeviceRepository'] + ); + container.bindClass( + 'UsersController', + UsersController, + ['UserRepository'] + ); + container.bindClass( + 'WebhooksController', + WebhooksController, + ['WebhookRepository'] + ); + + // managers + container.bindClass( + 'EventManager', + EventManager, + ['EventPublisher'] + ); + + // Repositories + container.bindClass( + 'DeviceFirmwareRepository', + DeviceFirmwareFileRepository, + ['FIRMWARE_DIRECTORY'] + ); + container.bindClass( + 'DeviceRepository', + DeviceRepository, + [ + 'DeviceAttributeRepository', + 'DeviceFirmwareRepository', + 'DeviceKeyRepository', + 'DeviceServer', + ] + ); + container.bindClass( + 'UserRepository', + UserFileRepository, + ['USERS_DIRECTORY'] + ); + container.bindClass( + 'WebhookRepository', + WebhookFileRepository, + ['WEBHOOKS_DIRECTORY'] + ); +}; diff --git a/src/interfaces.js b/src/interfaces.js new file mode 100644 index 00000000..37087ea0 --- /dev/null +++ b/src/interfaces.js @@ -0,0 +1,22 @@ + +class Abstract { + constructor() { + if (new.target === Abstract) { + throw new TypeError("Cannot construct Abstract instances directly"); + } + } +} + +class IDeviceFirmwareRepository extends Abstract {} +class IDeviceRepository extends Abstract {} +class IEventManager extends Abstract {} +class IUserRepository extends Abstract {} +class IWebhookRepository extends Abstract {} + +export { + IDeviceFirmwareRepository, + IDeviceRepository, + IEventManager, + IUserRepository, + IWebhookRepository, +}; diff --git a/src/lib/DeviceConnection.js b/src/lib/DeviceConnection.js deleted file mode 100644 index 1b8f07a9..00000000 --- a/src/lib/DeviceConnection.js +++ /dev/null @@ -1,17 +0,0 @@ -import {EventEmitter} from 'events'; - -let CONNECTION_COUNTER = 0; - -class DeviceConnection { - _socketId: number; - - constructor() { - this._socketId = CONNECTION_COUNTER++; - } - - async getStatus(deviceID: string): Object { - - } -} - -export default DeviceConnection; diff --git a/src/main.js b/src/main.js index 2f927748..10e62936 100644 --- a/src/main.js +++ b/src/main.js @@ -1,15 +1,10 @@ // @flow -import { - DeviceAttributeFileRepository, - DeviceKeyFileRepository, - DeviceServer, - EventPublisher, - ServerKeyFileRepository, -} from 'spark-protocol'; +import {Container} from 'constitute'; import utilities from './lib/utilities'; import logger from './lib/logger'; import createApp from './app'; +import defaultBindings from './defaultBindings'; import settings from './settings'; const NODE_PORT = process.env.NODE_PORT || 8080; @@ -28,34 +23,24 @@ process.on('uncaughtException', (exception: Error) => { logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); }); - -const deviceAttributeRepository = new DeviceAttributeFileRepository( - settings.deviceKeysDir, -); -const deviceKeyRepository = new DeviceKeyFileRepository( - settings.deviceKeysDir, -); -const serverKeyRepository = new ServerKeyFileRepository( - settings.serverKeysDir, - settings.serverKeyFile, -); -const eventPublisher = new EventPublisher(); - -const deviceServer = new DeviceServer( - deviceAttributeRepository, - deviceKeyRepository, - serverKeyRepository, - eventPublisher, - { - host: settings.HOST, - port: settings.PORT, - }, -); - -global.server = deviceServer; +/* This is the container used app-wide for dependency injection. If you want to + * override any of the implementations, create your module with the new + * implementation and use: + * + * container.bindAlias(DefaultImplementation, MyNewImplementation); + * + * You can also set a new value + * container.bindAlias(DefaultValue, 12345); + * + * See https://github.com/justmoon/constitute for more info + */ +const container = new Container(); +defaultBindings(container); + +const deviceServer = container.constitute('DeviceServer'); deviceServer.start(); -const app = createApp(settings, deviceServer, eventPublisher); +const app = createApp(container, settings); app.listen( NODE_PORT, diff --git a/src/settings.js b/src/settings.js index e9d68f1c..961f60a9 100644 --- a/src/settings.js +++ b/src/settings.js @@ -20,38 +20,34 @@ */ import path from 'path'; -import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository'; +import {Value} from 'constitute' +//import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository'; import WebhookFileRepository from './repository/WebhookFileRepository'; import UsersFileRepository from './repository/UserFileRepository'; export default { + DEVICE_DIRECTORY: path.join(__dirname, './data/deviceKeys'), + FIRMWARE_DIRECTORY: path.join(__dirname, './data/knownApps'), + SERVER_KEY_FILENAME: 'default_key.pem', + SERVER_KEYS_DIRECTORY: path.join(__dirname, './data'), + USERS_DIRECTORY: path.join(__dirname, './data/users'), + WEBHOOKS_DIRECTORY: path.join(__dirname, './data/webhooks'), + accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, coreRequestTimeout: 30000, coreSignalTimeout: 30000, - deviceKeysDir: path.join(__dirname, './data/deviceKeys'), isCoreOnlineTimeout: 2000, loginRoute: '/oauth/token', logRequests: true, maxHooksPerDevice: 10, maxHooksPerUser: 20, - deviceFirmwareRepository: new DeviceFirmwareFileRepository( - path.join(__dirname, './data/knownApps'), - ), - webhookRepository: new WebhookFileRepository( - path.join(__dirname, './data/webhooks'), - ), - usersRepository: new UsersFileRepository( - path.join(__dirname, './data/users'), - ), /** * Your server crypto keys! */ cryptoSalt: 'aes-128-cbc', - serverKeyFile: 'default_key.pem', - serverKeysDir: path.join(__dirname, './data'), serverKeyPassFile: null, serverKeyPassEnvVar: null, diff --git a/src/types.js b/src/types.js index 1532fc2a..968474cc 100644 --- a/src/types.js +++ b/src/types.js @@ -119,8 +119,6 @@ export type Settings = { coreRequestTimeout: number, coreSignalTimeout: number, cryptoSalt: string, - deviceFirmwareRepository: DeviceFirmwareRepository, - deviceKeysDir: string, HOST: string, isCoreOnlineTimeout: number, loginRoute: string, @@ -128,11 +126,8 @@ export type Settings = { maxHooksPerDevice: number, maxHooksPerUser: number, PORT: number, - serverKeyFile: string, serverKeyPassEnvVar: ?string, serverKeyPassFile: ?string, - usersRepository: UserRepository, - webhookRepository: Repository<*>, }; export type DeviceAttributeRepository = Repository & { diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index c240992e..b152d816 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -2,7 +2,6 @@ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; -import settings from './setup/settings'; const USER_CREDENTIALS = { password: 'password', @@ -134,8 +133,10 @@ test.serial('should claim device', async t => { // TODO write test for checking the error if device belongs to somebody else // TODO write tests for updateDevice & callFunction +// Used to get implementations +const container = app.container; test.after.always(async (): Promise => { - await settings.usersRepository.deleteById(testUser.id); - await settings.deviceAttributeRepository.deleteById(DEVICE_ID); - await settings.deviceKeyFileRepository.delete(DEVICE_ID); + await container.constitute('UserRepository').deleteById(testUser.id); + await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID); + await container.constitute('DeviceKeyRepository').delete(DEVICE_ID); }); diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js index 920a1373..081ca65c 100644 --- a/test/ProvisioningController.test.js +++ b/test/ProvisioningController.test.js @@ -2,7 +2,6 @@ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; -import settings from './setup/settings'; const USER_CREDENTIALS = { password: 'password', @@ -75,8 +74,10 @@ test('should throw an error if public key is not provided', async t => { t.is(response.body.error, 'No key provided'); }); +// Used to get implementations +const container = app.container; test.after.always(async (): Promise => { - await settings.usersRepository.deleteById(testUser.id); - await settings.deviceAttributeRepository.deleteById(DEVICE_ID); - await settings.deviceKeyFileRepository.delete(DEVICE_ID); + await container.constitute('UserRepository').deleteById(testUser.id); + await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID); + await container.constitute('DeviceKeyRepository').delete(DEVICE_ID); }); diff --git a/test/UsersController.test.js b/test/UsersController.test.js index 76c589d0..4bbaabec 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -4,7 +4,6 @@ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; -import settings from './setup/settings'; const USER_CREDENTIALS: UserCredentials = { password: 'password', @@ -31,7 +30,7 @@ test.serial('should throw an error if username already in use', async t => { .post('/v1/users') .send(USER_CREDENTIALS); t.is(response.status, 400); - t.is(response.body.error, 'user with the username is already exist'); + t.is(response.body.error, 'user with the username already exists'); }); test.serial('should login the user', async t => { @@ -81,6 +80,8 @@ test.serial('should deleteById access token for the user', async t => { )); }); +// Used to get implementations +const container = app.container; test.after.always(async (): Promise => { - await settings.usersRepository.deleteById(user.id); + await container.constitute('UserRepository').deleteById(user.id); }); diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index ef7296b8..d9fc0e04 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -157,7 +157,8 @@ test.serial('should delete webhook', async t => { )); }); +const container = app.container; test.after.always(async (): Promise => { - await settings.webhookRepository.deleteById(testWebhook.id); - await settings.usersRepository.deleteById(testUser.id); + await container.constitute('WebhookRepository').deleteById(testWebhook.id); + await container.constitute('UserRepository').deleteById(testUser.id); }); diff --git a/test/setup/settings.js b/test/setup/settings.js index 3f3660b5..898a30ba 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -7,10 +7,16 @@ import UsersFileRepository from '../../src/repository/UserFileRepository'; import { DeviceAttributeFileRepository, DeviceKeyFileRepository } from 'spark-protocol'; export default { + DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'), + FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'), + SERVER_KEY_FILENAME: 'default_key.pem', + SERVER_KEYS_DIRECTORY: path.join(__dirname, '../__test_data__'), + USERS_DIRECTORY: path.join(__dirname, '../__test_data__/users'), + WEBHOOKS_DIRECTORY: path.join(__dirname, '../__test_data__/webhooks'), + accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', coreFlashTimeout: 90000, - deviceKeysDir: path.join(__dirname, '../__test_data__/deviceKeys'), coreRequestTimeout: 30000, coreSignalTimeout: 30000, isCoreOnlineTimeout: 2000, @@ -18,21 +24,6 @@ export default { logRequests: false, maxHooksPerDevice: 10, maxHooksPerUser: 20, - deviceAttributeRepository: new DeviceAttributeFileRepository( - path.join(__dirname, '../__test_data__/deviceKeys'), - ), - deviceFirmwareRepository: new DeviceFirmwareFileRepository( - path.join(__dirname, '../__test_data__/knownApp'), - ), - deviceKeyFileRepository: new DeviceKeyFileRepository( - path.join(__dirname, '../__test_data__/deviceKeys'), - ), - usersRepository: new UsersFileRepository( - path.join(__dirname, '../__test_data__/users'), - ), - webhookRepository: new WebhookFileRepository( - path.join(__dirname, '../__test_data__/webhooks'), - ), /** * Your server crypto keys! diff --git a/test/setup/testApp.js b/test/setup/testApp.js index 5443eae4..b49f57f2 100644 --- a/test/setup/testApp.js +++ b/test/setup/testApp.js @@ -1,10 +1,26 @@ // @flow +import {Container} from 'constitute'; import createApp from '../../src/app'; import settings from './settings'; +import defaultBindings from '../../src/defaultBindings'; import DeviceServerMock from './DeviceServerMock'; -const deviceServer = new DeviceServerMock(); -const app = createApp(settings, deviceServer); +const container = new Container(); +// TODO - we should be creating different bindings per test so we can mock out +// different modules to test +defaultBindings(container); + +// settings +container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); +container.bindValue('FIRMWARE_DIRECTORY', settings.FIRMWARE_DIRECTORY); +container.bindValue('SERVER_KEY_FILENAME', settings.SERVER_KEY_FILENAME); +container.bindValue('SERVER_KEYS_DIRECTORY', settings.SERVER_KEYS_DIRECTORY); +container.bindValue('USERS_DIRECTORY', settings.USERS_DIRECTORY); +container.bindValue('WEBHOOKS_DIRECTORY', settings.WEBHOOKS_DIRECTORY); + +container.bindAlias('DeviceServer', DeviceServerMock); +const app = createApp(container, settings); +(app: any).container = container; export default app; From 4468d24a70103a95df9c033034ef8531d666bee9 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 8 Jan 2017 14:34:37 -0800 Subject: [PATCH 235/504] Controllers are now transient and are recreated for each request. --- src/RouteConfig.js | 19 ++++++++++++++----- src/defaultBindings.js | 27 +++++++++++++-------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 05287b52..6f06d0cb 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -107,10 +107,19 @@ export default ( const values = argumentNames .map((argument: string): string => request.params[argument]); - const controllerContext = Object.create(controller); - controllerContext.request = request; - controllerContext.response = response; - controllerContext.user = (request: any).user; + const controllerInstance = container.constitute(controllerName); + + // In order parallel requests on the controller, the state + // (request/response/user) must be added to the controller. + if (controllerInstance === controller) { + throw new Error( + '`Transient.with` must be used when binding controllers', + ); + } + + controllerInstance.request = request; + controllerInstance.response = response; + controllerInstance.user = (request: any).user; // Take access token out if it's posted. const { @@ -120,7 +129,7 @@ export default ( try { const functionResult = mappedFunction.call( - controllerContext, + controllerInstance, ...values, body, ); diff --git a/src/defaultBindings.js b/src/defaultBindings.js index 6d226078..c122c95c 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -2,9 +2,8 @@ import type {Container} from 'constitute'; -import { - defaultBindings, -} from 'spark-protocol'; +import {defaultBindings} from 'spark-protocol'; +import {Transient} from 'constitute'; import DeviceClaimsController from './controllers/DeviceClaimsController'; import DevicesController from './controllers/DevicesController'; import EventsController from './controllers/EventsController'; @@ -34,46 +33,46 @@ export default (container: Container): void => { container.bindClass( 'DeviceClaimsController', DeviceClaimsController, - ['DeviceRepository'] + Transient.with(['DeviceRepository']), ); container.bindClass( 'DevicesController', DevicesController, - ['DeviceRepository'] + Transient.with(['DeviceRepository']), ); container.bindClass( 'EventsController', EventsController, - ['EventManager'] + Transient.with(['EventManager']), ); container.bindClass( 'ProvisioningController', ProvisioningController, - ['DeviceRepository'] + Transient.with(['DeviceRepository']), ); container.bindClass( 'UsersController', UsersController, - ['UserRepository'] + Transient.with(['UserRepository']), ); container.bindClass( 'WebhooksController', WebhooksController, - ['WebhookRepository'] + Transient.with(['WebhookRepository']), ); // managers container.bindClass( 'EventManager', EventManager, - ['EventPublisher'] + ['EventPublisher'], ); // Repositories container.bindClass( 'DeviceFirmwareRepository', DeviceFirmwareFileRepository, - ['FIRMWARE_DIRECTORY'] + ['FIRMWARE_DIRECTORY'], ); container.bindClass( 'DeviceRepository', @@ -83,16 +82,16 @@ export default (container: Container): void => { 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'DeviceServer', - ] + ], ); container.bindClass( 'UserRepository', UserFileRepository, - ['USERS_DIRECTORY'] + ['USERS_DIRECTORY'], ); container.bindClass( 'WebhookRepository', WebhookFileRepository, - ['WEBHOOKS_DIRECTORY'] + ['WEBHOOKS_DIRECTORY'], ); }; From 28e06bd0c813e23c730203ba2351b3b1e1d5ecd7 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 8 Jan 2017 14:41:43 -0800 Subject: [PATCH 236/504] Cleaning app.js --- src/app.js | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/app.js b/src/app.js index 74793875..85b57b20 100644 --- a/src/app.js +++ b/src/app.js @@ -8,31 +8,12 @@ import type { NextFunction, } from 'express'; import type {Container} from 'constitute'; -import type { EventPublisher, DeviceServer } from 'spark-protocol'; -import type { Settings } from './types'; +import type {Settings} from './types'; import bodyParser from 'body-parser'; import express from 'express'; import morgan from 'morgan'; - -// Repositories -import DeviceRepository from './repository/DeviceRepository'; -import { - DeviceAttributeFileRepository, - DeviceKeyFileRepository, -} from 'spark-protocol'; - -// Managers -import EventManager from './managers/EventManager'; - -// Routing import routeConfig from './RouteConfig'; -import DeviceClaimsController from './controllers/DeviceClaimsController'; -import DevicesController from './controllers/DevicesController'; -import EventsController from './controllers/EventsController'; -import ProvisioningController from './controllers/ProvisioningController'; -import UsersController from './controllers/UsersController'; -import WebhooksController from './controllers/WebhooksController'; export default ( container: Container, From bd6535ee20d9ca2ebf4dfaede71eaa1a0d677e0a Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 9 Jan 2017 07:45:21 -0800 Subject: [PATCH 237/504] Added test data so we could run the tests in parallel --- package.json | 4 +-- test/DevicesController.test.js | 12 ++++++--- test/ProvisioningController.test.js | 10 +++++--- test/UsersController.test.js | 4 ++- test/WebhooksController.test.js | 4 ++- test/setup/TestData.js | 39 +++++++++++++++++++++++++++++ 6 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 test/setup/TestData.js diff --git a/package.json b/package.json index b0348384..5403e17c 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", "start:prod": "npm run build && node ./build/main.js", - "test": "ava --serial", - "test:watch": "ava --watch --serial" + "test": "ava", + "test:watch": "ava --watch" }, "pre-commit": "test", "ava": { diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index b152d816..f0251473 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -2,14 +2,14 @@ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; +import TestData from './setup/TestData'; -const USER_CREDENTIALS = { +let USER_CREDENTIALS = { password: 'password', username: 'deviceTestUser@test.com', }; - -const DEVICE_ID = '350023001951353337343732'; -const TEST_PUBLIC_KEY = +let DEVICE_ID = null; +let TEST_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----\n' + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsxJFqlUOxK5bsEfTtBCe9sXBa' + '43q9QoSPFXEG5qY/+udOpf2SKacgfUVdUbK4WOkLou7FQ+DffpwztBk5fWM9qfzF' + @@ -22,6 +22,10 @@ let userToken; let deviceToApiAttributes; test.before(async () => { + USER_CREDENTIALS = TestData.getUser(); + DEVICE_ID = TestData.getID(); + TEST_PUBLIC_KEY = TestData.getPublicKey(); + const userResponse = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js index 081ca65c..2145c585 100644 --- a/test/ProvisioningController.test.js +++ b/test/ProvisioningController.test.js @@ -2,14 +2,15 @@ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; +import TestData from './setup/TestData'; -const USER_CREDENTIALS = { +let USER_CREDENTIALS = { password: 'password', username: 'provisionTestUser@test.com', }; -const DEVICE_ID = '350023001951353337343733'; -const TEST_PUBLIC_KEY = +let DEVICE_ID = '350023001951353337343733'; +let TEST_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----\n' + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsxJFqlUOxK5bsEfTtBCe9sXBa' + '43q9QoSPFXEG5qY/+udOpf2SKacgfUVdUbK4WOkLou7FQ+DffpwztBk5fWM9qfzF' + @@ -21,6 +22,9 @@ let testUser; let userToken; test.before(async () => { + USER_CREDENTIALS = TestData.getUser(); + DEVICE_ID = TestData.getID(); + TEST_PUBLIC_KEY = TestData.getPublicKey(); const userResponse = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); diff --git a/test/UsersController.test.js b/test/UsersController.test.js index 4bbaabec..a0a0a9a4 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -4,8 +4,9 @@ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; +import TestData from './setup/TestData'; -const USER_CREDENTIALS: UserCredentials = { +let USER_CREDENTIALS: UserCredentials = { password: 'password', username: 'newUser@test.com', }; @@ -14,6 +15,7 @@ let user; let userToken; test.serial('should return a new user object', async t => { + USER_CREDENTIALS = TestData.getUser(); const response = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index d9fc0e04..ecd5a0ab 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -5,8 +5,9 @@ import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import settings from './setup/settings'; +import TestData from './setup/TestData'; -const USER_CREDENTIALS = { +let USER_CREDENTIALS = { password: 'password', username: 'webhookTestUser@test.com', }; @@ -22,6 +23,7 @@ let userToken; let testWebhook; test.before(async () => { + USER_CREDENTIALS = TestData.getUser(); const userResponse = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); diff --git a/test/setup/TestData.js b/test/setup/TestData.js new file mode 100644 index 00000000..8065892d --- /dev/null +++ b/test/setup/TestData.js @@ -0,0 +1,39 @@ +// @flow + +import uuid from 'uuid'; +import ursa from 'ursa'; + +const uuidSet = new Set(); +const privateKeys = new Set(); + +class TestData { + static getUser = (): {password: string, username: string} => { + return { + password: 'password', + username: `testUser+${TestData.getID()}@test.com`, + }; + }; + + static getID = (): string => { + let newID = uuid(); + while (uuidSet.has(newID)) { + newID = uuid(); + } + + uuidSet.add(newID); + return newID; + }; + + static getPublicKey = (): string => { + let key = ursa.generatePrivateKey(); + + while (privateKeys.has(key.toPrivatePem())) { + key = ursa.generatePrivateKey(); + } + + privateKeys.add(key.toPrivatePem()); + return key.toPublicPem(); + }; +} + +export default TestData; From 5e1aea7efaccce26915cb5e46604ae71bd86a154 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 10 Jan 2017 02:04:56 +0200 Subject: [PATCH 238/504] implement particle setup --- src/controllers/DeviceClaimsController.js | 28 +++++++++----- src/defaultBindings.js | 13 ++++--- src/repository/DeviceRepository.js | 12 +++--- src/repository/UserFileRepository.js | 45 ++++++++++++++++++++++- src/types.js | 4 ++ 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js index 70120e12..3f9cba19 100644 --- a/src/controllers/DeviceClaimsController.js +++ b/src/controllers/DeviceClaimsController.js @@ -1,35 +1,43 @@ // @flow -import type { Device, DeviceRepository } from '../types'; -import type { DeviceAPIType } from '../lib/deviceToAPI'; - +import type { + Device, + DeviceRepository, + UserRepository, +} from '../types'; import Controller from './Controller'; -import HttpError from '../lib/HttpError'; -import allowUpload from '../decorators/allowUpload'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; -import deviceToAPI from '../lib/deviceToAPI'; class DeviceClaimsController extends Controller { _deviceRepository: DeviceRepository; + _userRepository: UserRepository; + - constructor(deviceRepository: DeviceRepository) { + constructor( + deviceRepository: DeviceRepository, + userRepository: UserRepository, + ) { super(); + this._deviceRepository = deviceRepository; + this._userRepository = userRepository; } @httpVerb('post') @route('/v1/device_claims') - async claimDevice(postBody: { id: string }): Promise<*> { + async generateClaimCode(): Promise<*> { const claimCode = await this._deviceRepository.generateClaimCode( this.user.id, ); + + await this._userRepository.addClaimCode(this.user.id, claimCode); const devices = await this._deviceRepository.getAll(this.user.id); const deviceIDs = devices.map( - device => device.deviceID, + (device: Device): string => device.deviceID, ); - return this.ok({claim_code: claimCode, device_ids: deviceIDs}); + return this.ok({ claim_code: claimCode, device_ids: deviceIDs }); } } diff --git a/src/defaultBindings.js b/src/defaultBindings.js index c122c95c..f30c9903 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -1,9 +1,9 @@ // @flow -import type {Container} from 'constitute'; +import type { Container } from 'constitute'; -import {defaultBindings} from 'spark-protocol'; -import {Transient} from 'constitute'; +import { defaultBindings } from 'spark-protocol'; +import { Transient } from 'constitute'; import DeviceClaimsController from './controllers/DeviceClaimsController'; import DevicesController from './controllers/DevicesController'; import EventsController from './controllers/EventsController'; @@ -17,7 +17,7 @@ import UserFileRepository from './repository/UserFileRepository'; import WebhookFileRepository from './repository/WebhookFileRepository'; import settings from './settings'; -export default (container: Container): void => { +export default (container: Container) => { // spark protocol container bindings defaultBindings(container); @@ -33,7 +33,10 @@ export default (container: Container): void => { container.bindClass( 'DeviceClaimsController', DeviceClaimsController, - Transient.with(['DeviceRepository']), + Transient.with([ + 'DeviceRepository', + 'UserRepository', + ]), ); container.bindClass( 'DevicesController', diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 9a89a973..916642e6 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -16,6 +16,7 @@ import ursa from 'ursa'; import HttpError from '../lib/HttpError'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); +const CLAIM_CODE_LENGTH = 63; class DeviceRepository { _deviceAttributeRepository: DeviceAttributeRepository; @@ -55,11 +56,11 @@ class DeviceRepository { return await this._deviceAttributeRepository.update(attributesToSave); }; - generateClaimCode = async (userID: string): Promise => { - // TODO - we should probably save this to a repository so we can use it in - // subsequent requests - return crypto.randomBytes(63).toString(); - }; + generateClaimCode = (): string => + crypto + .randomBytes(CLAIM_CODE_LENGTH) + .toString('base64') + .substring(0, CLAIM_CODE_LENGTH); unclaimDevice = async ( deviceID: string, @@ -74,6 +75,7 @@ class DeviceRepository { const attributesToSave = { ...deviceAttributes, + claimCode: null, ownerID: null, }; return await this._deviceAttributeRepository.update(attributesToSave); diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js index f90106e0..845a834c 100644 --- a/src/repository/UserFileRepository.js +++ b/src/repository/UserFileRepository.js @@ -14,6 +14,37 @@ class UserFileRepository { this._fileManager = new JSONFileManager(path); } + addClaimCode = async ( + userID: string, + claimCode: string, + ): Promise => { + const user = await this.getById(userID); + if (!user) { + throw new Error('no user found'); + } + return await this.update({ + ...user, + claimCodes: [...user.claimCodes, claimCode], + }); + }; + + removeClaimCode = async ( + userID: string, + claimCode: string, + ): Promise => { + const user = await this.getById(userID); + if (!user) { + return null; + } + return await this.update({ + ...user, + claimCodes: user.claimCodes.filter( + (code: string): boolean => + code !== claimCode, + ), + }); + }; + createWithCredentials = async ( userCredentials: UserCredentials, ): Promise => { @@ -28,6 +59,7 @@ class UserFileRepository { const modelToSave = { accessTokens: [], + claimCodes: [], created_at: new Date(), created_by: null, id, @@ -44,8 +76,9 @@ class UserFileRepository { throw new HttpError('Not implemented'); }; - update = (user: User): Promise => { - throw new HttpError('Not implemented'); + update = async (model: User): Promise => { + this._fileManager.writeFile(`${model.id}.json`, model); + return model; }; getAll = async (): Promise> => @@ -54,6 +87,14 @@ class UserFileRepository { getById = async (id: string): Promise => this._fileManager.getFile(`${id}.json`); + getByClaimCode = async (claimCode: string): Promise => + (await this.getAll()).find( + (user: User): boolean => + user.claimCodes.some((code: string): boolean => + code === claimCode, + ), + ); + getByUsername = async (username: string): Promise => (await this.getAll()).find( (user: User): boolean => user.username === username, diff --git a/src/types.js b/src/types.js index 968474cc..a3e2fc0b 100644 --- a/src/types.js +++ b/src/types.js @@ -74,6 +74,7 @@ export type TokenObject = { export type User = { accessTokens: Array, + claimCodes: Array, created_at: Date, id: string, passwordHash: string, @@ -103,11 +104,14 @@ export type Repository = { }; export type UserRepository = Repository & { + addClaimCode(userID: string, claimCode: string): Promise, createWithCredentials(credentials: UserCredentials): Promise, deleteAccessToken(user: User, accessToken: string): Promise, getByAccessToken(accessToken: string): Promise, + getByClaimCode(claimCode: string): Promise, getByUsername(username: string): Promise, isUserNameInUse(username: string): Promise, + removeClaimCode(userID: string, claimCode: string): Promise, saveAccessToken(userId: string, tokenObject: TokenObject): Promise, validateLogin(username: string, password: string): Promise, }; From 67c8b231f1d58adfde735bb3d2fc1bab9408b723 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Tue, 10 Jan 2017 02:53:11 +0200 Subject: [PATCH 239/504] fix nit --- src/repository/UserFileRepository.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js index 845a834c..4d83cfc5 100644 --- a/src/repository/UserFileRepository.js +++ b/src/repository/UserFileRepository.js @@ -40,7 +40,7 @@ class UserFileRepository { ...user, claimCodes: user.claimCodes.filter( (code: string): boolean => - code !== claimCode, + code !== claimCode, ), }); }; From 5c2415c6f3f0cd10b4485644b0dc2690892a4838 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 9 Jan 2017 22:58:31 -0800 Subject: [PATCH 240/504] Added a script for downloading binaries and creating a settings file. --- package.json | 8 +- scripts/update-firmware-binaries.js | 163 ++++++++++++++++++++++++++++ src/settings.js | 9 +- 3 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 scripts/update-firmware-binaries.js diff --git a/package.json b/package.json index 5403e17c..a4786197 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "scripts": { "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", + "update": "node ./scripts/update-firmware-binaries", "lint": "eslint -- .", + "postinstall": "npm run update-firmware", "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", "start:prod": "npm run build && node ./build/main.js", @@ -86,10 +88,14 @@ "eslint-plugin-import": "^2.2.0", "eslint-plugin-sorting": "^0.3.0", "flow-bin": "^0.37.0", + "github": "^7.3.2", "nodemon": "^1.11.0", "pre-commit": "^1.2.2", + "request": "^2.79.0", "rimraf": "^2.5.4", + "rmfr": "^1.0.1", "supertest": "^2.0.1", - "supertest-as-promised": "^4.0.2" + "supertest-as-promised": "^4.0.2", + "uglifyjs": "^2.4.10" } } diff --git a/scripts/update-firmware-binaries.js b/scripts/update-firmware-binaries.js new file mode 100644 index 00000000..c1a5cbc5 --- /dev/null +++ b/scripts/update-firmware-binaries.js @@ -0,0 +1,163 @@ +// @flow + +const fs = require('fs'); +const github = require('github'); +const mkdirp = require('mkdirp'); +const request = require('request'); +const rmfr = require('rmfr'); +const settings = require('../src/settings'); + +const GITHUB_USER = 'spark'; +const GITHUB_REPOSITORY = 'firmware'; +const SETTINGS_FILE = settings.BINARIES_DIRECTORY + '/settings.json'; + +// This default is here so that the regex will work when updating these files. +const DEFAULT_SETTINGS = { + knownApps: { + 'deep_update_2014_06': true, + 'cc3000': true, + 'cc3000_1_14': true, + 'tinker': true, + 'voodoo': true + }, + knownPlatforms: { + '0': 'Core', + '6': 'Photon', + '8': 'P1', + '10': 'Electron', + '88': 'Duo', + '103': 'Bluz' + }, + updates: { + '2b04:d006': { + systemFirmwareOne: 'system-part1-0.6.0-photon.bin', + systemFirmwareTwo: 'system-part2-0.6.0-photon.bin' + }, + '2b04:d008': { + systemFirmwareOne: 'system-part1-0.6.0-p1.bin', + systemFirmwareTwo: 'system-part2-0.6.0-p1.bin' + }, + '2b04:d00a': { + // The bin files MUST be in this order to be flashed to the correct memory locations + systemFirmwareOne: 'system-part2-0.6.0-electron.bin', + systemFirmwareTwo: 'system-part3-0.6.0-electron.bin', + systemFirmwareThree: 'system-part1-0.6.0-electron.bin' + } + }, +}; + +const exitWithMessage = message => { + console.log(message); + process.exit(0); +}; + +const cleanBinariesDirectory = () => { + return rmfr(settings.BINARIES_DIRECTORY); +}; + +const exitWithJSON = json => { + exitWithMessage(JSON.stringify(json, null, 2)); +} + +const downloadFile = url => { + return new Promise((resolve, reject) => { + const filename = url.match(/.*\/(.*)/)[1]; + console.log('Downloading ' + filename + '...'); + const file = fs.createWriteStream( + settings.BINARIES_DIRECTORY + + '/' + + filename + ); + file.on('finish', () => file.close(() => resolve(filename))); + request(url).pipe(file).on('error', exitWithJSON); + }); +}; + +const downloadFirmwareBinaries = assets => { + return Promise.all(assets.map(asset => { + if (asset.name.match(/^system-part/)) { + return downloadFile(asset.browser_download_url); + } + }).filter(item => item)); +}; + +const updateSettings = () => { + let versionNumber = versionTag; + if (versionNumber[0] === 'v') { + versionNumber = versionNumber.substr(1); + } + + if (!fs.exists(SETTINGS_FILE)) { + fs.writeFileSync( + SETTINGS_FILE, + JSON.stringify(DEFAULT_SETTINGS, null, 2), + { flag: 'wx' } + ); + } + + let settings = fs.readFileSync(SETTINGS_FILE, 'utf8'); + const settingsBinaries = []; + settings = settings.replace( + /(system-part\d-).*(-.*.bin)/g, + (filename, part, device) => { + var newFilename = part + versionNumber + device; + settingsBinaries.push(newFilename); + return newFilename; + } + ); + + + fs.writeFileSync(SETTINGS_FILE, settings, 'utf8'); + console.log('Updated settings.js'); + + return settingsBinaries; +}; + +const verifyBinariesMatch = data => { + const downloadedBinaries = data.downloadedBinaries.sort(); + const settingsBinaries = data.settingsBinaries.sort(); + if (JSON.stringify(downloadedBinaries) !== JSON.stringify(settingsBinaries)) { + console.log( + '\n\nWARNING: the list of downloaded binaries doesn\'t match the list ' + + 'of binaries in settings.js' + ); + console.log('Downloaded: ' + downloadedBinaries); + console.log('settings.js: ' + settingsBinaries); + } +} + +// Start running process. If you pass `0.6.0` it will install that version of +// the firmware. +let versionTag = process.argv[2]; +if (versionTag && versionTag[0] !== 'v') { + versionTag = 'v' + versionTag; +} + +const githubAPI = new github(); + +const promise = process.argv.length !== 3 + ? githubAPI.repos.getLatestRelease({ + owner: GITHUB_USER, + repo: GITHUB_REPOSITORY + }) + : githubAPI.repos.getReleaseByTag({ + owner: GITHUB_USER, + repo: GITHUB_REPOSITORY, + tag: versionTag, + }); + +promise.then(release => { + versionTag = release.tag_name; + return cleanBinariesDirectory() + .then(() => { + if (!fs.existsSync(settings.BINARIES_DIRECTORY)) { + mkdirp.sync(settings.BINARIES_DIRECTORY); + } + return downloadFirmwareBinaries(release.assets); + }); +}) +.then(downloadedBinaries => ({ + downloadedBinaries, + settingsBinaries: updateSettings(), +})) +.then(fileData => verifyBinariesMatch(fileData)) diff --git a/src/settings.js b/src/settings.js index 961f60a9..5dde24e6 100644 --- a/src/settings.js +++ b/src/settings.js @@ -19,13 +19,10 @@ * */ -import path from 'path'; -import {Value} from 'constitute' -//import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository'; -import WebhookFileRepository from './repository/WebhookFileRepository'; -import UsersFileRepository from './repository/UserFileRepository'; +const path = require('path'); -export default { +module.exports = { + BINARIES_DIRECTORY: path.join(__dirname, './data/binaries'), DEVICE_DIRECTORY: path.join(__dirname, './data/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, './data/knownApps'), SERVER_KEY_FILENAME: 'default_key.pem', From e34de22e54c6d770266b0c4dacf3682fbfb2e10e Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Tue, 10 Jan 2017 07:06:45 -0800 Subject: [PATCH 241/504] Correctly fetch the latest release production release. Added the ability to download known apps. --- scripts/update-firmware-binaries.js | 79 ++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/scripts/update-firmware-binaries.js b/scripts/update-firmware-binaries.js index c1a5cbc5..14bfbeb1 100644 --- a/scripts/update-firmware-binaries.js +++ b/scripts/update-firmware-binaries.js @@ -8,7 +8,8 @@ const rmfr = require('rmfr'); const settings = require('../src/settings'); const GITHUB_USER = 'spark'; -const GITHUB_REPOSITORY = 'firmware'; +const GITHUB_FIRMWARE_REPOSITORY = 'firmware'; +const GITHUB_CLI_REPOSITORY = 'particle-cli'; const SETTINGS_FILE = settings.BINARIES_DIRECTORY + '/settings.json'; // This default is here so that the regex will work when updating these files. @@ -126,6 +127,16 @@ const verifyBinariesMatch = data => { } } +const downloadAppBinaries = () => { + githubAPI.repos.getContent({ + owner: GITHUB_USER, + repo: GITHUB_CLI_REPOSITORY, + path: 'binaries' + }).then(assets => Promise.all( + assets.map(asset => downloadFile(asset.download_url)) + )).catch(console.log); +} + // Start running process. If you pass `0.6.0` it will install that version of // the firmware. let versionTag = process.argv[2]; @@ -135,29 +146,59 @@ if (versionTag && versionTag[0] !== 'v') { const githubAPI = new github(); -const promise = process.argv.length !== 3 - ? githubAPI.repos.getLatestRelease({ - owner: GITHUB_USER, - repo: GITHUB_REPOSITORY +const clearBinariesPromise = cleanBinariesDirectory().then(() => { + if (!fs.existsSync(settings.BINARIES_DIRECTORY)) { + mkdirp.sync(settings.BINARIES_DIRECTORY); + } +}); + +// Download app binaries +const appPromise = clearBinariesPromise.then(() => downloadAppBinaries()); + +// Download firmware binaries +const promise = clearBinariesPromise.then( + () => process.argv.length !== 3 + ? githubAPI.repos.getTags({ + owner: GITHUB_USER, + page: 0, + perPage: 30, + repo: GITHUB_FIRMWARE_REPOSITORY + }).then(tags => { + tags = tags.filter(tag => + !tag.name.includes('-rc') && + !tag.name.includes('-pi') + ); + + tags.sort((a, b) => { + if (a.name < b.name) { + return 1; + } + if (a.name > b.name) { + return -1; + } + return 0; + }); + + versionTag = tags[0].name; + }) + : Promise.resolve() +).then(() => + githubAPI.repos.getReleaseByTag({ + owner: GITHUB_USER, + repo: GITHUB_FIRMWARE_REPOSITORY, + tag: versionTag }) - : githubAPI.repos.getReleaseByTag({ - owner: GITHUB_USER, - repo: GITHUB_REPOSITORY, - tag: versionTag, - }); +); promise.then(release => { - versionTag = release.tag_name; - return cleanBinariesDirectory() - .then(() => { - if (!fs.existsSync(settings.BINARIES_DIRECTORY)) { - mkdirp.sync(settings.BINARIES_DIRECTORY); - } - return downloadFirmwareBinaries(release.assets); - }); + return downloadFirmwareBinaries(release.assets); }) .then(downloadedBinaries => ({ downloadedBinaries, settingsBinaries: updateSettings(), })) -.then(fileData => verifyBinariesMatch(fileData)) +.then(fileData => verifyBinariesMatch(fileData)); + +Promise.all([appPromise, promise]) + .then(() => console.log('\r\nCompleted Sync')) + .catch(console.log); From b6d64ef662e21a8fee64d7687a4c5364c23f7cae Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Tue, 10 Jan 2017 07:27:27 -0800 Subject: [PATCH 242/504] Firmware sync now grabs settings for flashing. --- scripts/update-firmware-binaries.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/scripts/update-firmware-binaries.js b/scripts/update-firmware-binaries.js index 14bfbeb1..dd7b87ad 100644 --- a/scripts/update-firmware-binaries.js +++ b/scripts/update-firmware-binaries.js @@ -10,7 +10,8 @@ const settings = require('../src/settings'); const GITHUB_USER = 'spark'; const GITHUB_FIRMWARE_REPOSITORY = 'firmware'; const GITHUB_CLI_REPOSITORY = 'particle-cli'; -const SETTINGS_FILE = settings.BINARIES_DIRECTORY + '/settings.json'; +const SPECIFICATIONS_FILE = settings.BINARIES_DIRECTORY + '/specifications.js'; +const SETTINGS_FILE = settings.BINARIES_DIRECTORY + '/settings.js'; // This default is here so that the regex will work when updating these files. const DEFAULT_SETTINGS = { @@ -91,7 +92,7 @@ const updateSettings = () => { if (!fs.exists(SETTINGS_FILE)) { fs.writeFileSync( SETTINGS_FILE, - JSON.stringify(DEFAULT_SETTINGS, null, 2), + `module.exports = ${JSON.stringify(DEFAULT_SETTINGS, null, 2)};`, { flag: 'wx' } ); } @@ -156,7 +157,7 @@ const clearBinariesPromise = cleanBinariesDirectory().then(() => { const appPromise = clearBinariesPromise.then(() => downloadAppBinaries()); // Download firmware binaries -const promise = clearBinariesPromise.then( +const firmwarePromise = clearBinariesPromise.then( () => process.argv.length !== 3 ? githubAPI.repos.getTags({ owner: GITHUB_USER, @@ -190,7 +191,7 @@ const promise = clearBinariesPromise.then( }) ); -promise.then(release => { +firmwarePromise.then(release => { return downloadFirmwareBinaries(release.assets); }) .then(downloadedBinaries => ({ @@ -199,6 +200,18 @@ promise.then(release => { })) .then(fileData => verifyBinariesMatch(fileData)); -Promise.all([appPromise, promise]) +const specificationsPromise = githubAPI.repos.getContent({ + owner: GITHUB_USER, + path: 'lib/deviceSpecs/specifications.js', + repo: GITHUB_CLI_REPOSITORY +}).then(response => { + fs.writeFileSync( + SPECIFICATIONS_FILE, + new Buffer(response.content, 'base64').toString(), + { flag: 'wx' } + ); +}) + +Promise.all([appPromise, firmwarePromise, specificationsPromise]) .then(() => console.log('\r\nCompleted Sync')) .catch(console.log); From 0325646dc76d66e2b227cddcdd92c45e7fe02686 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 11 Jan 2017 02:21:38 +0200 Subject: [PATCH 243/504] implement claimCodeManager, remove claimCodes from User; --- src/controllers/DeviceClaimsController.js | 17 ++++------ src/defaultBindings.js | 2 +- src/repository/DeviceRepository.js | 9 ----- src/repository/UserFileRepository.js | 40 ----------------------- src/types.js | 5 --- 5 files changed, 7 insertions(+), 66 deletions(-) diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js index 3f9cba19..f3609d71 100644 --- a/src/controllers/DeviceClaimsController.js +++ b/src/controllers/DeviceClaimsController.js @@ -1,10 +1,7 @@ // @flow -import type { - Device, - DeviceRepository, - UserRepository, -} from '../types'; +import type { Device, DeviceRepository } from '../types'; +import type { ClaimCodeManager } from 'spark-protocol'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; @@ -12,27 +9,25 @@ import route from '../decorators/route'; class DeviceClaimsController extends Controller { _deviceRepository: DeviceRepository; - _userRepository: UserRepository; - + _claimCodeManager: ClaimCodeManager; constructor( deviceRepository: DeviceRepository, - userRepository: UserRepository, + claimCodeManager: ClaimCodeManager, ) { super(); this._deviceRepository = deviceRepository; - this._userRepository = userRepository; + this._claimCodeManager = claimCodeManager; } @httpVerb('post') @route('/v1/device_claims') async generateClaimCode(): Promise<*> { - const claimCode = await this._deviceRepository.generateClaimCode( + const claimCode = this._claimCodeManager.addClaimCode( this.user.id, ); - await this._userRepository.addClaimCode(this.user.id, claimCode); const devices = await this._deviceRepository.getAll(this.user.id); const deviceIDs = devices.map( (device: Device): string => device.deviceID, diff --git a/src/defaultBindings.js b/src/defaultBindings.js index f30c9903..b579057c 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -35,7 +35,7 @@ export default (container: Container) => { DeviceClaimsController, Transient.with([ 'DeviceRepository', - 'UserRepository', + 'ClaimCodeManager', ]), ); container.bindClass( diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 916642e6..a363a958 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -10,13 +10,11 @@ import type { } from '../types'; import type DeviceFirmwareRepository from './DeviceFirmwareFileRepository'; -import crypto from 'crypto'; import Moniker from 'moniker'; import ursa from 'ursa'; import HttpError from '../lib/HttpError'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); -const CLAIM_CODE_LENGTH = 63; class DeviceRepository { _deviceAttributeRepository: DeviceAttributeRepository; @@ -56,12 +54,6 @@ class DeviceRepository { return await this._deviceAttributeRepository.update(attributesToSave); }; - generateClaimCode = (): string => - crypto - .randomBytes(CLAIM_CODE_LENGTH) - .toString('base64') - .substring(0, CLAIM_CODE_LENGTH); - unclaimDevice = async ( deviceID: string, userID: string, @@ -75,7 +67,6 @@ class DeviceRepository { const attributesToSave = { ...deviceAttributes, - claimCode: null, ownerID: null, }; return await this._deviceAttributeRepository.update(attributesToSave); diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js index 4d83cfc5..bd6cb931 100644 --- a/src/repository/UserFileRepository.js +++ b/src/repository/UserFileRepository.js @@ -14,37 +14,6 @@ class UserFileRepository { this._fileManager = new JSONFileManager(path); } - addClaimCode = async ( - userID: string, - claimCode: string, - ): Promise => { - const user = await this.getById(userID); - if (!user) { - throw new Error('no user found'); - } - return await this.update({ - ...user, - claimCodes: [...user.claimCodes, claimCode], - }); - }; - - removeClaimCode = async ( - userID: string, - claimCode: string, - ): Promise => { - const user = await this.getById(userID); - if (!user) { - return null; - } - return await this.update({ - ...user, - claimCodes: user.claimCodes.filter( - (code: string): boolean => - code !== claimCode, - ), - }); - }; - createWithCredentials = async ( userCredentials: UserCredentials, ): Promise => { @@ -59,7 +28,6 @@ class UserFileRepository { const modelToSave = { accessTokens: [], - claimCodes: [], created_at: new Date(), created_by: null, id, @@ -87,14 +55,6 @@ class UserFileRepository { getById = async (id: string): Promise => this._fileManager.getFile(`${id}.json`); - getByClaimCode = async (claimCode: string): Promise => - (await this.getAll()).find( - (user: User): boolean => - user.claimCodes.some((code: string): boolean => - code === claimCode, - ), - ); - getByUsername = async (username: string): Promise => (await this.getAll()).find( (user: User): boolean => user.username === username, diff --git a/src/types.js b/src/types.js index a3e2fc0b..6e8a7664 100644 --- a/src/types.js +++ b/src/types.js @@ -74,7 +74,6 @@ export type TokenObject = { export type User = { accessTokens: Array, - claimCodes: Array, created_at: Date, id: string, passwordHash: string, @@ -104,14 +103,11 @@ export type Repository = { }; export type UserRepository = Repository & { - addClaimCode(userID: string, claimCode: string): Promise, createWithCredentials(credentials: UserCredentials): Promise, deleteAccessToken(user: User, accessToken: string): Promise, getByAccessToken(accessToken: string): Promise, - getByClaimCode(claimCode: string): Promise, getByUsername(username: string): Promise, isUserNameInUse(username: string): Promise, - removeClaimCode(userID: string, claimCode: string): Promise, saveAccessToken(userId: string, tokenObject: TokenObject): Promise, validateLogin(username: string, password: string): Promise, }; @@ -146,7 +142,6 @@ export type DeviceRepository = { functionArguments: Object, ): Promise<*>, claimDevice(deviceID: string, userID: string): Promise, - generateClaimCode(userID: string): Promise, flashBinary(deviceID: string, files: File): Promise<*>, flashKnownApp(deviceID: string, userID: string, app: string): Promise<*>, getAll(userID: string): Promise>, From 00ba787fae6198d35c84082a0aa5d551245ce2f9 Mon Sep 17 00:00:00 2001 From: AntonPuko Date: Wed, 11 Jan 2017 03:03:58 +0200 Subject: [PATCH 244/504] fix nit --- src/controllers/DeviceClaimsController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js index f3609d71..560bdef5 100644 --- a/src/controllers/DeviceClaimsController.js +++ b/src/controllers/DeviceClaimsController.js @@ -23,8 +23,8 @@ class DeviceClaimsController extends Controller { @httpVerb('post') @route('/v1/device_claims') - async generateClaimCode(): Promise<*> { - const claimCode = this._claimCodeManager.addClaimCode( + async createClaimCode(): Promise<*> { + const claimCode = this._claimCodeManager.createClaimCode( this.user.id, ); From 3b455562cf5a7bfe68d2f9c4c8847c6f1fedbf97 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Tue, 10 Jan 2017 20:54:17 -0800 Subject: [PATCH 245/504] Working on firmware manager. This still doesn't work correctly -- For some reason the update fails on the first chunk sent. --- package.json | 3 +- scripts/update-firmware-binaries.js | 231 +++++++++++++++------------ src/controllers/DevicesController.js | 2 +- src/managers/FirmwareManager.js | 89 +++++++++++ src/repository/DeviceRepository.js | 22 +++ 5 files changed, 244 insertions(+), 103 deletions(-) create mode 100644 src/managers/FirmwareManager.js diff --git a/package.json b/package.json index a4786197..d0df8179 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "scripts": { "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", - "update": "node ./scripts/update-firmware-binaries", + "update-firmware": "babel-node ./scripts/update-firmware-binaries", "lint": "eslint -- .", "postinstall": "npm run update-firmware", "prebuild": "npm run build:clean", @@ -51,6 +51,7 @@ }, "dependencies": { "basic-auth-parser": "0.0.2", + "binary-version-reader": "^0.5.0", "body-parser": "^1.15.2", "constitute": "^1.6.2", "express": "^4.14.0", diff --git a/scripts/update-firmware-binaries.js b/scripts/update-firmware-binaries.js index dd7b87ad..328d406d 100644 --- a/scripts/update-firmware-binaries.js +++ b/scripts/update-firmware-binaries.js @@ -1,17 +1,24 @@ // @flow -const fs = require('fs'); -const github = require('github'); -const mkdirp = require('mkdirp'); -const request = require('request'); -const rmfr = require('rmfr'); -const settings = require('../src/settings'); +import fs from 'fs'; +import github from 'github'; +import mkdirp from 'mkdirp'; +import request from 'request'; +import rmfr from 'rmfr'; +import settings from '../src/settings'; +import nullthrows from 'nullthrows'; + +type Asset = { + browser_download_url: string, + name: string, +}; const GITHUB_USER = 'spark'; const GITHUB_FIRMWARE_REPOSITORY = 'firmware'; const GITHUB_CLI_REPOSITORY = 'particle-cli'; +const MAPPING_FILE = settings.BINARIES_DIRECTORY + '/versions.json'; const SPECIFICATIONS_FILE = settings.BINARIES_DIRECTORY + '/specifications.js'; -const SETTINGS_FILE = settings.BINARIES_DIRECTORY + '/settings.js'; +const SETTINGS_FILE = settings.BINARIES_DIRECTORY + '/settings.json'; // This default is here so that the regex will work when updating these files. const DEFAULT_SETTINGS = { @@ -48,22 +55,25 @@ const DEFAULT_SETTINGS = { }, }; -const exitWithMessage = message => { +let versionTag = ''; +const githubAPI = new github(); + +const exitWithMessage = (message: string): void => { console.log(message); process.exit(0); }; -const cleanBinariesDirectory = () => { - return rmfr(settings.BINARIES_DIRECTORY); +const cleanBinariesDirectory = async (): Promise<*> => { + return rmfr(settings.BINARIES_DIRECTORY + '/'); }; -const exitWithJSON = json => { +const exitWithJSON = (json: Object): void => { exitWithMessage(JSON.stringify(json, null, 2)); } -const downloadFile = url => { +const downloadFile = (url: string): Promise<*> => { return new Promise((resolve, reject) => { - const filename = url.match(/.*\/(.*)/)[1]; + const filename = nullthrows(url.match(/.*\/(.*)/))[1]; console.log('Downloading ' + filename + '...'); const file = fs.createWriteStream( settings.BINARIES_DIRECTORY + @@ -75,29 +85,26 @@ const downloadFile = url => { }); }; -const downloadFirmwareBinaries = assets => { - return Promise.all(assets.map(asset => { +const downloadFirmwareBinaries = async ( + assets: Array, +): Promise<*> => { + const assetFileNames = await Promise.all(assets.map(asset => { if (asset.name.match(/^system-part/)) { return downloadFile(asset.browser_download_url); } - }).filter(item => item)); + return ''; + })); + + return assetFileNames.filter(item => item); }; -const updateSettings = () => { +const updateSettings = (): Array => { let versionNumber = versionTag; if (versionNumber[0] === 'v') { versionNumber = versionNumber.substr(1); } - if (!fs.exists(SETTINGS_FILE)) { - fs.writeFileSync( - SETTINGS_FILE, - `module.exports = ${JSON.stringify(DEFAULT_SETTINGS, null, 2)};`, - { flag: 'wx' } - ); - } - - let settings = fs.readFileSync(SETTINGS_FILE, 'utf8'); + let settings = JSON.stringify(DEFAULT_SETTINGS, null, 2); const settingsBinaries = []; settings = settings.replace( /(system-part\d-).*(-.*.bin)/g, @@ -108,110 +115,132 @@ const updateSettings = () => { } ); - - fs.writeFileSync(SETTINGS_FILE, settings, 'utf8'); - console.log('Updated settings.js'); + fs.writeFileSync(SETTINGS_FILE, settings, { flag: 'wx' }); + console.log('Updated settings'); return settingsBinaries; }; -const verifyBinariesMatch = data => { - const downloadedBinaries = data.downloadedBinaries.sort(); - const settingsBinaries = data.settingsBinaries.sort(); +const verifyBinariesMatch = ( + downloadedBinaries: Array, + settingsBinaries: Array, +): void => { + downloadedBinaries = downloadedBinaries.sort(); + settingsBinaries = settingsBinaries.sort(); if (JSON.stringify(downloadedBinaries) !== JSON.stringify(settingsBinaries)) { console.log( '\n\nWARNING: the list of downloaded binaries doesn\'t match the list ' + 'of binaries in settings.js' ); - console.log('Downloaded: ' + downloadedBinaries); - console.log('settings.js: ' + settingsBinaries); + console.log('Downloaded: ', downloadedBinaries); + console.log('settings.js: ', settingsBinaries); } -} +}; -const downloadAppBinaries = () => { - githubAPI.repos.getContent({ +const downloadAppBinaries = async (): Promise<*> => { + const assets = await githubAPI.repos.getContent({ owner: GITHUB_USER, repo: GITHUB_CLI_REPOSITORY, path: 'binaries' - }).then(assets => Promise.all( - assets.map(asset => downloadFile(asset.download_url)) - )).catch(console.log); -} + }); -// Start running process. If you pass `0.6.0` it will install that version of -// the firmware. -let versionTag = process.argv[2]; -if (versionTag && versionTag[0] !== 'v') { - versionTag = 'v' + versionTag; -} + return await Promise.all( + assets.map(asset => downloadFile(asset.download_url)), + ); +}; -const githubAPI = new github(); +(async (): Promise<*> => { + // Start running process. If you pass `0.6.0` it will install that version of + // the firmware. + versionTag = process.argv[2]; + if (versionTag && versionTag[0] !== 'v') { + versionTag = 'v' + versionTag; + } -const clearBinariesPromise = cleanBinariesDirectory().then(() => { + + await cleanBinariesDirectory(); if (!fs.existsSync(settings.BINARIES_DIRECTORY)) { mkdirp.sync(settings.BINARIES_DIRECTORY); } -}); -// Download app binaries -const appPromise = clearBinariesPromise.then(() => downloadAppBinaries()); + // Download app binaries + await downloadAppBinaries(); -// Download firmware binaries -const firmwarePromise = clearBinariesPromise.then( - () => process.argv.length !== 3 - ? githubAPI.repos.getTags({ + // Download firmware binaries + if (process.argv.length !== 3) { + let tags = await githubAPI.repos.getTags({ owner: GITHUB_USER, page: 0, perPage: 30, repo: GITHUB_FIRMWARE_REPOSITORY - }).then(tags => { - tags = tags.filter(tag => - !tag.name.includes('-rc') && - !tag.name.includes('-pi') - ); - - tags.sort((a, b) => { - if (a.name < b.name) { - return 1; - } - if (a.name > b.name) { - return -1; - } - return 0; - }); - - versionTag = tags[0].name; }) - : Promise.resolve() -).then(() => - githubAPI.repos.getReleaseByTag({ - owner: GITHUB_USER, - repo: GITHUB_FIRMWARE_REPOSITORY, - tag: versionTag - }) -); - -firmwarePromise.then(release => { - return downloadFirmwareBinaries(release.assets); -}) -.then(downloadedBinaries => ({ - downloadedBinaries, - settingsBinaries: updateSettings(), -})) -.then(fileData => verifyBinariesMatch(fileData)); - -const specificationsPromise = githubAPI.repos.getContent({ - owner: GITHUB_USER, - path: 'lib/deviceSpecs/specifications.js', - repo: GITHUB_CLI_REPOSITORY -}).then(response => { + tags = tags.filter(tag => + // Don't use release candidates.. we only need main releases. + !tag.name.includes('-rc') && + !tag.name.includes('-pi') + ); + + tags.sort((a, b) => { + if (a.name < b.name) { + return 1; + } + if (a.name > b.name) { + return -1; + } + return 0; + }); + + versionTag = tags[0].name; + } + + const release = await githubAPI.repos.getReleaseByTag({ + owner: GITHUB_USER, + repo: GITHUB_FIRMWARE_REPOSITORY, + tag: versionTag + }); + + const downloadedBinaries = await downloadFirmwareBinaries(release.assets); + const settingsBinaries = await updateSettings(); + verifyBinariesMatch(downloadedBinaries, settingsBinaries); + + const specificationsResponse = await githubAPI.repos.getContent({ + owner: GITHUB_USER, + path: 'lib/deviceSpecs/specifications.js', + repo: GITHUB_CLI_REPOSITORY + }); + fs.writeFileSync( SPECIFICATIONS_FILE, - new Buffer(response.content, 'base64').toString(), + new Buffer(specificationsResponse.content, 'base64').toString(), + { flag: 'wx' } + ); + + const versionResponse = await githubAPI.repos.getContent({ + owner: GITHUB_USER, + path: 'system/system-versions.md', + repo: GITHUB_FIRMWARE_REPOSITORY + }) + + const versionText = new Buffer(versionResponse.content, 'base64').toString(); + const startIndex = versionText.indexOf('| 0 '); + const endIndex = versionText.indexOf('\n\n', startIndex); + const data = versionText + .substring(startIndex, endIndex) + .replace(/\s/g, '') + .split('|'); + + const mapping = []; + for (var i = 0; i < data.length; i += 4) { + if (!data[i+1]) { + continue; + } + mapping.push([data[i+1], data[i+2]]); + } + fs.writeFileSync( + MAPPING_FILE, + JSON.stringify(mapping, null, 2), { flag: 'wx' } ); -}) -Promise.all([appPromise, firmwarePromise, specificationsPromise]) - .then(() => console.log('\r\nCompleted Sync')) - .catch(console.log); + console.log('\r\nCompleted Sync') +})(); diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index b0de8a7f..ec9c37fe 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -115,7 +115,7 @@ class DevicesController extends Controller { if (postBody.app_id) { const flashStatus = await this._deviceRepository.flashKnownApp( deviceID, - this.user.id, + this.user.id, postBody.app_id, ); diff --git a/src/managers/FirmwareManager.js b/src/managers/FirmwareManager.js new file mode 100644 index 00000000..1165b193 --- /dev/null +++ b/src/managers/FirmwareManager.js @@ -0,0 +1,89 @@ +// @flow + +import {HalDescribeParser} from 'binary-version-reader'; + +// TODO - these should be written to a non-configurable folder like +// `../third-party` +import settings from '../data/binaries/settings'; +import specifications from '../data/binaries/specifications'; +import versions from '../data/binaries/versions'; + +type OtaUpdate = { + address: string, + alt: string, + binaryFileName: string, +}; + +const platformSettings = Object.entries(specifications); +const SPECIFICATION_KEY_BY_PLATFORM = new Map( + Object.values(settings.knownPlatforms).map( + platform => { + const spec = platformSettings.find( + ([key, value]) => (value: any).productName === platform, + ); + + return [platform, spec && spec[0]] + }, + ).filter(item => item[1]), +); +const MAX_RELEASE_VERSION = Math.max( + ...versions + // Filter out Release Candidates and only use release versions + .filter(version => !version[1].includes('rc')) + .map(version => Number.parseInt(version[0])), +); + +class FirmwareManager { + _modules: Array; + _platform: string; + _systemVersion: number; + + constructor(description: Object) { + const parser = new HalDescribeParser(); + this._platform = settings.knownPlatforms[description.p + '']; + this._systemVersion = parser.getSystemVersion(description); + this._modules = parser.getModules(description); + } + + getModules(): Array { + return this._modules; + } + + getPlatform(): string { + return this._platform; + } + + getSystemVersion(): number { + return this._systemVersion; + } + + // Gets OTA updates if the device needs to be updated + getOtaUpdateConfig(): ?Array { + if (this._systemVersion >= MAX_RELEASE_VERSION) { + //return null; + } + + const key = SPECIFICATION_KEY_BY_PLATFORM.get(this._platform); + + if (!key) { + return null; + } + + const firmwareSettings = settings.updates[key]; + if (!key) { + return null; + } + + const firmwareKeys = Object.keys(firmwareSettings); + return firmwareKeys.map(firmwareKey => ({ + ...specifications[key][firmwareKey], + binaryFileName: firmwareSettings[firmwareKey], + })); + } + + getKnownAppFileName(): ?string { + throw new Error('getKnownAppFileName has not been implemented.') + } +} + +export default FirmwareManager; diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 916642e6..bd19fe48 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -10,10 +10,13 @@ import type { } from '../types'; import type DeviceFirmwareRepository from './DeviceFirmwareFileRepository'; +import fs from 'fs'; import crypto from 'crypto'; import Moniker from 'moniker'; import ursa from 'ursa'; import HttpError from '../lib/HttpError'; +import FirmwareManager from '../managers/FirmwareManager'; +import settings from '../settings'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); const CLAIM_CODE_LENGTH = 63; @@ -204,6 +207,25 @@ class DeviceRepository { throw new HttpError('Could not get device for ID', 404); } + // TODO make FirmwareManager stateless + const firmwareManager = new FirmwareManager(device.getSystemInformation()); + const otaUpdateConfig = firmwareManager.getOtaUpdateConfig(); + + console.log(otaUpdateConfig); + + if (otaUpdateConfig) { + // TODO use a repository instead of just fetching from disk + for (var i = 0; i < otaUpdateConfig.length; i++) { + const config = otaUpdateConfig[i]; + const file = fs.readFileSync( + settings.BINARIES_DIRECTORY + '/' + config.binaryFileName, + ); + console.log('FLASHING', file.length, config.binaryFileName) + await device.flash(file, config.address); + await new Promise(resolve => setTimeout(() => resolve(), 2000)); + }; + } + return await device.flash(file.buffer); }; From 2901e506f3f69111e4c1cc37566946471df2d165 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 12 Jan 2017 01:09:04 +0200 Subject: [PATCH 246/504] implement base WebhookManager functionality --- src/controllers/WebhooksController.js | 29 ++++-- src/defaultBindings.js | 8 +- src/managers/WebhookManager.js | 117 ++++++++++++++++++++++++ src/repository/WebhookFileRepository.js | 30 +++++- src/types.js | 1 + 5 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 src/managers/WebhookManager.js diff --git a/src/controllers/WebhooksController.js b/src/controllers/WebhooksController.js index 44f18c48..f22e9ad4 100644 --- a/src/controllers/WebhooksController.js +++ b/src/controllers/WebhooksController.js @@ -1,11 +1,10 @@ // @flow import type { - Repository, RequestType, - Webhook, WebhookMutator, } from '../types'; +import type WebhookManager from '../managers/WebhookManager'; import Controller from './Controller'; import HttpError from '../lib/HttpError'; @@ -34,24 +33,31 @@ const validateWebhookMutator = (webhookMutator: WebhookMutator): ?HttpError => { }; class WebhooksController extends Controller { - _webhookRepository: Repository; + _webhookManager: WebhookManager; - constructor(webhookRepository: Repository) { + constructor(webhookManager: WebhookManager) { super(); - this._webhookRepository = webhookRepository; + this._webhookManager = webhookManager; } @httpVerb('get') @route('/v1/webhooks') async getAll(): Promise<*> { - return this.ok(await this._webhookRepository.getAll()); + return this.ok( + await this._webhookManager.getAll(this.user.id), + ); } @httpVerb('get') @route('/v1/webhooks/:webhookId') async getById(webhookId: string): Promise<*> { - return this.ok(await this._webhookRepository.getById(webhookId)); + return this.ok( + await this._webhookManager.getByID( + webhookId, + this.user.id, + ), + ); } @httpVerb('post') @@ -62,7 +68,10 @@ class WebhooksController extends Controller { throw validateError; } - const newWebhook = await this._webhookRepository.create(model); + const newWebhook = await this._webhookManager.create({ + ...model, + ownerID: this.user.id, + }); return this.ok({ created_at: newWebhook.created_at, @@ -76,8 +85,8 @@ class WebhooksController extends Controller { @httpVerb('delete') @route('/v1/webhooks/:webhookId') async deleteById(webhookId: string): Promise<*> { - this._webhookRepository.deleteById(webhookId); - return this.ok(); + await this._webhookManager.deleteByID(webhookId, this.user.id); + return this.ok({ ok: true }); } } diff --git a/src/defaultBindings.js b/src/defaultBindings.js index b579057c..495b6d53 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -10,6 +10,7 @@ import EventsController from './controllers/EventsController'; import ProvisioningController from './controllers/ProvisioningController'; import UsersController from './controllers/UsersController'; import WebhooksController from './controllers/WebhooksController'; +import WebhookManager from './managers/WebhookManager'; import EventManager from './managers/EventManager'; import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository'; import DeviceRepository from './repository/DeviceRepository'; @@ -61,7 +62,7 @@ export default (container: Container) => { container.bindClass( 'WebhooksController', WebhooksController, - Transient.with(['WebhookRepository']), + Transient.with(['WebhookManager']), ); // managers @@ -70,6 +71,11 @@ export default (container: Container) => { EventManager, ['EventPublisher'], ); + container.bindClass( + 'WebhookManager', + WebhookManager, + ['WebhookRepository', 'EventPublisher'], + ); // Repositories container.bindClass( diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js new file mode 100644 index 00000000..d9afa0f4 --- /dev/null +++ b/src/managers/WebhookManager.js @@ -0,0 +1,117 @@ +// @flow + +import type { + Event, + Repository, + Webhook, + WebhookMutator, +} from '../types'; +import type { EventPublisher } from 'spark-protocol'; + +import request from 'request'; +import logger from '../lib/logger'; +import HttpError from '../lib/HttpError'; + +class WebhookManager { + _eventPublisher: EventPublisher; + _subscriptionIDsByWebhookID: Map = new Map(); + _webhookRepository: Repository; + + constructor( + webhookRepository: Repository, + eventPublisher: EventPublisher, + ) { + this._webhookRepository = webhookRepository; + this._eventPublisher = eventPublisher; + + (async (): Promise => this._init())(); + } + + _init = async (): Promise => { + const allWebhooks = await this._webhookRepository.getAll(); + allWebhooks.forEach( + (webhook: Webhook): void => this._subscribeWebhook(webhook), + ); + }; + + _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => + (event: Event) => { + try { + const responseHandler = (error, response) => { + if (error) { + // todo block the webhook calls after some amount of fails + // on 1 min or so.. + // todo responseTemplates + throw error; + } + }; + + // todo request <-> webhooks options + request({ + body: webhook.json, + formData: webhook.form, + headers: webhook.headers, + json: !!webhook.json, + method: webhook.requestType, + url: webhook.url, + }, responseHandler); + } catch (error) { + logger.error(`webhook error: ${error}`); + } + }; + + _subscribeWebhook = (webhook: Webhook) => { + const subscriptionID = this._eventPublisher.subscribe( + webhook.event, + this._onNewWebhookEvent(webhook), + // todo separate filtering for MY_DEVICES and for public/private events + { + deviceID: webhook.deviceID, + userID: webhook.ownerID, + }, + ); + this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID); + }; + + _unsubscribeWebhookByID = (webhookID: string) => { + const subscriptionID = this._subscriptionIDsByWebhookID.get(webhookID); + if (!subscriptionID) { + return; + } + + this._eventPublisher.unsubscribe(subscriptionID); + this._subscriptionIDsByWebhookID.delete(webhookID); + }; + + create = async (model: WebhookMutator): Promise => { + const webhook = await this._webhookRepository.create(model); + this._subscribeWebhook(webhook); + return webhook; + }; + + deleteByID = async ( + webhookID: string, + userID: string, + ): Promise => { + const webhook = await this._webhookRepository.getById(webhookID, userID); + if (!webhook) { + throw new HttpError('no webhook found', 404); + } + await this._webhookRepository.deleteById(webhookID); + this._unsubscribeWebhookByID(webhookID); + }; + + getAll = async (userID: string): Promise> => + await this._webhookRepository.getAll(userID); + + getByID = async (webhookID: string, userID: string): Promise => { + const webhook = await this._webhookRepository.getById(webhookID, userID); + if (!webhook) { + throw new HttpError('no webhook found', 404); + } + + return webhook; + }; +} + +export default WebhookManager; diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js index 035761d6..53e8945d 100644 --- a/src/repository/WebhookFileRepository.js +++ b/src/repository/WebhookFileRepository.js @@ -32,11 +32,33 @@ class WebhookFileRepository { deleteById = async (id: string): Promise => this._fileManager.deleteFile(`${id}.json`); - getAll = async (): Promise> => - this._fileManager.getAllData(); + getAll = async (userID: ?string = null): Promise> => { + const allData = this._fileManager.getAllData(); - getById = async (id: string): Promise => - this._fileManager.getFile(`${id}.json`); + if (userID) { + return Promise.resolve( + allData.filter( + (webhook: Webhook): boolean => + webhook.ownerID === userID, + ), + ); + } + return allData; + }; + + getById = async ( + id: string, + userID: ?string = null, + ): Promise => { + const webhook = this._fileManager.getFile(`${id}.json`); + if ( + !webhook || + webhook.ownerID !== userID + ) { + return null; + } + return webhook; + }; update = async (model: WebhookMutator): Promise => { throw new HttpError('Not implemented'); diff --git a/src/types.js b/src/types.js index 6e8a7664..86386212 100644 --- a/src/types.js +++ b/src/types.js @@ -18,6 +18,7 @@ export type WebhookMutator = { json?: { [key: string]: Object }, mydevices?: boolean, noDefaults?: boolean, + ownerID: string, productIdOrSlug?: string, query?: { [key: string]: Object }, rejectUnauthorized?: boolean, From 90852495d9d6073df4d2b4b04ff7a9275141a884 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 12 Jan 2017 01:26:30 +0200 Subject: [PATCH 247/504] fix nits --- src/repository/WebhookFileRepository.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js index 53e8945d..fc6af4a2 100644 --- a/src/repository/WebhookFileRepository.js +++ b/src/repository/WebhookFileRepository.js @@ -36,11 +36,9 @@ class WebhookFileRepository { const allData = this._fileManager.getAllData(); if (userID) { - return Promise.resolve( - allData.filter( - (webhook: Webhook): boolean => + return allData.filter( + (webhook: Webhook): boolean => webhook.ownerID === userID, - ), ); } return allData; From 75d6f9db895d15f6b3d6ba135645e38eaa67b613 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 13 Jan 2017 03:09:07 +0200 Subject: [PATCH 248/504] implement base hogan templates --- package.json | 1 + src/managers/WebhookManager.js | 109 ++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index d0df8179..72b6483c 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "constitute": "^1.6.2", "express": "^4.14.0", "express-oauth-server": "^2.0.0-b1", + "hogan.js": "^3.0.2", "moment": "*", "moniker": "^0.1.2", "morgan": "^1.7.0", diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index d9afa0f4..f9548b9c 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -8,9 +8,10 @@ import type { } from '../types'; import type { EventPublisher } from 'spark-protocol'; -import request from 'request'; -import logger from '../lib/logger'; +import hogan from 'hogan.js'; import HttpError from '../lib/HttpError'; +import logger from '../lib/logger'; +import request from 'request'; class WebhookManager { _eventPublisher: EventPublisher; @@ -37,23 +38,111 @@ class WebhookManager { _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => (event: Event) => { try { - const responseHandler = (error, response) => { + const defaultWebhookVariables = { + // todo add old defaults for compatibility + PARTICLE_DEVICE_ID: event.deviceID, + PARTICLE_EVENT_NAME: event.name, + PARTICLE_EVENT_VALUE: event.data, + PARTICLE_PUBLISHED_AT: event.publishedAt, + }; + + let eventDataVariables = {}; + if (typeof event.data === 'string') { + try { + eventDataVariables = JSON.parse(event.data); + } catch (error) { + eventDataVariables = {}; + } + } + + const webhookVariablesObject = webhook.noDefaults + ? eventDataVariables + : { + ...defaultWebhookVariables, + ...eventDataVariables, + }; + + const requestJSON = webhook.json && JSON.parse( + hogan + .compile(JSON.stringify(webhook.json)) + .render(webhookVariablesObject), + ); + + + const requestFormData = webhook.form && JSON.parse( + hogan + .compile(JSON.stringify(webhook.form)) + .render(webhookVariablesObject), + ); + + const requestUrl = hogan + .compile(webhook.url) + .render(webhookVariablesObject); + + const requestQuery = webhook.query && JSON.parse( + hogan + .compile(JSON.stringify(webhook.query)) + .render(webhookVariablesObject), + ); + + const responseTopic = webhook.responseTopic && hogan + .compile(webhook.responseTopic) + .render(webhookVariablesObject); + + const errorResponseTopic = webhook.errorResponseTopic && hogan + .compile(webhook.responseTopic) + .render(webhookVariablesObject); + + const responseHandler = ( + error: ?Error, + response: http$IncomingMessage, + responseBody: string | Buffer | Object, + ) => { if (error) { - // todo block the webhook calls after some amount of fails + // todo block the webhook calls after 10 fails // on 1 min or so.. - // todo responseTemplates + if (errorResponseTopic) { + this._eventPublisher.publish({ + // todo not sure if we need to provide deviceID here + deviceID: event.deviceID, + name: errorResponseTopic, + ttl: 60, + }); + } throw error; } + + this._eventPublisher.publish({ + // todo not sure if we need to provide deviceID here + deviceID: event.deviceID, + name: `hook-sent/${event.name}`, + ttl: 60, + }); + + const responseTemplate = webhook.responseTemplate && hogan + .compile(webhook.responseTemplate) + .render(responseBody); + + if (responseTopic) { + this._eventPublisher.publish({ + // todo not sure if we need to provide deviceID here + data: webhook.responseTemplate && responseTemplate, + deviceID: event.deviceID, + name: responseTopic, + ttl: 60, + }); + } }; - // todo request <-> webhooks options request({ - body: webhook.json, - formData: webhook.form, + body: requestJSON, + formData: requestFormData, headers: webhook.headers, - json: !!webhook.json, + json: true, method: webhook.requestType, - url: webhook.url, + qs: requestQuery, + url: requestUrl, + // todo add auth }, responseHandler); } catch (error) { logger.error(`webhook error: ${error}`); From f300c4f233c853f68af17b81c23aee0954810415 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 13 Jan 2017 12:54:32 +0200 Subject: [PATCH 249/504] fix ttl --- src/controllers/EventsController.js | 2 +- src/managers/WebhookManager.js | 12 ++---------- src/types.js | 5 +++-- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index b95d4be0..db5401cd 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -93,7 +93,7 @@ class EventsController extends Controller { name: string, data: ?Object, private: boolean, - ttl: number, + ttl?: number, }): Promise<*> { const eventData: EventData = { data: postBody.data, diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index f9548b9c..98f418d6 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -34,10 +34,11 @@ class WebhookManager { (webhook: Webhook): void => this._subscribeWebhook(webhook), ); }; - + // todo figure MY_DEVICES webhooks. _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => (event: Event) => { try { + const defaultWebhookVariables = { // todo add old defaults for compatibility PARTICLE_DEVICE_ID: event.deviceID, @@ -103,20 +104,14 @@ class WebhookManager { // on 1 min or so.. if (errorResponseTopic) { this._eventPublisher.publish({ - // todo not sure if we need to provide deviceID here - deviceID: event.deviceID, name: errorResponseTopic, - ttl: 60, }); } throw error; } this._eventPublisher.publish({ - // todo not sure if we need to provide deviceID here - deviceID: event.deviceID, name: `hook-sent/${event.name}`, - ttl: 60, }); const responseTemplate = webhook.responseTemplate && hogan @@ -125,11 +120,8 @@ class WebhookManager { if (responseTopic) { this._eventPublisher.publish({ - // todo not sure if we need to provide deviceID here data: webhook.responseTemplate && responseTemplate, - deviceID: event.deviceID, name: responseTopic, - ttl: 60, }); } }; diff --git a/src/types.js b/src/types.js index 86386212..30722148 100644 --- a/src/types.js +++ b/src/types.js @@ -48,15 +48,16 @@ export type DeviceAttributes = { }; export type Event = EventData & { + ttl: number, publishedAt: Date, }; export type EventData = { - data: ?Object, + data: ?Object | string, deviceID?: ?string, isPublic: boolean, name: string, - ttl: number, + ttl?: number, userID?: ?string, }; From 5c97e859f5626ea76b1d4be6f0f6a0cd94c75fcf Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 13 Jan 2017 13:13:02 +0200 Subject: [PATCH 250/504] add MyDevices support for webhooks and responseEvents --- src/managers/WebhookManager.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 98f418d6..fd5e2b13 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -34,10 +34,16 @@ class WebhookManager { (webhook: Webhook): void => this._subscribeWebhook(webhook), ); }; - // todo figure MY_DEVICES webhooks. + _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => (event: Event) => { try { + if ( + webhook.mydevices && + webhook.ownerID !== event.userID + ) { + return; + } const defaultWebhookVariables = { // todo add old defaults for compatibility @@ -105,6 +111,7 @@ class WebhookManager { if (errorResponseTopic) { this._eventPublisher.publish({ name: errorResponseTopic, + userID: event.userID, }); } throw error; @@ -112,6 +119,7 @@ class WebhookManager { this._eventPublisher.publish({ name: `hook-sent/${event.name}`, + userID: event.userID, }); const responseTemplate = webhook.responseTemplate && hogan @@ -122,6 +130,7 @@ class WebhookManager { this._eventPublisher.publish({ data: webhook.responseTemplate && responseTemplate, name: responseTopic, + userID: event.userID, }); } }; From 13f2b4d70612ca4dcd8c209331bd94b087d7ba24 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 13 Jan 2017 13:23:06 +0200 Subject: [PATCH 251/504] add old default webhook event names for compatibility --- src/managers/WebhookManager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index fd5e2b13..bece20bc 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -46,11 +46,15 @@ class WebhookManager { } const defaultWebhookVariables = { - // todo add old defaults for compatibility PARTICLE_DEVICE_ID: event.deviceID, PARTICLE_EVENT_NAME: event.name, PARTICLE_EVENT_VALUE: event.data, PARTICLE_PUBLISHED_AT: event.publishedAt, + // old event names, added for compatibility + SPARK_CORE_ID: event.deviceID, + SPARK_EVENT_NAME: event.name, + SPARK_EVENT_VALUE: event.data, + SPARK_PUBLISHED_AT: event.publishedAt, }; let eventDataVariables = {}; From 1014b1100633c6870b20dbbe9758678a4d23b24a Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 13 Jan 2017 08:10:26 -0800 Subject: [PATCH 252/504] Disabling OTA system updates. --- src/repository/DeviceRepository.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index b7c91004..f47ae997 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -200,7 +200,7 @@ class DeviceRepository { // TODO make FirmwareManager stateless const firmwareManager = new FirmwareManager(device.getSystemInformation()); - const otaUpdateConfig = null; // firmwareManager.getOtaUpdateConfig(); + const otaUpdateConfig = null;// firmwareManager.getOtaUpdateConfig(); console.log(otaUpdateConfig); @@ -208,15 +208,14 @@ class DeviceRepository { // TODO use a repository instead of just fetching from disk for (var i = 0; i < otaUpdateConfig.length; i++) { const config = otaUpdateConfig[i]; - const file = fs.readFileSync( + const systemFile = fs.readFileSync( settings.BINARIES_DIRECTORY + '/' + config.binaryFileName, ); - console.log('FLASHING', file.length, config.binaryFileName) - await device.flash(file, config.address); - await new Promise(resolve => setTimeout(() => resolve(), 2000)); + console.log('FLASHING', systemFile.length, config.binaryFileName) + await device.flash(systemFile, config.address); + await new Promise(resolve => setTimeout(() => resolve(), 15000)); }; } - return await device.flash(file.buffer); }; From 19b1df283ebaf03692012ebb018577455c683035 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 14 Jan 2017 02:39:55 +0200 Subject: [PATCH 253/504] implement webhook requests throttling; pass eventData to request body if webhook.json/webhook.form does not exist; --- package.json | 1 + src/managers/WebhookManager.js | 90 ++++++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 72b6483c..2142f81f 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "express": "^4.14.0", "express-oauth-server": "^2.0.0-b1", "hogan.js": "^3.0.2", + "lodash": "^4.17.4", "moment": "*", "moniker": "^0.1.2", "morgan": "^1.7.0", diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index bece20bc..94c5dce1 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -12,10 +12,15 @@ import hogan from 'hogan.js'; import HttpError from '../lib/HttpError'; import logger from '../lib/logger'; import request from 'request'; +import throttle from 'lodash/throttle'; + +const MAX_WEBHOOK_ERRORS_COUNT = 10; +const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; class WebhookManager { _eventPublisher: EventPublisher; _subscriptionIDsByWebhookID: Map = new Map(); + _errorsCountByWebhookID: Map = new Map(); _webhookRepository: Repository; constructor( @@ -35,6 +40,23 @@ class WebhookManager { ); }; + _incrementWebhookErrorCounter = (webhookID: string) => { + const errorsCount = this._errorsCountByWebhookID.get(webhookID) || 0; + this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); + }; + + _resetWebhookErrorCounter = (webhookID: string): void => + this._errorsCountByWebhookID.set(webhookID, 0); + + // todo annotate arguments + _webhookHandler = ( + requestOptions: Object, + responseHandler: Function, + ): void => request(requestOptions, responseHandler); + + _throttledWebhookHandler = + throttle(this._webhookHandler, WEBHOOK_THROTTLE_TIME); + _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => (event: Event) => { try { @@ -58,14 +80,13 @@ class WebhookManager { }; let eventDataVariables = {}; - if (typeof event.data === 'string') { - try { - eventDataVariables = JSON.parse(event.data); - } catch (error) { - eventDataVariables = {}; - } + try { + eventDataVariables = JSON.parse(event.data); + } catch (error) { + eventDataVariables = {}; } + const webhookVariablesObject = webhook.noDefaults ? eventDataVariables : { @@ -79,7 +100,6 @@ class WebhookManager { .render(webhookVariablesObject), ); - const requestFormData = webhook.form && JSON.parse( hogan .compile(JSON.stringify(webhook.form)) @@ -97,12 +117,12 @@ class WebhookManager { ); const responseTopic = webhook.responseTopic && hogan - .compile(webhook.responseTopic) - .render(webhookVariablesObject); + .compile(webhook.responseTopic) + .render(webhookVariablesObject); const errorResponseTopic = webhook.errorResponseTopic && hogan - .compile(webhook.responseTopic) - .render(webhookVariablesObject); + .compile(webhook.responseTopic) + .render(webhookVariablesObject) || `hook-error/${event.name}`; const responseHandler = ( error: ?Error, @@ -110,17 +130,20 @@ class WebhookManager { responseBody: string | Buffer | Object, ) => { if (error) { - // todo block the webhook calls after 10 fails - // on 1 min or so.. - if (errorResponseTopic) { - this._eventPublisher.publish({ - name: errorResponseTopic, - userID: event.userID, - }); - } + this._incrementWebhookErrorCounter(webhook.id); + + this._eventPublisher.publish({ + data: error.message, + name: errorResponseTopic, + userID: event.userID, + }); + + return; throw error; } + this._resetWebhookErrorCounter(webhook.id); + this._eventPublisher.publish({ name: `hook-sent/${event.name}`, userID: event.userID, @@ -139,18 +162,35 @@ class WebhookManager { } }; - request({ - body: requestJSON, + + const requestOptions = { + body: requestJSON || event.data, formData: requestFormData, headers: webhook.headers, - json: true, + json: !!requestJSON, method: webhook.requestType, qs: requestQuery, url: requestUrl, // todo add auth - }, responseHandler); - } catch (error) { - logger.error(`webhook error: ${error}`); + }; + + const isWebhookDisabled = + this._errorsCountByWebhookID.get(webhook.id) >= MAX_WEBHOOK_ERRORS_COUNT; + + if (isWebhookDisabled) { + this._eventPublisher.publish({ + data: 'Too many errors, webhook disabled', + name: errorResponseTopic, + userID: event.userID, + }); + + this._throttledWebhookHandler(requestOptions, responseHandler); + } else { + this._webhookHandler(requestOptions, responseHandler); + } + } + catch (error) { + logger.error(`webhookError: ${error}`); } }; From 085010cbe65b9af5e8fbfa4a9b266f848395a3ad Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 14 Jan 2017 03:36:14 +0200 Subject: [PATCH 254/504] add webhook.auth --- src/managers/WebhookManager.js | 2 +- src/types.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 94c5dce1..b3853d6b 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -164,6 +164,7 @@ class WebhookManager { const requestOptions = { + auth: webhook.auth, body: requestJSON || event.data, formData: requestFormData, headers: webhook.headers, @@ -171,7 +172,6 @@ class WebhookManager { method: webhook.requestType, qs: requestQuery, url: requestUrl, - // todo add auth }; const isWebhookDisabled = diff --git a/src/types.js b/src/types.js index 30722148..1e87169d 100644 --- a/src/types.js +++ b/src/types.js @@ -9,7 +9,7 @@ export type Webhook = WebhookMutator & { }; export type WebhookMutator = { - auth?: { Authorization: string }, + auth?: { password: string, username: string }, deviceID?: string, errorResponseTopic?: string, event: string, From e5b587d35a16dda6aea3e9316264ddaf04664db3 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 13 Jan 2017 17:38:48 -0800 Subject: [PATCH 255/504] Fixed flow errors --- package.json | 2 +- src/repository/DeviceRepository.js | 2 +- src/repository/WebhookFileRepository.js | 2 +- src/types.js | 20 ++++++++++++++++++-- test/setup/SparkCoreMock.js | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d0df8179..b1e9ed67 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "morgan": "^1.7.0", "multer": "^1.2.1", "request": "*", - "spark-protocol": "../spark-protocol", + "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git", "ursa": "*", "uuid": "^3.0.1", "when": "*", diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index f47ae997..126e3535 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -176,7 +176,7 @@ class DeviceRepository { deviceID: string, userID: string, varName: string, - ): Promise => { + ): Promise<*> => { if (!await this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { throw new HttpError('No device found', 404); } diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js index 035761d6..8fe7768d 100644 --- a/src/repository/WebhookFileRepository.js +++ b/src/repository/WebhookFileRepository.js @@ -13,7 +13,7 @@ class WebhookFileRepository { this._fileManager = new JSONFileManager(path); } - create = async (model: WebhookMutator): Promise => { + create = async (model: $Shape): Promise => { let id = uuid(); while (await this.getById(id)) { id = uuid(); diff --git a/src/types.js b/src/types.js index 6e8a7664..fa746e5b 100644 --- a/src/types.js +++ b/src/types.js @@ -3,9 +3,25 @@ import type { File } from 'express'; import type DeviceFirmwareRepository from './repository/DeviceFirmwareFileRepository'; -export type Webhook = WebhookMutator & { +export type Webhook = { + auth?: { Authorization: string }, created_at: Date, + deviceID?: string, + errorResponseTopic?: string, + event: string, + form?: { [key: string]: Object }, + headers?: { [key: string]: string }, id: string, + json?: { [key: string]: Object }, + mydevices?: boolean, + noDefaults?: boolean, + productIdOrSlug?: string, + query?: { [key: string]: Object }, + rejectUnauthorized?: boolean, + requestType: RequestType, + responseTemplate?: string, + responseTopic?: string, + url: string, }; export type WebhookMutator = { @@ -95,7 +111,7 @@ export type Device = DeviceAttributes & { }; export type Repository = { - create: (model: TModel) => Promise, + create: (model: TModel | $Shape) => Promise, deleteById: (id: string) => Promise, getAll: () => Promise>, getById: (id: string) => Promise, diff --git a/test/setup/SparkCoreMock.js b/test/setup/SparkCoreMock.js index 590be515..6d2c887a 100644 --- a/test/setup/SparkCoreMock.js +++ b/test/setup/SparkCoreMock.js @@ -5,7 +5,7 @@ class SparkCoreMock { return true; } - getVariableValue = (): Object => 0; + getVariableValue = (): any => 0; getDescription = (): Object => ({ firmware_version: '0.6.0', From 45e9bc29ae93e5fb5219abc8652c2c7b4a46dcaa Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 14 Jan 2017 03:47:04 +0200 Subject: [PATCH 256/504] add throttling options --- src/managers/WebhookManager.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index b3853d6b..957fac74 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -54,8 +54,11 @@ class WebhookManager { responseHandler: Function, ): void => request(requestOptions, responseHandler); - _throttledWebhookHandler = - throttle(this._webhookHandler, WEBHOOK_THROTTLE_TIME); + _throttledWebhookHandler = throttle( + this._webhookHandler, + WEBHOOK_THROTTLE_TIME, + { leading: false, trailing: true }, + ); _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => (event: Event) => { @@ -138,7 +141,6 @@ class WebhookManager { userID: event.userID, }); - return; throw error; } @@ -188,8 +190,7 @@ class WebhookManager { } else { this._webhookHandler(requestOptions, responseHandler); } - } - catch (error) { + } catch (error) { logger.error(`webhookError: ${error}`); } }; From 408f0fc1e67fa560ed49a578049ca63e976d58ac Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 14 Jan 2017 04:27:22 +0200 Subject: [PATCH 257/504] fix Flow errors --- src/managers/WebhookManager.js | 8 +++++--- src/types.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 957fac74..f0fe4fe0 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -45,7 +45,7 @@ class WebhookManager { this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); }; - _resetWebhookErrorCounter = (webhookID: string): void => + _resetWebhookErrorCounter = (webhookID: string): Map => this._errorsCountByWebhookID.set(webhookID, 0); // todo annotate arguments @@ -84,7 +84,9 @@ class WebhookManager { let eventDataVariables = {}; try { - eventDataVariables = JSON.parse(event.data); + if (event.data) { + eventDataVariables = JSON.parse(event.data); + } } catch (error) { eventDataVariables = {}; } @@ -177,7 +179,7 @@ class WebhookManager { }; const isWebhookDisabled = - this._errorsCountByWebhookID.get(webhook.id) >= MAX_WEBHOOK_ERRORS_COUNT; + (this._errorsCountByWebhookID.get(webhook.id) || 0) >= MAX_WEBHOOK_ERRORS_COUNT; if (isWebhookDisabled) { this._eventPublisher.publish({ diff --git a/src/types.js b/src/types.js index 1e87169d..a82004db 100644 --- a/src/types.js +++ b/src/types.js @@ -53,7 +53,7 @@ export type Event = EventData & { }; export type EventData = { - data: ?Object | string, + data?: string, deviceID?: ?string, isPublic: boolean, name: string, From 5fd570d78fcdf66593b2444553737fe3d88c4035 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 13 Jan 2017 21:38:35 -0800 Subject: [PATCH 258/504] Moved firmware manager to spark-protocol --- package.json | 5 - scripts/update-firmware-binaries.js | 246 ---------------------------- src/managers/FirmwareManager.js | 89 ---------- src/managers/WebhookManager.js | 3 +- src/repository/DeviceRepository.js | 19 --- src/settings.js | 1 - 6 files changed, 2 insertions(+), 361 deletions(-) delete mode 100644 scripts/update-firmware-binaries.js delete mode 100644 src/managers/FirmwareManager.js diff --git a/package.json b/package.json index f4756c1c..cbe48212 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,7 @@ "scripts": { "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", - "update-firmware": "babel-node ./scripts/update-firmware-binaries", "lint": "eslint -- .", - "postinstall": "npm run update-firmware", "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", "start:prod": "npm run build && node ./build/main.js", @@ -91,12 +89,9 @@ "eslint-plugin-import": "^2.2.0", "eslint-plugin-sorting": "^0.3.0", "flow-bin": "^0.37.0", - "github": "^7.3.2", "nodemon": "^1.11.0", "pre-commit": "^1.2.2", - "request": "^2.79.0", "rimraf": "^2.5.4", - "rmfr": "^1.0.1", "supertest": "^2.0.1", "supertest-as-promised": "^4.0.2", "uglifyjs": "^2.4.10" diff --git a/scripts/update-firmware-binaries.js b/scripts/update-firmware-binaries.js deleted file mode 100644 index 328d406d..00000000 --- a/scripts/update-firmware-binaries.js +++ /dev/null @@ -1,246 +0,0 @@ -// @flow - -import fs from 'fs'; -import github from 'github'; -import mkdirp from 'mkdirp'; -import request from 'request'; -import rmfr from 'rmfr'; -import settings from '../src/settings'; -import nullthrows from 'nullthrows'; - -type Asset = { - browser_download_url: string, - name: string, -}; - -const GITHUB_USER = 'spark'; -const GITHUB_FIRMWARE_REPOSITORY = 'firmware'; -const GITHUB_CLI_REPOSITORY = 'particle-cli'; -const MAPPING_FILE = settings.BINARIES_DIRECTORY + '/versions.json'; -const SPECIFICATIONS_FILE = settings.BINARIES_DIRECTORY + '/specifications.js'; -const SETTINGS_FILE = settings.BINARIES_DIRECTORY + '/settings.json'; - -// This default is here so that the regex will work when updating these files. -const DEFAULT_SETTINGS = { - knownApps: { - 'deep_update_2014_06': true, - 'cc3000': true, - 'cc3000_1_14': true, - 'tinker': true, - 'voodoo': true - }, - knownPlatforms: { - '0': 'Core', - '6': 'Photon', - '8': 'P1', - '10': 'Electron', - '88': 'Duo', - '103': 'Bluz' - }, - updates: { - '2b04:d006': { - systemFirmwareOne: 'system-part1-0.6.0-photon.bin', - systemFirmwareTwo: 'system-part2-0.6.0-photon.bin' - }, - '2b04:d008': { - systemFirmwareOne: 'system-part1-0.6.0-p1.bin', - systemFirmwareTwo: 'system-part2-0.6.0-p1.bin' - }, - '2b04:d00a': { - // The bin files MUST be in this order to be flashed to the correct memory locations - systemFirmwareOne: 'system-part2-0.6.0-electron.bin', - systemFirmwareTwo: 'system-part3-0.6.0-electron.bin', - systemFirmwareThree: 'system-part1-0.6.0-electron.bin' - } - }, -}; - -let versionTag = ''; -const githubAPI = new github(); - -const exitWithMessage = (message: string): void => { - console.log(message); - process.exit(0); -}; - -const cleanBinariesDirectory = async (): Promise<*> => { - return rmfr(settings.BINARIES_DIRECTORY + '/'); -}; - -const exitWithJSON = (json: Object): void => { - exitWithMessage(JSON.stringify(json, null, 2)); -} - -const downloadFile = (url: string): Promise<*> => { - return new Promise((resolve, reject) => { - const filename = nullthrows(url.match(/.*\/(.*)/))[1]; - console.log('Downloading ' + filename + '...'); - const file = fs.createWriteStream( - settings.BINARIES_DIRECTORY + - '/' + - filename - ); - file.on('finish', () => file.close(() => resolve(filename))); - request(url).pipe(file).on('error', exitWithJSON); - }); -}; - -const downloadFirmwareBinaries = async ( - assets: Array, -): Promise<*> => { - const assetFileNames = await Promise.all(assets.map(asset => { - if (asset.name.match(/^system-part/)) { - return downloadFile(asset.browser_download_url); - } - return ''; - })); - - return assetFileNames.filter(item => item); -}; - -const updateSettings = (): Array => { - let versionNumber = versionTag; - if (versionNumber[0] === 'v') { - versionNumber = versionNumber.substr(1); - } - - let settings = JSON.stringify(DEFAULT_SETTINGS, null, 2); - const settingsBinaries = []; - settings = settings.replace( - /(system-part\d-).*(-.*.bin)/g, - (filename, part, device) => { - var newFilename = part + versionNumber + device; - settingsBinaries.push(newFilename); - return newFilename; - } - ); - - fs.writeFileSync(SETTINGS_FILE, settings, { flag: 'wx' }); - console.log('Updated settings'); - - return settingsBinaries; -}; - -const verifyBinariesMatch = ( - downloadedBinaries: Array, - settingsBinaries: Array, -): void => { - downloadedBinaries = downloadedBinaries.sort(); - settingsBinaries = settingsBinaries.sort(); - if (JSON.stringify(downloadedBinaries) !== JSON.stringify(settingsBinaries)) { - console.log( - '\n\nWARNING: the list of downloaded binaries doesn\'t match the list ' + - 'of binaries in settings.js' - ); - console.log('Downloaded: ', downloadedBinaries); - console.log('settings.js: ', settingsBinaries); - } -}; - -const downloadAppBinaries = async (): Promise<*> => { - const assets = await githubAPI.repos.getContent({ - owner: GITHUB_USER, - repo: GITHUB_CLI_REPOSITORY, - path: 'binaries' - }); - - return await Promise.all( - assets.map(asset => downloadFile(asset.download_url)), - ); -}; - -(async (): Promise<*> => { - // Start running process. If you pass `0.6.0` it will install that version of - // the firmware. - versionTag = process.argv[2]; - if (versionTag && versionTag[0] !== 'v') { - versionTag = 'v' + versionTag; - } - - - await cleanBinariesDirectory(); - if (!fs.existsSync(settings.BINARIES_DIRECTORY)) { - mkdirp.sync(settings.BINARIES_DIRECTORY); - } - - // Download app binaries - await downloadAppBinaries(); - - // Download firmware binaries - if (process.argv.length !== 3) { - let tags = await githubAPI.repos.getTags({ - owner: GITHUB_USER, - page: 0, - perPage: 30, - repo: GITHUB_FIRMWARE_REPOSITORY - }) - tags = tags.filter(tag => - // Don't use release candidates.. we only need main releases. - !tag.name.includes('-rc') && - !tag.name.includes('-pi') - ); - - tags.sort((a, b) => { - if (a.name < b.name) { - return 1; - } - if (a.name > b.name) { - return -1; - } - return 0; - }); - - versionTag = tags[0].name; - } - - const release = await githubAPI.repos.getReleaseByTag({ - owner: GITHUB_USER, - repo: GITHUB_FIRMWARE_REPOSITORY, - tag: versionTag - }); - - const downloadedBinaries = await downloadFirmwareBinaries(release.assets); - const settingsBinaries = await updateSettings(); - verifyBinariesMatch(downloadedBinaries, settingsBinaries); - - const specificationsResponse = await githubAPI.repos.getContent({ - owner: GITHUB_USER, - path: 'lib/deviceSpecs/specifications.js', - repo: GITHUB_CLI_REPOSITORY - }); - - fs.writeFileSync( - SPECIFICATIONS_FILE, - new Buffer(specificationsResponse.content, 'base64').toString(), - { flag: 'wx' } - ); - - const versionResponse = await githubAPI.repos.getContent({ - owner: GITHUB_USER, - path: 'system/system-versions.md', - repo: GITHUB_FIRMWARE_REPOSITORY - }) - - const versionText = new Buffer(versionResponse.content, 'base64').toString(); - const startIndex = versionText.indexOf('| 0 '); - const endIndex = versionText.indexOf('\n\n', startIndex); - const data = versionText - .substring(startIndex, endIndex) - .replace(/\s/g, '') - .split('|'); - - const mapping = []; - for (var i = 0; i < data.length; i += 4) { - if (!data[i+1]) { - continue; - } - mapping.push([data[i+1], data[i+2]]); - } - fs.writeFileSync( - MAPPING_FILE, - JSON.stringify(mapping, null, 2), - { flag: 'wx' } - ); - - console.log('\r\nCompleted Sync') -})(); diff --git a/src/managers/FirmwareManager.js b/src/managers/FirmwareManager.js deleted file mode 100644 index 1165b193..00000000 --- a/src/managers/FirmwareManager.js +++ /dev/null @@ -1,89 +0,0 @@ -// @flow - -import {HalDescribeParser} from 'binary-version-reader'; - -// TODO - these should be written to a non-configurable folder like -// `../third-party` -import settings from '../data/binaries/settings'; -import specifications from '../data/binaries/specifications'; -import versions from '../data/binaries/versions'; - -type OtaUpdate = { - address: string, - alt: string, - binaryFileName: string, -}; - -const platformSettings = Object.entries(specifications); -const SPECIFICATION_KEY_BY_PLATFORM = new Map( - Object.values(settings.knownPlatforms).map( - platform => { - const spec = platformSettings.find( - ([key, value]) => (value: any).productName === platform, - ); - - return [platform, spec && spec[0]] - }, - ).filter(item => item[1]), -); -const MAX_RELEASE_VERSION = Math.max( - ...versions - // Filter out Release Candidates and only use release versions - .filter(version => !version[1].includes('rc')) - .map(version => Number.parseInt(version[0])), -); - -class FirmwareManager { - _modules: Array; - _platform: string; - _systemVersion: number; - - constructor(description: Object) { - const parser = new HalDescribeParser(); - this._platform = settings.knownPlatforms[description.p + '']; - this._systemVersion = parser.getSystemVersion(description); - this._modules = parser.getModules(description); - } - - getModules(): Array { - return this._modules; - } - - getPlatform(): string { - return this._platform; - } - - getSystemVersion(): number { - return this._systemVersion; - } - - // Gets OTA updates if the device needs to be updated - getOtaUpdateConfig(): ?Array { - if (this._systemVersion >= MAX_RELEASE_VERSION) { - //return null; - } - - const key = SPECIFICATION_KEY_BY_PLATFORM.get(this._platform); - - if (!key) { - return null; - } - - const firmwareSettings = settings.updates[key]; - if (!key) { - return null; - } - - const firmwareKeys = Object.keys(firmwareSettings); - return firmwareKeys.map(firmwareKey => ({ - ...specifications[key][firmwareKey], - binaryFileName: firmwareSettings[firmwareKey], - })); - } - - getKnownAppFileName(): ?string { - throw new Error('getKnownAppFileName has not been implemented.') - } -} - -export default FirmwareManager; diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 94c5dce1..e3dc4554 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -45,8 +45,9 @@ class WebhookManager { this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); }; - _resetWebhookErrorCounter = (webhookID: string): void => + _resetWebhookErrorCounter = (webhookID: string): void => { this._errorsCountByWebhookID.set(webhookID, 0); + }; // todo annotate arguments _webhookHandler = ( diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 126e3535..7fe7896d 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -14,7 +14,6 @@ import fs from 'fs'; import Moniker from 'moniker'; import ursa from 'ursa'; import HttpError from '../lib/HttpError'; -import FirmwareManager from '../managers/FirmwareManager'; import settings from '../settings'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); @@ -198,24 +197,6 @@ class DeviceRepository { throw new HttpError('Could not get device for ID', 404); } - // TODO make FirmwareManager stateless - const firmwareManager = new FirmwareManager(device.getSystemInformation()); - const otaUpdateConfig = null;// firmwareManager.getOtaUpdateConfig(); - - console.log(otaUpdateConfig); - - if (otaUpdateConfig) { - // TODO use a repository instead of just fetching from disk - for (var i = 0; i < otaUpdateConfig.length; i++) { - const config = otaUpdateConfig[i]; - const systemFile = fs.readFileSync( - settings.BINARIES_DIRECTORY + '/' + config.binaryFileName, - ); - console.log('FLASHING', systemFile.length, config.binaryFileName) - await device.flash(systemFile, config.address); - await new Promise(resolve => setTimeout(() => resolve(), 15000)); - }; - } return await device.flash(file.buffer); }; diff --git a/src/settings.js b/src/settings.js index 5dde24e6..352c9f78 100644 --- a/src/settings.js +++ b/src/settings.js @@ -22,7 +22,6 @@ const path = require('path'); module.exports = { - BINARIES_DIRECTORY: path.join(__dirname, './data/binaries'), DEVICE_DIRECTORY: path.join(__dirname, './data/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, './data/knownApps'), SERVER_KEY_FILENAME: 'default_key.pem', From cbc752ace29a78c414405fab55a582fc729a288c Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 14 Jan 2017 19:46:39 +0200 Subject: [PATCH 259/504] fix: request.js tries to write body if webhook.form provided. --- src/managers/WebhookManager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 2aa0c78d..6cc69bd5 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -167,10 +167,9 @@ class WebhookManager { } }; - const requestOptions = { auth: webhook.auth, - body: requestJSON || event.data, + body: requestFormData ? null : requestJSON || event.data, formData: requestFormData, headers: webhook.headers, json: !!requestJSON, From 878d94cb5376e59e16d76c458c8e9a83a13ace51 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 14 Jan 2017 20:11:46 +0200 Subject: [PATCH 260/504] fix Webhook Flow type --- src/types.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/types.js b/src/types.js index 97f77f91..8cd3a1ee 100644 --- a/src/types.js +++ b/src/types.js @@ -4,7 +4,7 @@ import type { File } from 'express'; import type DeviceFirmwareRepository from './repository/DeviceFirmwareFileRepository'; export type Webhook = { - auth?: { Authorization: string }, + auth?: { password: string, username: string }, created_at: Date, deviceID?: string, errorResponseTopic?: string, @@ -15,6 +15,7 @@ export type Webhook = { json?: { [key: string]: Object }, mydevices?: boolean, noDefaults?: boolean, + ownerID: string, productIdOrSlug?: string, query?: { [key: string]: Object }, rejectUnauthorized?: boolean, From 85dd55fdc428d2498d11ac08ca5e2c7b6b99365b Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 14 Jan 2017 12:20:31 -0800 Subject: [PATCH 261/504] Cleaned up code and removed or added issues for todos --- package.json | 1 + src/RouteConfig.js | 1 - src/controllers/DevicesController.js | 1 + src/lib/PasswordHasher.js | 2 - src/lib/deviceToAPI.js | 10 +- src/lib/utilities.js | 361 --------------------------- src/main.js | 33 +-- src/repository/DeviceRepository.js | 29 ++- src/types.js | 6 +- 9 files changed, 56 insertions(+), 388 deletions(-) delete mode 100644 src/lib/utilities.js diff --git a/package.json b/package.json index 6e7bbe4f..290a177b 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ ] }, "dependencies": { + "array-flatten": "^2.1.1", "basic-auth-parser": "0.0.2", "binary-version-reader": "^0.5.0", "body-parser": "^1.15.2", diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 6f06d0cb..6302539e 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -16,7 +16,6 @@ import multer from 'multer'; import OAuthModel from './OAuthModel'; import HttpError from './lib/HttpError'; -// TODO fix flow errors, come up with better name; const maybe = (middleware: Middleware, condition: boolean): Middleware => (request: $Request, response: $Response, next: NextFunction) => { if (condition) { diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index ec9c37fe..c40510a0 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -172,6 +172,7 @@ class DevicesController extends Controller { if (errorMessage.indexOf('Unknown Function') >= 0) { throw new HttpError('Function not found', 404); } + console.log(error); throw error; } } diff --git a/src/lib/PasswordHasher.js b/src/lib/PasswordHasher.js index ee7804d9..2ad28ad2 100644 --- a/src/lib/PasswordHasher.js +++ b/src/lib/PasswordHasher.js @@ -27,7 +27,6 @@ const KEY_LENGTH = 64; class PasswordHasher { static generateSalt(size: number = 64): Promise<*> { - // todo better annotate promise resolve, reject return new Promise((resolve: Function, reject: Function) => { crypto.randomBytes(size, (error: ?Error, buffer: Buffer) => { if (error) { @@ -43,7 +42,6 @@ class PasswordHasher { password: string, salt: string, ): Promise<*> { - // todo better annotate promise resolve, reject return new Promise((resolve: Function, reject: Function) => { crypto.pbkdf2( password, diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js index 16b1c9bb..9ac6c046 100644 --- a/src/lib/deviceToAPI.js +++ b/src/lib/deviceToAPI.js @@ -8,8 +8,10 @@ export type DeviceAPIType = {| current_build_target: string, functions?: Array, id: string, + imei?: string, last_app: ?string, last_heard: ?Date, + last_iccid?: string, last_ip_address: ?string, name: string, platform_id: number, @@ -20,19 +22,21 @@ export type DeviceAPIType = {| |}; const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ - cellular: false, // TODO: populate this from device. + cellular: device.isCellular, connected: device.connected, - current_build_target: '', // TODO: populate this as well :( + current_build_target: device.currentBuildTarget, functions: device.functions, + imei: device.imei, id: device.deviceID, last_app: device.lastFlashedAppName, last_heard: device.lastHeard, + last_iccid: device.last_iccid, last_ip_address: device.ip, name: device.name, platform_id: device.particleProductId, product_id: device.particleProductId, return_value: result, - status: 'normal', // TODO: populate this from device + status: 'normal', variables: device.variables, }); diff --git a/src/lib/utilities.js b/src/lib/utilities.js deleted file mode 100644 index 8e37fa22..00000000 --- a/src/lib/utilities.js +++ /dev/null @@ -1,361 +0,0 @@ -/** -* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License, version 3, -* as published by the Free Software Foundation. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see . -* -*/ - -var os = require('os'); -var when = require('when'); -var logger = require('./logger.js'); -var path = require('path'); -var fs = require('fs'); - -var that; -module.exports = that = { - /** - * Surely there is a better way to do this. - * NOTE! This function does NOT short-circuit when an in-equality is detected. This is - * to avoid timing attacks. - * @param left - * @param right - */ - bufferCompare: function (left, right) { - if ((left===null) && (right===null)) { - return true; - } - else if ((left===null) || (right===null)) { - return false; - } - - if (!Buffer.isBuffer(left)) { - left = new Buffer(left); - } - if (!Buffer.isBuffer(right)) { - right = new Buffer(right); - } - - logger.log('left: ', left.toString('hex'), ' right: ', right.toString('hex')); - - var same = (left.length===right.length), - i = 0, - max = left.length; - - while (i < max) { - same &= (left[i]===right[i]); - i++; - } - - return same; - }, - - /** - * Iterates over the properties of the right object, checking to make - * sure the properties on the left object match. - * @param left - * @param right - */ - leftHasRightFilter: function (left, right) { - if (!left && !right) { - return true; - } - var matches = true; - - for (var prop in right) { - if (!right.hasOwnProperty(prop)) { - continue; - } - matches &= (left[prop]===right[prop]); - } - return matches; - }, - - promiseDoFile: function (filename, callback) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - if (callback(data)) { - deferred.resolve(); - } - } - catch(ex) { - deferred.reject(ex); - } - - }); - } - }); - return deferred; - }, - - promiseGetJsonFile: function (filename) { - var deferred = when.defer(); - - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - fs.readFile(filename, function (err, data) { - if (err) { - logger.error("error reading " + filename, err); - deferred.reject(); - } - - try { - var obj = JSON.parse(data); - deferred.resolve(obj); - } - catch(ex) { - logger.error("Error parsing " + filename + " " + ex); - deferred.reject(ex); - } - }); - } - }); - return deferred; - }, - - promiseStreamFile: function (filename) { - var deferred = when.defer(); - fs.exists(filename, function (exists) { - if (!exists) { - logger.error("File: " + filename + " doesn't exist."); - deferred.reject(); - } - else { - var readStream = fs.createReadStream(filename); - - //TODO: catch can't read file stuff. - - deferred.resolve(readStream); - } - }); - return deferred; - }, - - bufferToHexString: function(buf) { - if (!buf || (buf.length <= 0)) { return null; } - - var r = []; - for(var i=0;i= 0) { - return filename.substr(idx); - } - else { - return filename; - } - }, - filenameNoExt: function (filename) { - if (!filename || (filename.length === 0)) { - return filename; - } - - var idx = filename.lastIndexOf('.'); - if (idx >= 0) { - return filename.substr(0, idx); - } - else { - return filename; - } - }, - - indexOf: function (arr, val) { - if (!arr || (arr.length===0)) { - return -1; - } - for (var i = 0; i < arr.length; i++) { - if (arr[i]===val) { - return i; - } - } - return -1; - }, - contains: function (arr, val) { - return (that.indexOf(arr, val) !== -1); - }, - pipeDeferred: function(left, right) { - when(left).then(function() { - right.resolve.apply(right, arguments); - }, function() { - right.reject.apply(right, arguments); - }) - }, - - /** - * Non-competitive version of when.any - * @param arr - */ - deferredAny: function (arr) { - var tmp = when.defer(); - var index = -1; - var reasons = []; - - //step through a list of FUNCTIONS that return deferreds, - //process in order and resolve with the first one that resolves. - - var doNext = function () { - index++; - if (index > arr.length) { - tmp.reject(reasons); - return; - } - else if (!arr[index]) { - process.nextTick(doNext); - return; - } - - var promise = null; - try { - promise = arr[index](); - } - catch (ex) { - logger.error("deferredAny - error calling fn in seq index: " + index + " err: " + ex); - } - - if (promise) { - when(promise).then( - function () { - //chain this forward and resolve! we're done! - tmp.resolve.apply(tmp, arguments); - }, - function (err) { - reasons.push(err); - - //lets try the next one! - process.nextTick(doNext); - }); - } - else { - process.nextTick(doNext); - } - }; - - process.nextTick(doNext); - return tmp.promise; - }, - - check_requires_update: function(device, target) { - var version = (device && device["cc3000_patch_version"]); - return (version && (version < target)); - }, - - getIPAddresses: function () { - //adapter = adapter || "eth0"; - var results = []; - var nics = os.networkInterfaces(); - - for (var name in nics) { - var nic = nics[name]; - - for (var i = 0; i < nic.length; i++) { - var addy = nic[i]; - - if ((addy.family !== "IPv4") || (addy.address==="127.0.0.1")) { - continue; - } - - results.push(addy.address); - } - } - - return results; - }, - - foo: null -}; diff --git a/src/main.js b/src/main.js index 10e62936..c3af255d 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,8 @@ // @flow import {Container} from 'constitute'; -import utilities from './lib/utilities'; +import os from 'os'; +import arrayFlatten from 'array-flatten'; import logger from './lib/logger'; import createApp from './app'; import defaultBindings from './defaultBindings'; @@ -9,18 +10,12 @@ import settings from './settings'; const NODE_PORT = process.env.NODE_PORT || 8080; -// TODO wny do we need this? (Anton Puko) -global._socket_counter = 1; - -// TODO: something better here process.on('uncaughtException', (exception: Error) => { - let details = ''; - try { - details = JSON.stringify(exception); - } catch (stringifyException) { - logger.error(`Caught exception: ${stringifyException}`); - } - logger.error(`Caught exception: ${exception.toString()} ${exception.stack}`); + logger.error( + 'uncaughtException', + { message : exception.message, stack : exception.stack }, + ); // logging with MetaData + process.exit(1); // exit with failure }); /* This is the container used app-wide for dependency injection. If you want to @@ -47,6 +42,16 @@ app.listen( (): void => console.log(`express server started on port ${NODE_PORT}`), ); -utilities.getIPAddresses().forEach((ip: string): void => - console.log(`Your device server IP address is: ${ip}`), +const addresses = arrayFlatten( + Object.entries(os.networkInterfaces()).map(([name, nic]) => + (nic: any) + .filter(address => + address.family === 'IPv4' && + address.address !== '127.0.0.1', + ) + .map(address => address.address), + ), +); +addresses.forEach((address: string): void => + console.log(`Your device server IP address is: ${address}`), ); diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 7fe7896d..dbe6c058 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -40,7 +40,8 @@ class DeviceRepository { deviceID: string, userID: string, ): Promise => { - const deviceAttributes = await this._deviceAttributeRepository.getById(deviceID); + const deviceAttributes = + await this._deviceAttributeRepository.getById(deviceID); if (!deviceAttributes) { throw new HttpError('No device found', 404); @@ -101,7 +102,10 @@ class DeviceRepository { }; }; - getDetailsByID = async (deviceID: string, userID: string): Promise => { + getDetailsByID = async ( + deviceID: string, + userID: string, + ): Promise => { const device = this._deviceServer.getDevice(deviceID); if (!device) { throw new HttpError('No device found', 404); @@ -154,9 +158,14 @@ class DeviceRepository { deviceID: string, userID: string, functionName: string, - functionArguments: Object, + functionArguments: {[key: string]: string}, ): Promise<*> => { - if (await !this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + const doesUserHaveAccess = + await this._deviceAttributeRepository.doesUserHaveAccess( + deviceID, + userID, + ); + if (!doesUserHaveAccess) { throw new HttpError('No device found', 404); } @@ -176,7 +185,12 @@ class DeviceRepository { userID: string, varName: string, ): Promise<*> => { - if (!await this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID)) { + const doesUserHaveAccess = + await this._deviceAttributeRepository.doesUserHaveAccess( + deviceID, + userID, + ); + if (!doesUserHaveAccess) { throw new HttpError('No device found', 404); } @@ -282,7 +296,10 @@ class DeviceRepository { userID: string, name: string, ): Promise => { - const attributes = await this._deviceAttributeRepository.getById(deviceID, userID); + const attributes = await this._deviceAttributeRepository.getById( + deviceID, + userID, + ); if (!attributes) { throw new HttpError('No device found', 404); diff --git a/src/types.js b/src/types.js index 97f77f91..b14db0f5 100644 --- a/src/types.js +++ b/src/types.js @@ -53,8 +53,12 @@ export type Client = { }; export type DeviceAttributes = { + currentBuildTarget: string, deviceID: string, + imei?: string, ip: string, + isCellular: boolean, + last_iccid?: string, name: string, ownerID: ?string, particleProductId: number, @@ -157,7 +161,7 @@ export type DeviceRepository = { deviceID: string, userID: string, functionName: string, - functionArguments: Object, + functionArguments: {[key: string]: string}, ): Promise<*>, claimDevice(deviceID: string, userID: string): Promise, flashBinary(deviceID: string, files: File): Promise<*>, From 85759ab34794b1d434892e88288cdcc03d0a260b Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 14 Jan 2017 16:52:37 -0800 Subject: [PATCH 262/504] Updated types to match spark-protocol. This goes with changes in spark-protocol that closes #77 --- src/types.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types.js b/src/types.js index b14db0f5..b150a2e0 100644 --- a/src/types.js +++ b/src/types.js @@ -53,6 +53,7 @@ export type Client = { }; export type DeviceAttributes = { + appHash: ?string, currentBuildTarget: string, deviceID: string, imei?: string, From a26b84492e86e371314f3440841be1ff2c17eda4 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sun, 15 Jan 2017 21:24:54 +0200 Subject: [PATCH 263/504] fix: rename eventName to eventNamePrefix in eventController --- src/controllers/EventsController.js | 21 ++++++++++++--------- src/managers/EventManager.js | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index db5401cd..98ac8ba8 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -44,11 +44,11 @@ class EventsController extends Controller { } @httpVerb('get') - @route('/v1/events/:eventName?') + @route('/v1/events/:eventNamePrefix?') @serverSentEvents() - async getEvents(eventName: ?string): Promise<*> { + async getEvents(eventNamePrefix: ?string): Promise<*> { const subscriptionID = this._eventManager.subscribe( - eventName, + eventNamePrefix, this._pipeEvent.bind(this), ); @@ -57,11 +57,11 @@ class EventsController extends Controller { } @httpVerb('get') - @route('/v1/devices/events/:eventName?') + @route('/v1/devices/events/:eventNamePrefix?') @serverSentEvents() - async getMyEvents(eventName: ?string): Promise<*> { + async getMyEvents(eventNamePrefix: ?string): Promise<*> { const subscriptionID = this._eventManager.subscribe( - eventName, + eventNamePrefix, this._pipeEvent.bind(this), { userID: this.user.id }, ); @@ -73,9 +73,12 @@ class EventsController extends Controller { @httpVerb('get') @route('/v1/devices/:deviceID/events/:eventName?/') @serverSentEvents() - async getDeviceEvents(deviceID: string, eventName: ?string): Promise<*> { + async getDeviceEvents( + deviceID: string, + eventNamePrefix: ?string, + ): Promise<*> { const subscriptionID = this._eventManager.subscribe( - eventName, + eventNamePrefix, this._pipeEvent.bind(this), { deviceID, @@ -91,7 +94,7 @@ class EventsController extends Controller { @route('/v1/devices/events') async publish(postBody: { name: string, - data: ?Object, + data: ?string, private: boolean, ttl?: number, }): Promise<*> { diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js index 77ddc937..d337a286 100644 --- a/src/managers/EventManager.js +++ b/src/managers/EventManager.js @@ -16,12 +16,12 @@ class EventManager { } subscribe = ( - eventName: ?string, + eventNamePrefix: ?string, eventHandler: (event: Event) => void, filterOptions?: FilterOptions, ): string => this._eventPublisher.subscribe( - eventName, + eventNamePrefix, eventHandler, filterOptions, ); From 24d7fafcfb12df0e1c18e2025e6f66afbc21f419 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 15 Jan 2017 22:38:44 -0800 Subject: [PATCH 264/504] Finished memoize --- package.json | 5 +- src/OAuthModel.js | 11 ++- src/controllers/UsersController.js | 2 +- .../DeviceFirmwareFileRepository.js | 8 +- src/repository/UserFileRepository.js | 77 ++++++++++++------- src/repository/WebhookFileRepository.js | 34 +++++--- src/types.js | 5 +- test/UserFileRepository.test.js | 43 +++++++++++ test/UsersController.test.js | 2 +- test/setup/getDefaultContainer.js | 24 ++++++ test/setup/testApp.js | 17 +--- 11 files changed, 165 insertions(+), 63 deletions(-) create mode 100644 test/UserFileRepository.test.js create mode 100644 test/setup/getDefaultContainer.js diff --git a/package.json b/package.json index 290a177b..7b42c39e 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", "start:prod": "npm run build && node ./build/main.js", - "test": "ava", - "test:watch": "ava --watch" + "test": "ava --serial", + "test:watch": "ava --watch --serial" }, "pre-commit": "test", "ava": { @@ -91,6 +91,7 @@ "nodemon": "^1.11.0", "pre-commit": "^1.2.2", "rimraf": "^2.5.4", + "sinon": "^1.17.7", "supertest": "^2.0.1", "supertest-as-promised": "^4.0.2", "uglifyjs": "^2.4.10" diff --git a/src/OAuthModel.js b/src/OAuthModel.js index 2921e7df..75388d8d 100644 --- a/src/OAuthModel.js +++ b/src/OAuthModel.js @@ -42,11 +42,16 @@ class OauthModel { client.clientId === clientId && client.clientSecret === clientSecret, ); - getUser = async (username: string, password: string): Promise => - await this._userRepository.validateLogin(username, password); + getUser = async (username: string, password: string): Promise => { + return await this._userRepository.validateLogin(username, password); + } - saveToken = (tokenObject: TokenObject, client: Client, user: User): Object => { + saveToken = ( + tokenObject: TokenObject, + client: Client, + user: User, + ): Object => { this._userRepository.saveAccessToken(user.id, tokenObject); return { accessToken: tokenObject.accessToken, diff --git a/src/controllers/UsersController.js b/src/controllers/UsersController.js index 8817cc9c..7500fc7e 100644 --- a/src/controllers/UsersController.js +++ b/src/controllers/UsersController.js @@ -53,7 +53,7 @@ class UsersController extends Controller { password, ); - this._userRepository.deleteAccessToken(user, token); + this._userRepository.deleteAccessToken(user.id, token); return this.ok({ ok: true }); } diff --git a/src/repository/DeviceFirmwareFileRepository.js b/src/repository/DeviceFirmwareFileRepository.js index 7da4deac..14f0693d 100644 --- a/src/repository/DeviceFirmwareFileRepository.js +++ b/src/repository/DeviceFirmwareFileRepository.js @@ -1,6 +1,6 @@ // @flow -import { FileManager } from 'spark-protocol'; +import { FileManager, memoizeGet } from 'spark-protocol'; class DeviceFirmwareFileRepository { _fileManager: FileManager; @@ -9,8 +9,10 @@ class DeviceFirmwareFileRepository { this._fileManager = new FileManager(path, false); } - getByName = (appName: string): ?Buffer => - this._fileManager.getFileBuffer(`${appName}.bin`); + @memoizeGet([], {promise: false}) + getByName(appName: string): ?Buffer { + return this._fileManager.getFileBuffer(`${appName}.bin`); + } } export default DeviceFirmwareFileRepository; diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js index bd6cb931..fead5db7 100644 --- a/src/repository/UserFileRepository.js +++ b/src/repository/UserFileRepository.js @@ -3,7 +3,7 @@ import type { TokenObject, User, UserCredentials } from '../types'; import uuid from 'uuid'; -import { JSONFileManager } from 'spark-protocol'; +import { JSONFileManager, memoizeGet, memoizeSet } from 'spark-protocol'; import PasswordHasher from '../lib/PasswordHasher'; import HttpError from '../lib/HttpError'; @@ -21,48 +21,61 @@ class UserFileRepository { const salt = await PasswordHasher.generateSalt(); const passwordHash = await PasswordHasher.hash(password, salt); + const modelToSave = { + accessTokens: [], + passwordHash, + salt, + username, + }; + + return await this.create(modelToSave); + }; + + @memoizeSet() + async create(user: $Shape): Promise { let id = uuid(); - while (await this.getById(id)) { + while (await this._fileManager.hasFile(`${id}.json`)) { id = uuid(); } const modelToSave = { - accessTokens: [], + ...user, created_at: new Date(), created_by: null, id, - passwordHash, - salt, - username, }; this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); return modelToSave; }; - create = (user: User): Promise => { - throw new HttpError('Not implemented'); - }; - - update = async (model: User): Promise => { + @memoizeSet() + async update(model: User): Promise { this._fileManager.writeFile(`${model.id}.json`, model); return model; }; - getAll = async (): Promise> => - this._fileManager.getAllData(); + @memoizeGet() + async getAll(): Promise> { + return this._fileManager.getAllData(); + } - getById = async (id: string): Promise => - this._fileManager.getFile(`${id}.json`); + @memoizeGet(['id']) + async getById(id: string): Promise { + return this._fileManager.getFile(`${id}.json`); + } - getByUsername = async (username: string): Promise => - (await this.getAll()).find( + @memoizeGet(['username']) + async getByUsername(username: string): Promise { + return (await this.getAll()).find( (user: User): boolean => user.username === username, ); + } validateLogin = async (username: string, password: string): Promise => { try { const user = await this.getByUsername(username); + if (!user) { throw new Error('User doesn\'t exist'); } @@ -78,14 +91,22 @@ class UserFileRepository { } }; - getByAccessToken = async (accessToken: string): Promise => - (await this.getAll()).find((user: User): boolean => + // This isn't a good one to memoize as we can't key off user ID and there + // isn't a good way to clear the cache. + getByAccessToken = async (accessToken: string): Promise => { + return (await this.getAll()).find((user: User): boolean => user.accessTokens.some((tokenObject: TokenObject): boolean => tokenObject.accessToken === accessToken, ), ); + } + + deleteAccessToken = async (userID: string, token: string): Promise<*> => { + const user = await this.getById(userID); + if (!user) { + throw new Error('User doesn\'t exist'); + } - deleteAccessToken = async (user: User, token: string): Promise<*> => { const userToSave = { ...user, accessTokens: user.accessTokens.filter( @@ -94,17 +115,21 @@ class UserFileRepository { ), }; - this._fileManager.writeFile(`${user.id}.json`, userToSave); - }; + await this.update(userToSave); + } - deleteById = async (id: string): Promise => + @memoizeSet(['id']) + async deleteById(id: string): Promise { this._fileManager.deleteFile(`${id}.json`); + } - isUserNameInUse = async (username: string): Promise => - (await this.getAll()).some((user: User): boolean => + @memoizeGet(['username']) + async isUserNameInUse(username: string): Promise { + return (await this.getAll()).some((user: User): boolean => user.username === username, ); + } saveAccessToken = async ( userID: string, @@ -121,7 +146,7 @@ class UserFileRepository { accessTokens: [...user.accessTokens, tokenObject], }; - this._fileManager.writeFile(`${userID}.json`, userToSave); + return await this.update(userToSave); } } diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js index f464cf6f..97e1a79c 100644 --- a/src/repository/WebhookFileRepository.js +++ b/src/repository/WebhookFileRepository.js @@ -3,7 +3,7 @@ import type { Webhook, WebhookMutator } from '../types'; import uuid from 'uuid'; -import { JSONFileManager } from 'spark-protocol'; +import { JSONFileManager, memoizeGet, memoizeSet } from 'spark-protocol'; import HttpError from '../lib/HttpError'; class WebhookFileRepository { @@ -13,9 +13,10 @@ class WebhookFileRepository { this._fileManager = new JSONFileManager(path); } - create = async (model: $Shape): Promise => { + @memoizeSet() + async create(model: $Shape): Promise { let id = uuid(); - while (await this.getById(id)) { + while (await this._fileManager.hasFile(`${id}.json`)) { id = uuid(); } @@ -29,11 +30,13 @@ class WebhookFileRepository { return modelToSave; }; - deleteById = async (id: string): Promise => + @memoizeSet(['id']) + async deleteById(id: string): Promise { this._fileManager.deleteFile(`${id}.json`); + } getAll = async (userID: ?string = null): Promise> => { - const allData = this._fileManager.getAllData(); + const allData = await this._getAll(); if (userID) { return allData.filter( @@ -44,23 +47,34 @@ class WebhookFileRepository { return allData; }; - getById = async ( - id: string, - userID: ?string = null, - ): Promise => { - const webhook = this._fileManager.getFile(`${id}.json`); + getById = async (id: string, userID: ?string = null): Promise => { + const webhook = await this._getByID(id); + if ( !webhook || webhook.ownerID !== userID ) { return null; } + return webhook; }; update = async (model: WebhookMutator): Promise => { throw new HttpError('Not implemented'); }; + + @memoizeGet() + async _getAll(): Promise> { + return this._fileManager.getAllData(); + } + + @memoizeGet(['id']) + async _getByID( + id: string, + ): Promise { + return this._fileManager.getFile(`${id}.json`); + } } export default WebhookFileRepository; diff --git a/src/types.js b/src/types.js index b150a2e0..04857d96 100644 --- a/src/types.js +++ b/src/types.js @@ -15,6 +15,7 @@ export type Webhook = { json?: { [key: string]: Object }, mydevices?: boolean, noDefaults?: boolean, + ownerID: string, productIdOrSlug?: string, query?: { [key: string]: Object }, rejectUnauthorized?: boolean, @@ -127,11 +128,11 @@ export type Repository = { export type UserRepository = Repository & { createWithCredentials(credentials: UserCredentials): Promise, - deleteAccessToken(user: User, accessToken: string): Promise, + deleteAccessToken(userID: string, accessToken: string): Promise, getByAccessToken(accessToken: string): Promise, getByUsername(username: string): Promise, isUserNameInUse(username: string): Promise, - saveAccessToken(userId: string, tokenObject: TokenObject): Promise, + saveAccessToken(userID: string, tokenObject: TokenObject): Promise, validateLogin(username: string, password: string): Promise, }; diff --git a/test/UserFileRepository.test.js b/test/UserFileRepository.test.js new file mode 100644 index 00000000..406ea973 --- /dev/null +++ b/test/UserFileRepository.test.js @@ -0,0 +1,43 @@ +import test from 'ava'; +import sinon from 'sinon'; +import TestData from './setup/TestData'; +import UserFileRepository from '../src/repository/UserFileRepository'; + +// Testing memoize +test(async t => { + let testIterator = 0; + const repository = new UserFileRepository('path'); + const user = await repository.createWithCredentials(TestData.getUser()); + const fileManager = repository._fileManager; + const getAllSpy = sinon.spy(fileManager, 'getAllData'); + const getByIdSpy = sinon.spy(fileManager, 'getFile'); + const deleteByIdSpy = sinon.spy(fileManager, 'deleteFile'); + + async function testAllAccessors() { + testIterator++; + + await repository.getAll(); + t.truthy(getAllSpy.callCount === testIterator); + await repository.getAll(); + t.truthy(getAllSpy.callCount === testIterator); + + await repository.getById(user.id); + t.truthy(getByIdSpy.callCount === testIterator); + await repository.getById(user.id); + t.truthy(getByIdSpy.callCount === testIterator); + + await repository.getByUsername(user.username); + t.truthy(getAllSpy.callCount === testIterator); + await repository.getByUsername(user.username); + t.truthy(getAllSpy.callCount === testIterator); + }; + + await testAllAccessors(); + + await repository.update(user); + await testAllAccessors(); + + await repository.deleteById(user.id); + await testAllAccessors(); + t.truthy(deleteByIdSpy.callCount === 1); +}); diff --git a/test/UsersController.test.js b/test/UsersController.test.js index a0a0a9a4..6288d960 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -65,7 +65,7 @@ test.serial('should return all access tokens for the user', async t => { }); -test.serial('should deleteById access token for the user', async t => { +test.serial('should delete the access token for the user', async t => { const deleteResponse = await request(app) .delete(`/v1/access_tokens/${userToken}`) .auth(USER_CREDENTIALS.username, USER_CREDENTIALS.password); diff --git a/test/setup/getDefaultContainer.js b/test/setup/getDefaultContainer.js new file mode 100644 index 00000000..c770c551 --- /dev/null +++ b/test/setup/getDefaultContainer.js @@ -0,0 +1,24 @@ +// @flow + +import {Container} from 'constitute'; +import defaultBindings from '../../src/defaultBindings'; +import settings from './settings'; +import DeviceServerMock from './DeviceServerMock'; + + +const container = new Container(); +// TODO - we should be creating different bindings per test so we can mock out +// different modules to test +defaultBindings(container); + +// settings +container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); +container.bindValue('FIRMWARE_DIRECTORY', settings.FIRMWARE_DIRECTORY); +container.bindValue('SERVER_KEY_FILENAME', settings.SERVER_KEY_FILENAME); +container.bindValue('SERVER_KEYS_DIRECTORY', settings.SERVER_KEYS_DIRECTORY); +container.bindValue('USERS_DIRECTORY', settings.USERS_DIRECTORY); +container.bindValue('WEBHOOKS_DIRECTORY', settings.WEBHOOKS_DIRECTORY); + +container.bindAlias('DeviceServer', DeviceServerMock); + +export default () => container; diff --git a/test/setup/testApp.js b/test/setup/testApp.js index b49f57f2..75f3c507 100644 --- a/test/setup/testApp.js +++ b/test/setup/testApp.js @@ -3,23 +3,10 @@ import {Container} from 'constitute'; import createApp from '../../src/app'; import settings from './settings'; -import defaultBindings from '../../src/defaultBindings'; -import DeviceServerMock from './DeviceServerMock'; +import getDefaultContainer from './getDefaultContainer'; -const container = new Container(); -// TODO - we should be creating different bindings per test so we can mock out -// different modules to test -defaultBindings(container); +const container = getDefaultContainer(); -// settings -container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); -container.bindValue('FIRMWARE_DIRECTORY', settings.FIRMWARE_DIRECTORY); -container.bindValue('SERVER_KEY_FILENAME', settings.SERVER_KEY_FILENAME); -container.bindValue('SERVER_KEYS_DIRECTORY', settings.SERVER_KEYS_DIRECTORY); -container.bindValue('USERS_DIRECTORY', settings.USERS_DIRECTORY); -container.bindValue('WEBHOOKS_DIRECTORY', settings.WEBHOOKS_DIRECTORY); - -container.bindAlias('DeviceServer', DeviceServerMock); const app = createApp(container, settings); (app: any).container = container; From 3bb923409fb036df83a56c9cbb1b45af6c02e89b Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 17 Jan 2017 03:51:17 +0200 Subject: [PATCH 265/504] implement webhook response chunking --- src/managers/WebhookManager.js | 41 ++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 6cc69bd5..3623c8e1 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -14,8 +14,22 @@ import logger from '../lib/logger'; import request from 'request'; import throttle from 'lodash/throttle'; +const splitBufferIntoChunks = ( + buffer: Buffer, + chunkSize: number, +): Array => { + const chunks = []; + let i = 0; + while (i < buffer.length) { + chunks.push(buffer.slice(i, i += chunkSize)); + } + + return chunks; +}; + const MAX_WEBHOOK_ERRORS_COUNT = 10; const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; +const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; class WebhookManager { _eventPublisher: EventPublisher; @@ -30,7 +44,7 @@ class WebhookManager { this._webhookRepository = webhookRepository; this._eventPublisher = eventPublisher; - (async (): Promise => this._init())(); + (async (): Promise => await this._init())(); } _init = async (): Promise => { @@ -45,7 +59,7 @@ class WebhookManager { this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); }; - _resetWebhookErrorCounter = (webhookID: string): void => { + _resetWebhookErrorCounter = (webhookID: string) => { this._errorsCountByWebhookID.set(webhookID, 0); }; @@ -92,7 +106,6 @@ class WebhookManager { eventDataVariables = {}; } - const webhookVariablesObject = webhook.noDefaults ? eventDataVariables : { @@ -135,6 +148,7 @@ class WebhookManager { response: http$IncomingMessage, responseBody: string | Buffer | Object, ) => { + // todo check response.statusCode > 300 also. if (error) { this._incrementWebhookErrorCounter(webhook.id); @@ -154,17 +168,30 @@ class WebhookManager { userID: event.userID, }); + if (!responseBody) { + return; + } + const responseTemplate = webhook.responseTemplate && hogan .compile(webhook.responseTemplate) .render(responseBody); - if (responseTopic) { + const chunks = splitBufferIntoChunks( + Buffer.from(responseTemplate || responseBody), + MAX_RESPONSE_MESSAGE_CHUNK_SIZE, + ); + + chunks.forEach((chunk: Buffer, index: number) => { + const responseEventName = + responseTopic && `${responseTopic}/$${index}` || + `hook-response/${event.name}/${index}`; + this._eventPublisher.publish({ - data: webhook.responseTemplate && responseTemplate, - name: responseTopic, + data: chunk, + name: responseEventName, userID: event.userID, }); - } + }); }; const requestOptions = { From 4379b9e2933ab5e61233d4679c3b1f37e8f3c3d0 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 17 Jan 2017 13:36:46 +0200 Subject: [PATCH 266/504] fix responseTemplate variables parsing --- src/managers/WebhookManager.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 3623c8e1..26d3f5db 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -14,6 +14,14 @@ import logger from '../lib/logger'; import request from 'request'; import throttle from 'lodash/throttle'; +const parseVariables = (data: string | Buffer): Object => { + try { + return JSON.parse(data.toString()); + } catch (error) { + return {}; + } +}; + const splitBufferIntoChunks = ( buffer: Buffer, chunkSize: number, @@ -97,14 +105,9 @@ class WebhookManager { SPARK_PUBLISHED_AT: event.publishedAt, }; - let eventDataVariables = {}; - try { - if (event.data) { - eventDataVariables = JSON.parse(event.data); - } - } catch (error) { - eventDataVariables = {}; - } + const eventDataVariables = event.data + ? parseVariables(event.data) + : {}; const webhookVariablesObject = webhook.noDefaults ? eventDataVariables @@ -146,7 +149,7 @@ class WebhookManager { const responseHandler = ( error: ?Error, response: http$IncomingMessage, - responseBody: string | Buffer | Object, + responseBody: string | Buffer, ) => { // todo check response.statusCode > 300 also. if (error) { @@ -174,7 +177,7 @@ class WebhookManager { const responseTemplate = webhook.responseTemplate && hogan .compile(webhook.responseTemplate) - .render(responseBody); + .render(parseVariables(responseBody)); const chunks = splitBufferIntoChunks( Buffer.from(responseTemplate || responseBody), From b241301987416b984a800cc0806d5da42c76fdc1 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 17 Jan 2017 13:43:06 +0200 Subject: [PATCH 267/504] add MAX_RESPONSE_MESSAGE_SIZE for webhook responses --- src/managers/WebhookManager.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 26d3f5db..2995ce3a 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -38,6 +38,7 @@ const splitBufferIntoChunks = ( const MAX_WEBHOOK_ERRORS_COUNT = 10; const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; +const MAX_RESPONSE_MESSAGE_SIZE = 100000; // 100kb; class WebhookManager { _eventPublisher: EventPublisher; @@ -180,7 +181,9 @@ class WebhookManager { .render(parseVariables(responseBody)); const chunks = splitBufferIntoChunks( - Buffer.from(responseTemplate || responseBody), + Buffer + .from(responseTemplate || responseBody) + .slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE, ); From c08e95146e109db69c3aced452c725e0038a3ed6 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 17 Jan 2017 13:52:34 +0200 Subject: [PATCH 268/504] add eventToDefaultWebhookVariables() helper --- src/managers/WebhookManager.js | 39 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 2995ce3a..b632e939 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -14,6 +14,32 @@ import logger from '../lib/logger'; import request from 'request'; import throttle from 'lodash/throttle'; +type DefaultWebhookVariables = { + PARTICLE_DEVICE_ID: ?string, + PARTICLE_EVENT_NAME: string, + PARTICLE_EVENT_VALUE: ?string, + PARTICLE_PUBLISHED_AT: Date, + // old event names, added for compatibility + SPARK_CORE_ID: ?string, + SPARK_EVENT_NAME: string, + SPARK_EVENT_VALUE: ?string, + SPARK_PUBLISHED_AT: Date, +}; + +const eventToDefaultWebhookVariables = ( + event: Event, +): DefaultWebhookVariables => ({ + PARTICLE_DEVICE_ID: event.deviceID, + PARTICLE_EVENT_NAME: event.name, + PARTICLE_EVENT_VALUE: event.data, + PARTICLE_PUBLISHED_AT: event.publishedAt, + // old event names, added for compatibility + SPARK_CORE_ID: event.deviceID, + SPARK_EVENT_NAME: event.name, + SPARK_EVENT_VALUE: event.data, + SPARK_PUBLISHED_AT: event.publishedAt, +}); + const parseVariables = (data: string | Buffer): Object => { try { return JSON.parse(data.toString()); @@ -94,18 +120,7 @@ class WebhookManager { return; } - const defaultWebhookVariables = { - PARTICLE_DEVICE_ID: event.deviceID, - PARTICLE_EVENT_NAME: event.name, - PARTICLE_EVENT_VALUE: event.data, - PARTICLE_PUBLISHED_AT: event.publishedAt, - // old event names, added for compatibility - SPARK_CORE_ID: event.deviceID, - SPARK_EVENT_NAME: event.name, - SPARK_EVENT_VALUE: event.data, - SPARK_PUBLISHED_AT: event.publishedAt, - }; - + const defaultWebhookVariables = eventToDefaultWebhookVariables(event); const eventDataVariables = event.data ? parseVariables(event.data) : {}; From 59b12ab8030ba9299dbf0620d0794067629b7ee7 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 17 Jan 2017 13:59:02 +0200 Subject: [PATCH 269/504] use ii in splitBufferIntoChunks() --- src/managers/WebhookManager.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index b632e939..28693735 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -19,7 +19,6 @@ type DefaultWebhookVariables = { PARTICLE_EVENT_NAME: string, PARTICLE_EVENT_VALUE: ?string, PARTICLE_PUBLISHED_AT: Date, - // old event names, added for compatibility SPARK_CORE_ID: ?string, SPARK_EVENT_NAME: string, SPARK_EVENT_VALUE: ?string, @@ -53,9 +52,9 @@ const splitBufferIntoChunks = ( chunkSize: number, ): Array => { const chunks = []; - let i = 0; - while (i < buffer.length) { - chunks.push(buffer.slice(i, i += chunkSize)); + let ii = 0; + while (ii < buffer.length) { + chunks.push(buffer.slice(ii, ii += chunkSize)); } return chunks; From 77e358a660330aa71c8e4fb081e38de204db0270 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 18 Jan 2017 00:39:18 +0200 Subject: [PATCH 270/504] fix webhooks response parsing --- src/managers/WebhookManager.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 28693735..18d92e50 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -121,8 +121,8 @@ class WebhookManager { const defaultWebhookVariables = eventToDefaultWebhookVariables(event); const eventDataVariables = event.data - ? parseVariables(event.data) - : {}; + ? parseVariables(event.data) + : {}; const webhookVariablesObject = webhook.noDefaults ? eventDataVariables @@ -131,11 +131,9 @@ class WebhookManager { ...eventDataVariables, }; - const requestJSON = webhook.json && JSON.parse( - hogan - .compile(JSON.stringify(webhook.json)) - .render(webhookVariablesObject), - ); + const requestJSON = webhook.json && hogan + .compile(JSON.stringify(webhook.json)) + .render(webhookVariablesObject); const requestFormData = webhook.form && JSON.parse( hogan @@ -154,12 +152,12 @@ class WebhookManager { ); const responseTopic = webhook.responseTopic && hogan - .compile(webhook.responseTopic) - .render(webhookVariablesObject); + .compile(webhook.responseTopic) + .render(webhookVariablesObject); const errorResponseTopic = webhook.errorResponseTopic && hogan - .compile(webhook.responseTopic) - .render(webhookVariablesObject) || `hook-error/${event.name}`; + .compile(webhook.responseTopic) + .render(webhookVariablesObject) || `hook-error/${event.name}`; const responseHandler = ( error: ?Error, @@ -196,7 +194,7 @@ class WebhookManager { const chunks = splitBufferIntoChunks( Buffer - .from(responseTemplate || responseBody) + .from(responseTemplate || responseBody.toString()) .slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE, ); @@ -218,8 +216,10 @@ class WebhookManager { auth: webhook.auth, body: requestFormData ? null : requestJSON || event.data, formData: requestFormData, - headers: webhook.headers, - json: !!requestJSON, + headers: { + 'Content-type': requestJSON && 'application/json', + ...webhook.headers, + }, method: webhook.requestType, qs: requestQuery, url: requestUrl, From 4503f007f2d6ec5851fe4a870add993eaee75927 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Tue, 17 Jan 2017 22:32:42 -0800 Subject: [PATCH 271/504] Started writing unit tests for the WebhookManager --- src/managers/WebhookManager.js | 459 +++++++++++++++++--------------- test/UserFileRepository.test.js | 77 +++--- test/WebhookManager.test.js | 234 ++++++++++++++++ 3 files changed, 523 insertions(+), 247 deletions(-) create mode 100644 test/WebhookManager.test.js diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 18d92e50..01e68eca 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -8,53 +8,21 @@ import type { } from '../types'; import type { EventPublisher } from 'spark-protocol'; +import querystring from 'querystring'; import hogan from 'hogan.js'; import HttpError from '../lib/HttpError'; import logger from '../lib/logger'; import request from 'request'; import throttle from 'lodash/throttle'; -type DefaultWebhookVariables = { - PARTICLE_DEVICE_ID: ?string, - PARTICLE_EVENT_NAME: string, - PARTICLE_EVENT_VALUE: ?string, - PARTICLE_PUBLISHED_AT: Date, - SPARK_CORE_ID: ?string, - SPARK_EVENT_NAME: string, - SPARK_EVENT_VALUE: ?string, - SPARK_PUBLISHED_AT: Date, -}; - -const eventToDefaultWebhookVariables = ( - event: Event, -): DefaultWebhookVariables => ({ - PARTICLE_DEVICE_ID: event.deviceID, - PARTICLE_EVENT_NAME: event.name, - PARTICLE_EVENT_VALUE: event.data, - PARTICLE_PUBLISHED_AT: event.publishedAt, - // old event names, added for compatibility - SPARK_CORE_ID: event.deviceID, - SPARK_EVENT_NAME: event.name, - SPARK_EVENT_VALUE: event.data, - SPARK_PUBLISHED_AT: event.publishedAt, -}); - -const parseVariables = (data: string | Buffer): Object => { - try { - return JSON.parse(data.toString()); - } catch (error) { - return {}; - } -}; - const splitBufferIntoChunks = ( buffer: Buffer, chunkSize: number, ): Array => { const chunks = []; - let ii = 0; - while (ii < buffer.length) { - chunks.push(buffer.slice(ii, ii += chunkSize)); + let i = 0; + while (i < buffer.length) { + chunks.push(buffer.slice(i, i += chunkSize)); } return chunks; @@ -63,7 +31,6 @@ const splitBufferIntoChunks = ( const MAX_WEBHOOK_ERRORS_COUNT = 10; const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; -const MAX_RESPONSE_MESSAGE_SIZE = 100000; // 100kb; class WebhookManager { _eventPublisher: EventPublisher; @@ -81,6 +48,36 @@ class WebhookManager { (async (): Promise => await this._init())(); } + create = async (model: WebhookMutator): Promise => { + const webhook = await this._webhookRepository.create(model); + this._subscribeWebhook(webhook); + return webhook; + }; + + deleteByID = async ( + webhookID: string, + userID: string, + ): Promise => { + const webhook = await this._webhookRepository.getById(webhookID, userID); + if (!webhook) { + throw new HttpError('no webhook found', 404); + } + await this._webhookRepository.deleteById(webhookID); + this._unsubscribeWebhookByID(webhookID); + }; + + getAll = async (userID: string): Promise> => + await this._webhookRepository.getAll(userID); + + getByID = async (webhookID: string, userID: string): Promise => { + const webhook = await this._webhookRepository.getById(webhookID, userID); + if (!webhook) { + throw new HttpError('no webhook found', 404); + } + + return webhook; + }; + _init = async (): Promise => { const allWebhooks = await this._webhookRepository.getAll(); allWebhooks.forEach( @@ -88,26 +85,28 @@ class WebhookManager { ); }; - _incrementWebhookErrorCounter = (webhookID: string) => { - const errorsCount = this._errorsCountByWebhookID.get(webhookID) || 0; - this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); - }; - - _resetWebhookErrorCounter = (webhookID: string) => { - this._errorsCountByWebhookID.set(webhookID, 0); + _subscribeWebhook = (webhook: Webhook) => { + const subscriptionID = this._eventPublisher.subscribe( + webhook.event, + this._onNewWebhookEvent(webhook), + // todo separate filtering for MY_DEVICES and for public/private events + { + deviceID: webhook.deviceID, + userID: webhook.ownerID, + }, + ); + this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID); }; - // todo annotate arguments - _webhookHandler = ( - requestOptions: Object, - responseHandler: Function, - ): void => request(requestOptions, responseHandler); + _unsubscribeWebhookByID = (webhookID: string) => { + const subscriptionID = this._subscriptionIDsByWebhookID.get(webhookID); + if (!subscriptionID) { + return; + } - _throttledWebhookHandler = throttle( - this._webhookHandler, - WEBHOOK_THROTTLE_TIME, - { leading: false, trailing: true }, - ); + this._eventPublisher.unsubscribe(subscriptionID); + this._subscriptionIDsByWebhookID.delete(webhookID); + }; _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => (event: Event) => { @@ -119,182 +118,222 @@ class WebhookManager { return; } - const defaultWebhookVariables = eventToDefaultWebhookVariables(event); - const eventDataVariables = event.data - ? parseVariables(event.data) - : {}; - - const webhookVariablesObject = webhook.noDefaults - ? eventDataVariables - : { - ...defaultWebhookVariables, - ...eventDataVariables, - }; - - const requestJSON = webhook.json && hogan - .compile(JSON.stringify(webhook.json)) - .render(webhookVariablesObject); - - const requestFormData = webhook.form && JSON.parse( - hogan - .compile(JSON.stringify(webhook.form)) - .render(webhookVariablesObject), - ); - - const requestUrl = hogan - .compile(webhook.url) - .render(webhookVariablesObject); - - const requestQuery = webhook.query && JSON.parse( - hogan - .compile(JSON.stringify(webhook.query)) - .render(webhookVariablesObject), - ); - - const responseTopic = webhook.responseTopic && hogan - .compile(webhook.responseTopic) - .render(webhookVariablesObject); - - const errorResponseTopic = webhook.errorResponseTopic && hogan - .compile(webhook.responseTopic) - .render(webhookVariablesObject) || `hook-error/${event.name}`; - - const responseHandler = ( - error: ?Error, - response: http$IncomingMessage, - responseBody: string | Buffer, - ) => { - // todo check response.statusCode > 300 also. - if (error) { - this._incrementWebhookErrorCounter(webhook.id); - - this._eventPublisher.publish({ - data: error.message, - name: errorResponseTopic, - userID: event.userID, - }); - - throw error; - } - - this._resetWebhookErrorCounter(webhook.id); + const webhookErrorCount = + this._errorsCountByWebhookID.get(webhook.id) || 0; - this._eventPublisher.publish({ - name: `hook-sent/${event.name}`, - userID: event.userID, - }); + if (webhookErrorCount < MAX_WEBHOOK_ERRORS_COUNT) { + this.runWebhook(webhook, event); + return; + } + + this._eventPublisher.publish({ + data: 'Too many errors, webhook disabled', + name: this._compileErrorResponseTopic( + webhook, + event, + ), + userID: event.userID, + }); + } catch (error) { + logger.error(`webhookError: ${error}`); + } + }; + + runWebhook = async (webhook: Webhook, event: Event): Promise => { + try { + const webhookVariablesObject = + this._getEventData(event, webhook.noDefaults); + + const requestJson = this._compileJsonTopic( + webhook.json, + webhookVariablesObject, + ); + + const requestFormData = this._compileJsonTopic( + webhook.form, + webhookVariablesObject, + ); + + const requestUrl = this._compileTopic( + webhook.url, + webhookVariablesObject, + ); + + const requestQuery = this._compileJsonTopic( + webhook.query, + webhookVariablesObject, + ); + + const responseTopic = this._compileTopic( + webhook.responseTopic, + webhookVariablesObject, + ); + + const isJsonRequest = !!requestJson; + const requestOptions = { + auth: webhook.auth, + body: isJsonRequest + ? requestJson + : undefined, + form: !isJsonRequest ? requestFormData : undefined, + headers: webhook.headers, + json: isJsonRequest, + method: webhook.requestType, + qs: requestQuery, + strictSSL: webhook.rejectUnauthorized, + url: requestUrl, + }; + + const responseBody = await this._callWebhook( + webhook, + event, + requestOptions, + ); + if (!responseBody) { + return; + } + + // TODO: responseBody is a string/buffer. + // We should only render this if it has been converted to a JSON object + const responseTemplate = webhook.responseTemplate && hogan + .compile(webhook.responseTemplate) + .render(responseBody); + + const chunks = splitBufferIntoChunks( + Buffer.from(responseTemplate || responseBody), + MAX_RESPONSE_MESSAGE_CHUNK_SIZE, + ); + + chunks.forEach((chunk: Buffer, index: number) => { + const responseEventName = + responseTopic && `${responseTopic}/${index}` || + `hook-response/${event.name}/${index}`; + + this._eventPublisher.publish({ + data: chunk, + name: responseEventName, + userID: event.userID, + }); + }); + } catch (error) { + logger.error(`webhookError: ${error}`); + } + }; + + _throttledWebhookHandler = throttle( + this.runWebhook, + WEBHOOK_THROTTLE_TIME, + { leading: false, trailing: true }, + ); + + // todo annotate requestOptions + _callWebhook = ( + webhook: Webhook, + event: Event, + requestOptions: Object, + ): Promise<*> => new Promise( + (resolve, reject) => request( + requestOptions, + ( + error: ?Error, + response: http$IncomingMessage, + responseBody: string | Buffer | Object, + ) => { + // todo check response.statusCode > 300 also. + if (error) { + this._incrementWebhookErrorCounter(webhook.id); - if (!responseBody) { - return; - } - - const responseTemplate = webhook.responseTemplate && hogan - .compile(webhook.responseTemplate) - .render(parseVariables(responseBody)); - - const chunks = splitBufferIntoChunks( - Buffer - .from(responseTemplate || responseBody.toString()) - .slice(0, MAX_RESPONSE_MESSAGE_SIZE), - MAX_RESPONSE_MESSAGE_CHUNK_SIZE, - ); - - chunks.forEach((chunk: Buffer, index: number) => { - const responseEventName = - responseTopic && `${responseTopic}/$${index}` || - `hook-response/${event.name}/${index}`; - - this._eventPublisher.publish({ - data: chunk, - name: responseEventName, - userID: event.userID, - }); - }); - }; - - const requestOptions = { - auth: webhook.auth, - body: requestFormData ? null : requestJSON || event.data, - formData: requestFormData, - headers: { - 'Content-type': requestJSON && 'application/json', - ...webhook.headers, - }, - method: webhook.requestType, - qs: requestQuery, - url: requestUrl, - }; - - const isWebhookDisabled = - (this._errorsCountByWebhookID.get(webhook.id) || 0) >= MAX_WEBHOOK_ERRORS_COUNT; - - if (isWebhookDisabled) { this._eventPublisher.publish({ - data: 'Too many errors, webhook disabled', - name: errorResponseTopic, + data: error.message, + name: this._compileErrorResponseTopic( + webhook, + event, + ), userID: event.userID, }); - this._throttledWebhookHandler(requestOptions, responseHandler); - } else { - this._webhookHandler(requestOptions, responseHandler); + reject(error); } - } catch (error) { - logger.error(`webhookError: ${error}`); - } - }; - _subscribeWebhook = (webhook: Webhook) => { - const subscriptionID = this._eventPublisher.subscribe( - webhook.event, - this._onNewWebhookEvent(webhook), - // todo separate filtering for MY_DEVICES and for public/private events - { - deviceID: webhook.deviceID, - userID: webhook.ownerID, + this._resetWebhookErrorCounter(webhook.id); + + this._eventPublisher.publish({ + name: `hook-sent/${event.name}`, + userID: event.userID, + }); + + resolve(responseBody); }, - ); - this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID); + ), + ); + + _getEventData = (event: Event, noDefaults: boolean = false): Object => { + const defaultWebhookVariables = { + PARTICLE_DEVICE_ID: event.deviceID, + PARTICLE_EVENT_NAME: event.name, + PARTICLE_EVENT_VALUE: event.data, + PARTICLE_PUBLISHED_AT: event.publishedAt, + // old event names, added for compatibility + SPARK_CORE_ID: event.deviceID, + SPARK_EVENT_NAME: event.name, + SPARK_EVENT_VALUE: event.data, + SPARK_PUBLISHED_AT: event.publishedAt, + }; + + const eventDataVariables = this._parseEventData(event); + + return noDefaults + ? eventDataVariables + : { + ...defaultWebhookVariables, + ...eventDataVariables, + }; }; - _unsubscribeWebhookByID = (webhookID: string) => { - const subscriptionID = this._subscriptionIDsByWebhookID.get(webhookID); - if (!subscriptionID) { - return; + _parseEventData = (event: Event): Object => { + try { + if (event.data) { + return JSON.parse(event.data); + } + return {}; + } catch (error) { + return {}; } + } - this._eventPublisher.unsubscribe(subscriptionID); - this._subscriptionIDsByWebhookID.delete(webhookID); - }; + _compileTopic = (topic?: ?string, variables: Object): ?string => + topic && hogan + .compile(topic) + .render(variables); - create = async (model: WebhookMutator): Promise => { - const webhook = await this._webhookRepository.create(model); - this._subscribeWebhook(webhook); - return webhook; - }; + _compileJsonTopic = (topic?: ?Object, variables: Object): ?Object => { + if (!topic) { + return; + } - deleteByID = async ( - webhookID: string, - userID: string, - ): Promise => { - const webhook = await this._webhookRepository.getById(webhookID, userID); - if (!webhook) { - throw new HttpError('no webhook found', 404); + const compiledTopic = this._compileTopic(JSON.stringify(topic), variables); + if (!compiledTopic) { + return null; } - await this._webhookRepository.deleteById(webhookID); - this._unsubscribeWebhookByID(webhookID); + + return JSON.parse(compiledTopic); }; - getAll = async (userID: string): Promise> => - await this._webhookRepository.getAll(userID); + _compileErrorResponseTopic = (webhook: Webhook, event: Event): string => { + const variables = this._getEventData(event, webhook.noDefaults); + return this._compileTopic( + webhook.errorResponseTopic, + variables, + ) || `hook-error/${event.name}`; + }; - getByID = async (webhookID: string, userID: string): Promise => { - const webhook = await this._webhookRepository.getById(webhookID, userID); - if (!webhook) { - throw new HttpError('no webhook found', 404); - } + _incrementWebhookErrorCounter = (webhookID: string) => { + const errorsCount = this._errorsCountByWebhookID.get(webhookID) || 0; + this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); + }; - return webhook; + _resetWebhookErrorCounter = (webhookID: string) => { + this._errorsCountByWebhookID.set(webhookID, 0); }; } diff --git a/test/UserFileRepository.test.js b/test/UserFileRepository.test.js index 406ea973..223b12d2 100644 --- a/test/UserFileRepository.test.js +++ b/test/UserFileRepository.test.js @@ -4,40 +4,43 @@ import TestData from './setup/TestData'; import UserFileRepository from '../src/repository/UserFileRepository'; // Testing memoize -test(async t => { - let testIterator = 0; - const repository = new UserFileRepository('path'); - const user = await repository.createWithCredentials(TestData.getUser()); - const fileManager = repository._fileManager; - const getAllSpy = sinon.spy(fileManager, 'getAllData'); - const getByIdSpy = sinon.spy(fileManager, 'getFile'); - const deleteByIdSpy = sinon.spy(fileManager, 'deleteFile'); - - async function testAllAccessors() { - testIterator++; - - await repository.getAll(); - t.truthy(getAllSpy.callCount === testIterator); - await repository.getAll(); - t.truthy(getAllSpy.callCount === testIterator); - - await repository.getById(user.id); - t.truthy(getByIdSpy.callCount === testIterator); - await repository.getById(user.id); - t.truthy(getByIdSpy.callCount === testIterator); - - await repository.getByUsername(user.username); - t.truthy(getAllSpy.callCount === testIterator); - await repository.getByUsername(user.username); - t.truthy(getAllSpy.callCount === testIterator); - }; - - await testAllAccessors(); - - await repository.update(user); - await testAllAccessors(); - - await repository.deleteById(user.id); - await testAllAccessors(); - t.truthy(deleteByIdSpy.callCount === 1); -}); +test( + 'should memoize and cache bust when mutator functions are called', + async t => { + let testIterator = 0; + const repository = new UserFileRepository('path'); + const user = await repository.createWithCredentials(TestData.getUser()); + const fileManager = repository._fileManager; + const getAllSpy = sinon.spy(fileManager, 'getAllData'); + const getByIdSpy = sinon.spy(fileManager, 'getFile'); + const deleteByIdSpy = sinon.spy(fileManager, 'deleteFile'); + + async function testAllAccessors() { + testIterator++; + + await repository.getAll(); + t.truthy(getAllSpy.callCount === testIterator); + await repository.getAll(); + t.truthy(getAllSpy.callCount === testIterator); + + await repository.getById(user.id); + t.truthy(getByIdSpy.callCount === testIterator); + await repository.getById(user.id); + t.truthy(getByIdSpy.callCount === testIterator); + + await repository.getByUsername(user.username); + t.truthy(getAllSpy.callCount === testIterator); + await repository.getByUsername(user.username); + t.truthy(getAllSpy.callCount === testIterator); + }; + + await testAllAccessors(); + + await repository.update(user); + await testAllAccessors(); + + await repository.deleteById(user.id); + await testAllAccessors(); + t.truthy(deleteByIdSpy.callCount === 1); + }, +); diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js new file mode 100644 index 00000000..8b57532a --- /dev/null +++ b/test/WebhookManager.test.js @@ -0,0 +1,234 @@ +import test from 'ava'; +import sinon from 'sinon'; + +import { EventPublisher } from 'spark-protocol'; +import WebhookFileRepository from '../src/repository/WebhookFileRepository'; +import WebhookManager from '../src/managers/WebhookManager'; +import TestData from './setup/TestData'; + +/* +auth +deviceID +errorResponseTopic +headers +mydevices +noDefaults +productIdOrSlug +query +rejectUnauthorized +requestType +responseTemplate +responseTopic +url +*/ + +const WEBHOOK_BASE = { + event: 'test-event', + requestType: 'POST', + url: 'https://test.com/', +}; + +const getEvent = (data?: string) => ({ + data, + deviceID: TestData.getID(), + name: 'test-event', + publishedAt: new Date(), + ttl: 60, + userID: TestData.getID(), +}); + +test.beforeEach(t => { + const repository = new WebhookFileRepository(''); + repository.getAll = sinon.stub().returns([]); + t.context.repository = repository; + const eventPublisher = new EventPublisher(); + eventPublisher.publish = sinon.stub(); + t.context.eventPublisher = eventPublisher; +}); + +test( + 'should run basic request', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const data = 'testData' + const event = getEvent(data); + + manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is(requestOptions.form, undefined); + t.is(requestOptions.headers, undefined); + t.is(requestOptions.json, false); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.qs, undefined); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(WEBHOOK_BASE, event); + }, +); + +test( + 'should compile json topic', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const data = '{"t":"123"}' + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + json: { + "testValue": "{{t}}", + }, + }; + + manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + t.is(requestOptions.auth, undefined); + t.is( + JSON.stringify(requestOptions.body), + JSON.stringify({testValue: '123'}), + ); + t.is(requestOptions.form, undefined); + t.is(requestOptions.headers, undefined); + t.is(requestOptions.json, true); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.qs, undefined); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should compile form body', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const data = '{"t":"123","g": "foo bar"}' + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + form: { + "testValue": "{{t}}", + "testValue2": "{{g}}", + }, + }; + manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify({testValue: '123', testValue2: 'foo bar'}), + ); + t.is(requestOptions.headers, undefined); + t.is(requestOptions.json, false); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.qs, undefined); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should compile request url', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const data = '{"t":"123","g": "foobar"}' + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + url: 'https://test.com/{{t}}/{{g}}', + }; + manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is(requestOptions.form, undefined), + t.is(requestOptions.headers, undefined); + t.is(requestOptions.json, false); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.qs, undefined); + t.is(requestOptions.url, 'https://test.com/123/foobar'); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should compile request query', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const data = '{"t":"123","g": "foobar"}' + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + query: { + "testValue": "{{t}}", + "testValue2": "{{g}}", + }, + }; + manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is(requestOptions.form, undefined), + t.is(requestOptions.headers, undefined); + t.is(requestOptions.json, false); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is( + JSON.stringify(requestOptions.qs), + JSON.stringify({testValue: '123', testValue2: 'foobar'}), + ); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should publish default topic', + async t => { + // TODO: Make sure that the default topic publishes. You should be able to + // use the next test as an example (copy/paste and tweak it) + }, +); + +test( + 'should compile response topic and publish', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const event = getEvent(); + const webhook = { + ...WEBHOOK_BASE, + responseTopic: 'hook-response/tappt_request-pour-{{SPARK_CORE_ID}}', + }; + manager._callWebhook = sinon.stub().returns('data'); + + t.context.eventPublisher.publish = sinon.spy(({ + data, + name, + userID, + }) => { + t.is(data.toString(), 'data'); + t.is(name, `hook-response/tappt_request-pour-${event.deviceID}/0`); + t.is(userID, event.userID); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should compile response body and publish', + async t => { + // TODO run most of the same code as the last test but use a + // responseTemplate + }, +); From 4d085bf0400e13089dc4b08e694306a672077dfd Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 18 Jan 2017 16:22:04 +0200 Subject: [PATCH 272/504] add webhook tests. --- src/types.js | 12 ++++ test/WebhookManager.test.js | 128 +++++++++++++++++++++++++++++------- 2 files changed, 115 insertions(+), 25 deletions(-) diff --git a/src/types.js b/src/types.js index 6e24b13c..230d8057 100644 --- a/src/types.js +++ b/src/types.js @@ -177,3 +177,15 @@ export type DeviceRepository = { renameDevice(deviceID: string, userID: string, name: string): Promise, unclaimDevice(deviceID: string, userID: string): Promise, }; + +export type RequestOptions = { + auth: { password: string, username: string }, + body: ?Object, + form: ?Object, + headers: ?Object, + json: boolean, + method: RequestType, + qs: ?Object, + strictSSL: boolean, + url: string, +}; diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index 8b57532a..721eac14 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -1,3 +1,5 @@ +import type { Event, RequestOptions, Webhook } from '../src/types'; + import test from 'ava'; import sinon from 'sinon'; @@ -28,7 +30,7 @@ const WEBHOOK_BASE = { url: 'https://test.com/', }; -const getEvent = (data?: string) => ({ +const getEvent = (data?: string): Event => ({ data, deviceID: TestData.getID(), name: 'test-event', @@ -51,10 +53,14 @@ test( async t => { const manager = new WebhookManager(t.context.repository, t.context.eventPublisher); - const data = 'testData' + const data = 'testData'; const event = getEvent(data); - manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { t.is(requestOptions.auth, undefined); t.is(requestOptions.body, undefined); t.is(requestOptions.form, undefined); @@ -74,20 +80,24 @@ test( async t => { const manager = new WebhookManager(t.context.repository, t.context.eventPublisher); - const data = '{"t":"123"}' + const data = '{"t":"123"}'; const event = getEvent(data); const webhook = { ...WEBHOOK_BASE, json: { - "testValue": "{{t}}", + testValue: '{{t}}', }, }; - manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { t.is(requestOptions.auth, undefined); t.is( JSON.stringify(requestOptions.body), - JSON.stringify({testValue: '123'}), + JSON.stringify({ testValue: '123' }), ); t.is(requestOptions.form, undefined); t.is(requestOptions.headers, undefined); @@ -106,21 +116,25 @@ test( async t => { const manager = new WebhookManager(t.context.repository, t.context.eventPublisher); - const data = '{"t":"123","g": "foo bar"}' + const data = '{"t":"123","g": "foo bar"}'; const event = getEvent(data); const webhook = { ...WEBHOOK_BASE, form: { - "testValue": "{{t}}", - "testValue2": "{{g}}", + testValue: '{{t}}', + testValue2: '{{g}}', }, }; - manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { t.is(requestOptions.auth, undefined); t.is(requestOptions.body, undefined); t.is( JSON.stringify(requestOptions.form), - JSON.stringify({testValue: '123', testValue2: 'foo bar'}), + JSON.stringify({ testValue: '123', testValue2: 'foo bar' }), ); t.is(requestOptions.headers, undefined); t.is(requestOptions.json, false); @@ -138,16 +152,20 @@ test( async t => { const manager = new WebhookManager(t.context.repository, t.context.eventPublisher); - const data = '{"t":"123","g": "foobar"}' + const data = '{"t":"123","g": "foobar"}'; const event = getEvent(data); const webhook = { ...WEBHOOK_BASE, url: 'https://test.com/{{t}}/{{g}}', }; - manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { t.is(requestOptions.auth, undefined); t.is(requestOptions.body, undefined); - t.is(requestOptions.form, undefined), + t.is(requestOptions.form, undefined); t.is(requestOptions.headers, undefined); t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); @@ -164,25 +182,29 @@ test( async t => { const manager = new WebhookManager(t.context.repository, t.context.eventPublisher); - const data = '{"t":"123","g": "foobar"}' + const data = '{"t":"123","g": "foobar"}'; const event = getEvent(data); const webhook = { ...WEBHOOK_BASE, query: { - "testValue": "{{t}}", - "testValue2": "{{g}}", + testValue: '{{t}}', + testValue2: '{{g}}', }, }; - manager._callWebhook = sinon.spy((webhook, event, requestOptions) => { + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { t.is(requestOptions.auth, undefined); t.is(requestOptions.body, undefined); - t.is(requestOptions.form, undefined), + t.is(requestOptions.form, undefined); t.is(requestOptions.headers, undefined); t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); t.is( JSON.stringify(requestOptions.qs), - JSON.stringify({testValue: '123', testValue2: 'foobar'}), + JSON.stringify({ testValue: '123', testValue2: 'foobar' }), ); t.is(requestOptions.url, WEBHOOK_BASE.url); }); @@ -191,11 +213,46 @@ test( }, ); +test( + 'should publish sent event', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const event = getEvent(); + + t.context.eventPublisher.publish = sinon.spy(({ + data, + name, + userID, + }) => { + t.is(data, undefined); + t.is(name, `hook-sent/${event.name}`); + t.is(userID, event.userID); + }); + + manager.runWebhook(WEBHOOK_BASE, event); + }, +); + test( 'should publish default topic', async t => { - // TODO: Make sure that the default topic publishes. You should be able to - // use the next test as an example (copy/paste and tweak it) + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const event = getEvent(); + manager._callWebhook = sinon.stub().returns('data'); + + t.context.eventPublisher.publish = sinon.spy(({ + data, + name, + userID, + }) => { + t.is(data.toString(), 'data'); + t.is(name, `hook-response/${event.name}/0`); + t.is(userID, event.userID); + }); + + manager.runWebhook(WEBHOOK_BASE, event); }, ); @@ -228,7 +285,28 @@ test( test( 'should compile response body and publish', async t => { - // TODO run most of the same code as the last test but use a - // responseTemplate + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const event = getEvent(); + const webhook = { + ...WEBHOOK_BASE, + responseTemplate: 'testVar: {{t}}, testVar2: {{g}}', + }; + manager._callWebhook = sinon.stub().returns({ + g: 'foobar', + t: 123, + }); + + t.context.eventPublisher.publish = sinon.spy(({ + data, + name, + userID, + }) => { + t.is(data.toString(), 'testVar: 123, testVar2: foobar'); + t.is(name, `hook-response/${event.name}/0`); + t.is(userID, event.userID); + }); + + manager.runWebhook(webhook, event); }, ); From f197dbbc52ffcd8f044937fd95ad24ddce933e28 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 18 Jan 2017 18:51:55 +0200 Subject: [PATCH 273/504] rename methods, return back MAX_RESPONSE_MESSAGE, add defaultRequestData --- src/managers/WebhookManager.js | 115 ++++++++++++++++++++------------- test/WebhookManager.test.js | 78 +++++++++++++++++++--- 2 files changed, 139 insertions(+), 54 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 01e68eca..affb353b 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -3,26 +3,37 @@ import type { Event, Repository, + RequestOptions, Webhook, WebhookMutator, } from '../types'; import type { EventPublisher } from 'spark-protocol'; -import querystring from 'querystring'; import hogan from 'hogan.js'; import HttpError from '../lib/HttpError'; import logger from '../lib/logger'; import request from 'request'; import throttle from 'lodash/throttle'; +const parseEventData = (event: Event): Object => { + try { + if (event.data) { + return JSON.parse(event.data); + } + return {}; + } catch (error) { + return {}; + } +}; + const splitBufferIntoChunks = ( buffer: Buffer, chunkSize: number, ): Array => { const chunks = []; - let i = 0; - while (i < buffer.length) { - chunks.push(buffer.slice(i, i += chunkSize)); + let ii = 0; + while (ii < buffer.length) { + chunks.push(buffer.slice(ii, ii += chunkSize)); } return chunks; @@ -31,6 +42,7 @@ const splitBufferIntoChunks = ( const MAX_WEBHOOK_ERRORS_COUNT = 10; const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; +const MAX_RESPONSE_MESSAGE_SIZE = 100000; class WebhookManager { _eventPublisher: EventPublisher; @@ -134,6 +146,8 @@ class WebhookManager { ), userID: event.userID, }); + + this.runWebhookThrottled(webhook, event); } catch (error) { logger.error(`webhookError: ${error}`); } @@ -142,29 +156,29 @@ class WebhookManager { runWebhook = async (webhook: Webhook, event: Event): Promise => { try { const webhookVariablesObject = - this._getEventData(event, webhook.noDefaults); + this._getEventVariables(event); - const requestJson = this._compileJsonTopic( + const requestJson = this._compileJsonTemplate( webhook.json, webhookVariablesObject, ); - const requestFormData = this._compileJsonTopic( + const requestFormData = this._compileJsonTemplate( webhook.form, webhookVariablesObject, ); - const requestUrl = this._compileTopic( + const requestUrl = this._compileTemplate( webhook.url, webhookVariablesObject, ); - const requestQuery = this._compileJsonTopic( + const requestQuery = this._compileJsonTemplate( webhook.query, webhookVariablesObject, ); - const responseTopic = this._compileTopic( + const responseTopic = this._compileTemplate( webhook.responseTopic, webhookVariablesObject, ); @@ -173,9 +187,11 @@ class WebhookManager { const requestOptions = { auth: webhook.auth, body: isJsonRequest - ? requestJson + ? this._getRequestData(requestJson, event, webhook.noDefaults) + : undefined, + form: !isJsonRequest + ? this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, - form: !isJsonRequest ? requestFormData : undefined, headers: webhook.headers, json: isJsonRequest, method: webhook.requestType, @@ -200,7 +216,10 @@ class WebhookManager { .render(responseBody); const chunks = splitBufferIntoChunks( - Buffer.from(responseTemplate || responseBody), + Buffer + .from(responseTemplate || responseBody) + .slice(0, MAX_RESPONSE_MESSAGE_SIZE) + , MAX_RESPONSE_MESSAGE_CHUNK_SIZE, ); @@ -220,17 +239,16 @@ class WebhookManager { } }; - _throttledWebhookHandler = throttle( + runWebhookThrottled = throttle( this.runWebhook, WEBHOOK_THROTTLE_TIME, { leading: false, trailing: true }, ); - // todo annotate requestOptions _callWebhook = ( webhook: Webhook, event: Event, - requestOptions: Object, + requestOptions: RequestOptions, ): Promise<*> => new Promise( (resolve, reject) => request( requestOptions, @@ -267,7 +285,7 @@ class WebhookManager { ), ); - _getEventData = (event: Event, noDefaults: boolean = false): Object => { + _getEventVariables = (event: Event): Object => { const defaultWebhookVariables = { PARTICLE_DEVICE_ID: event.deviceID, PARTICLE_EVENT_NAME: event.name, @@ -280,48 +298,55 @@ class WebhookManager { SPARK_PUBLISHED_AT: event.publishedAt, }; - const eventDataVariables = this._parseEventData(event); + const eventDataVariables = parseEventData(event); - return noDefaults - ? eventDataVariables - : { - ...defaultWebhookVariables, - ...eventDataVariables, - }; + return { + ...defaultWebhookVariables, + ...eventDataVariables, + }; }; - _parseEventData = (event: Event): Object => { - try { - if (event.data) { - return JSON.parse(event.data); - } - return {}; - } catch (error) { - return {}; - } - } + _getRequestData = ( + customData: ?Object, + event: Event, + noDefaults: boolean, + ): ?Object => { + const defaultEventData = { + coreid: event.deviceID, + data: event.data, + event: event.name, + published_at: event.publishedAt, + }; - _compileTopic = (topic?: ?string, variables: Object): ?string => - topic && hogan - .compile(topic) + return noDefaults + ? customData + : { ...defaultEventData, ...(customData || {}) }; + }; + + _compileTemplate = (template?: ?string, variables: Object): ?string => + template && hogan + .compile(template) .render(variables); - _compileJsonTopic = (topic?: ?Object, variables: Object): ?Object => { - if (!topic) { + _compileJsonTemplate = (template?: ?Object, variables: Object): ?Object => { + if (!template) { return; } - const compiledTopic = this._compileTopic(JSON.stringify(topic), variables); - if (!compiledTopic) { - return null; + const compiledTemplate = this._compileTemplate( + JSON.stringify(template), + variables, + ); + if (!compiledTemplate) { + return; } - return JSON.parse(compiledTopic); + return JSON.parse(compiledTemplate); }; _compileErrorResponseTopic = (webhook: Webhook, event: Event): string => { - const variables = this._getEventData(event, webhook.noDefaults); - return this._compileTopic( + const variables = this._getEventVariables(event); + return this._compileTemplate( webhook.errorResponseTopic, variables, ) || `hook-error/${event.name}`; diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index 721eac14..afbffbf8 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import type { Event, RequestOptions, Webhook } from '../src/types'; import test from 'ava'; @@ -39,6 +40,13 @@ const getEvent = (data?: string): Event => ({ userID: TestData.getID(), }); +const getDefaultRequestData = (event: Event): Object => ({ + coreid: event.deviceID, + data: event.data, + event: event.name, + published_at: event.publishedAt, +}); + test.beforeEach(t => { const repository = new WebhookFileRepository(''); repository.getAll = sinon.stub().returns([]); @@ -55,6 +63,7 @@ test( new WebhookManager(t.context.repository, t.context.eventPublisher); const data = 'testData'; const event = getEvent(data); + const defaultRequestData = getDefaultRequestData(event); manager._callWebhook = sinon.spy(( webhook: Webhook, @@ -63,7 +72,10 @@ test( ) => { t.is(requestOptions.auth, undefined); t.is(requestOptions.body, undefined); - t.is(requestOptions.form, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify(defaultRequestData), + ); t.is(requestOptions.headers, undefined); t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); @@ -76,7 +88,38 @@ test( ); test( - 'should compile json topic', + 'should run basic request without default data', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const webhook = { + ...WEBHOOK_BASE, + noDefaults: true, + }; + const data = 'testData'; + const event = getEvent(data); + + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is(requestOptions.form, undefined); + t.is(requestOptions.headers, undefined); + t.is(requestOptions.json, false); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.qs, undefined); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should compile json body', async t => { const manager = new WebhookManager(t.context.repository, t.context.eventPublisher); @@ -85,9 +128,10 @@ test( const webhook = { ...WEBHOOK_BASE, json: { - testValue: '{{t}}', + "testValue": "{{t}}" }, }; + const defaultRequestData = getDefaultRequestData(event); manager._callWebhook = sinon.spy(( webhook: Webhook, @@ -97,7 +141,7 @@ test( t.is(requestOptions.auth, undefined); t.is( JSON.stringify(requestOptions.body), - JSON.stringify({ testValue: '123' }), + JSON.stringify({ ...defaultRequestData, testValue: '123' }), ); t.is(requestOptions.form, undefined); t.is(requestOptions.headers, undefined); @@ -121,10 +165,12 @@ test( const webhook = { ...WEBHOOK_BASE, form: { - testValue: '{{t}}', - testValue2: '{{g}}', + "testValue": "{{t}}", + "testValue2": "{{g}}" }, }; + const defaultRequestData = getDefaultRequestData(event); + manager._callWebhook = sinon.spy(( webhook: Webhook, event: Event, @@ -134,7 +180,11 @@ test( t.is(requestOptions.body, undefined); t.is( JSON.stringify(requestOptions.form), - JSON.stringify({ testValue: '123', testValue2: 'foo bar' }), + JSON.stringify({ + ...defaultRequestData, + testValue: '123', + testValue2: 'foo bar', + }), ); t.is(requestOptions.headers, undefined); t.is(requestOptions.json, false); @@ -158,6 +208,8 @@ test( ...WEBHOOK_BASE, url: 'https://test.com/{{t}}/{{g}}', }; + const defaultRequestData = getDefaultRequestData(event); + manager._callWebhook = sinon.spy(( webhook: Webhook, event: Event, @@ -165,7 +217,10 @@ test( ) => { t.is(requestOptions.auth, undefined); t.is(requestOptions.body, undefined); - t.is(requestOptions.form, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify(defaultRequestData), + ); t.is(requestOptions.headers, undefined); t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); @@ -191,6 +246,8 @@ test( testValue2: '{{g}}', }, }; + const defaultRequestData = getDefaultRequestData(event); + manager._callWebhook = sinon.spy(( webhook: Webhook, event: Event, @@ -198,7 +255,10 @@ test( ) => { t.is(requestOptions.auth, undefined); t.is(requestOptions.body, undefined); - t.is(requestOptions.form, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify(defaultRequestData), + ); t.is(requestOptions.headers, undefined); t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); From 05a91417ea593a60559c8b38cc53e10e3043b124 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 19 Jan 2017 02:17:43 +0200 Subject: [PATCH 274/504] add json: true --- src/managers/WebhookManager.js | 24 +++++++++++++++--------- test/WebhookManager.test.js | 6 ------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index affb353b..5bdd6bab 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -193,7 +193,7 @@ class WebhookManager { ? this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, headers: webhook.headers, - json: isJsonRequest, + json: true, method: webhook.requestType, qs: requestQuery, strictSSL: webhook.rejectUnauthorized, @@ -205,21 +205,26 @@ class WebhookManager { event, requestOptions, ); + if (!responseBody) { return; } - // TODO: responseBody is a string/buffer. - // We should only render this if it has been converted to a JSON object - const responseTemplate = webhook.responseTemplate && hogan - .compile(webhook.responseTemplate) - .render(responseBody); + const isResponseBodyAnObject = responseBody === Object(responseBody); + + const responseTemplate = + webhook.responseTemplate && isResponseBodyAnObject && hogan + .compile(webhook.responseTemplate) + .render(responseBody); + + const responseEventData = responseTemplate || (isResponseBodyAnObject + ? JSON.stringify(responseBody) + : responseBody); const chunks = splitBufferIntoChunks( Buffer - .from(responseTemplate || responseBody) - .slice(0, MAX_RESPONSE_MESSAGE_SIZE) - , + .from(responseEventData) + .slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE, ); @@ -271,6 +276,7 @@ class WebhookManager { }); reject(error); + return; } this._resetWebhookErrorCounter(webhook.id); diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index afbffbf8..33f9c886 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -77,7 +77,6 @@ test( JSON.stringify(defaultRequestData), ); t.is(requestOptions.headers, undefined); - t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); t.is(requestOptions.qs, undefined); t.is(requestOptions.url, WEBHOOK_BASE.url); @@ -108,7 +107,6 @@ test( t.is(requestOptions.body, undefined); t.is(requestOptions.form, undefined); t.is(requestOptions.headers, undefined); - t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); t.is(requestOptions.qs, undefined); t.is(requestOptions.url, WEBHOOK_BASE.url); @@ -145,7 +143,6 @@ test( ); t.is(requestOptions.form, undefined); t.is(requestOptions.headers, undefined); - t.is(requestOptions.json, true); t.is(requestOptions.method, WEBHOOK_BASE.requestType); t.is(requestOptions.qs, undefined); t.is(requestOptions.url, WEBHOOK_BASE.url); @@ -187,7 +184,6 @@ test( }), ); t.is(requestOptions.headers, undefined); - t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); t.is(requestOptions.qs, undefined); t.is(requestOptions.url, WEBHOOK_BASE.url); @@ -222,7 +218,6 @@ test( JSON.stringify(defaultRequestData), ); t.is(requestOptions.headers, undefined); - t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); t.is(requestOptions.qs, undefined); t.is(requestOptions.url, 'https://test.com/123/foobar'); @@ -260,7 +255,6 @@ test( JSON.stringify(defaultRequestData), ); t.is(requestOptions.headers, undefined); - t.is(requestOptions.json, false); t.is(requestOptions.method, WEBHOOK_BASE.requestType); t.is( JSON.stringify(requestOptions.qs), From 9767e8bf3deec641c68292770fe09d2d0bc26edd Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 19 Jan 2017 02:58:09 +0200 Subject: [PATCH 275/504] add 'should set request headers' --- test/WebhookManager.test.js | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index 33f9c886..e57e1833 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -288,6 +288,45 @@ test( }, ); +test( + 'should set request headers', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const event = getEvent(); + const webhook = { + ...WEBHOOK_BASE, + headers: { + 'Custom-Header-1': '123', + 'Custom-Header-2': '123', + }, + }; + const defaultRequestData = getDefaultRequestData(event); + + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify(defaultRequestData), + ); + t.is( + requestOptions.headers, + webhook.headers, + ); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.qs, undefined); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + test( 'should publish default topic', async t => { From 91d0a26481346e9c9ac67ab14f201bec1689038e Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 19 Jan 2017 03:13:33 +0200 Subject: [PATCH 276/504] handling errors for responses with status > 400 --- src/managers/WebhookManager.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 5bdd6bab..0abf4c26 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -262,12 +262,11 @@ class WebhookManager { response: http$IncomingMessage, responseBody: string | Buffer | Object, ) => { - // todo check response.statusCode > 300 also. - if (error) { + const onResponseError = (errorMessage: ?string) => { this._incrementWebhookErrorCounter(webhook.id); this._eventPublisher.publish({ - data: error.message, + data: errorMessage, name: this._compileErrorResponseTopic( webhook, event, @@ -275,7 +274,15 @@ class WebhookManager { userID: event.userID, }); - reject(error); + reject(new Error(errorMessage)); + }; + + if (error) { + onResponseError(error.message); + return; + } + if (response.statusCode >= 400) { + onResponseError(response.statusMessage); return; } From 01d25eb90c0b14d853d993de76424f531f21ae25 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Wed, 18 Jan 2017 22:19:09 -0800 Subject: [PATCH 277/504] Cleanup and fixing flow errors. --- src/managers/WebhookManager.js | 21 +++++++-------------- src/types.js | 4 ++-- test/WebhookManager.test.js | 16 ---------------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 0abf4c26..2edcb792 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -10,6 +10,7 @@ import type { import type { EventPublisher } from 'spark-protocol'; import hogan from 'hogan.js'; +import nullthrows from 'nullthrows'; import HttpError from '../lib/HttpError'; import logger from '../lib/logger'; import request from 'request'; @@ -197,7 +198,7 @@ class WebhookManager { method: webhook.requestType, qs: requestQuery, strictSSL: webhook.rejectUnauthorized, - url: requestUrl, + url: nullthrows(requestUrl), }; const responseBody = await this._callWebhook( @@ -262,11 +263,11 @@ class WebhookManager { response: http$IncomingMessage, responseBody: string | Buffer | Object, ) => { - const onResponseError = (errorMessage: ?string) => { + if (error) { this._incrementWebhookErrorCounter(webhook.id); this._eventPublisher.publish({ - data: errorMessage, + data: error.message, name: this._compileErrorResponseTopic( webhook, event, @@ -274,17 +275,9 @@ class WebhookManager { userID: event.userID, }); - reject(new Error(errorMessage)); - }; - - if (error) { - onResponseError(error.message); + reject(error); return; - } - if (response.statusCode >= 400) { - onResponseError(response.statusMessage); - return; - } + }; this._resetWebhookErrorCounter(webhook.id); @@ -322,7 +315,7 @@ class WebhookManager { _getRequestData = ( customData: ?Object, event: Event, - noDefaults: boolean, + noDefaults: ?boolean = false, ): ?Object => { const defaultEventData = { coreid: event.deviceID, diff --git a/src/types.js b/src/types.js index 230d8057..c0ce5e45 100644 --- a/src/types.js +++ b/src/types.js @@ -179,13 +179,13 @@ export type DeviceRepository = { }; export type RequestOptions = { - auth: { password: string, username: string }, + auth?: { password: string, username: string }, body: ?Object, form: ?Object, headers: ?Object, json: boolean, method: RequestType, qs: ?Object, - strictSSL: boolean, + strictSSL?: boolean, url: string, }; diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index e57e1833..94cec0a1 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -9,22 +9,6 @@ import WebhookFileRepository from '../src/repository/WebhookFileRepository'; import WebhookManager from '../src/managers/WebhookManager'; import TestData from './setup/TestData'; -/* -auth -deviceID -errorResponseTopic -headers -mydevices -noDefaults -productIdOrSlug -query -rejectUnauthorized -requestType -responseTemplate -responseTopic -url -*/ - const WEBHOOK_BASE = { event: 'test-event', requestType: 'POST', From 2893c084331d932d885fb45ef1cbc6ba7402fb4d Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 20 Jan 2017 02:22:20 +0200 Subject: [PATCH 278/504] fix events filtering --- src/controllers/EventsController.js | 6 +++++- src/managers/EventManager.js | 5 +++-- src/managers/WebhookManager.js | 13 +++++-------- src/types.js | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index 98ac8ba8..690df0d2 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -50,6 +50,7 @@ class EventsController extends Controller { const subscriptionID = this._eventManager.subscribe( eventNamePrefix, this._pipeEvent.bind(this), + { userID: this.user.id }, ); await this._closeStream(subscriptionID); @@ -63,7 +64,10 @@ class EventsController extends Controller { const subscriptionID = this._eventManager.subscribe( eventNamePrefix, this._pipeEvent.bind(this), - { userID: this.user.id }, + { + mydevices: true, + userID: this.user.id, + }, ); await this._closeStream(subscriptionID); diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js index d337a286..c5b4f51f 100644 --- a/src/managers/EventManager.js +++ b/src/managers/EventManager.js @@ -5,7 +5,8 @@ import type { Event, EventData } from '../types'; type FilterOptions = { deviceID?: string, - userID?: string, + mydevices?: boolean, + userID: string, }; class EventManager { @@ -18,7 +19,7 @@ class EventManager { subscribe = ( eventNamePrefix: ?string, eventHandler: (event: Event) => void, - filterOptions?: FilterOptions, + filterOptions: FilterOptions, ): string => this._eventPublisher.subscribe( eventNamePrefix, diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 0abf4c26..c3229a79 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -101,9 +101,9 @@ class WebhookManager { const subscriptionID = this._eventPublisher.subscribe( webhook.event, this._onNewWebhookEvent(webhook), - // todo separate filtering for MY_DEVICES and for public/private events { deviceID: webhook.deviceID, + mydevices: webhook.mydevices, userID: webhook.ownerID, }, ); @@ -123,13 +123,6 @@ class WebhookManager { _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void => (event: Event) => { try { - if ( - webhook.mydevices && - webhook.ownerID !== event.userID - ) { - return; - } - const webhookErrorCount = this._errorsCountByWebhookID.get(webhook.id) || 0; @@ -140,6 +133,7 @@ class WebhookManager { this._eventPublisher.publish({ data: 'Too many errors, webhook disabled', + isPublic: false, name: this._compileErrorResponseTopic( webhook, event, @@ -235,6 +229,7 @@ class WebhookManager { this._eventPublisher.publish({ data: chunk, + isPublic: false, name: responseEventName, userID: event.userID, }); @@ -267,6 +262,7 @@ class WebhookManager { this._eventPublisher.publish({ data: errorMessage, + isPublic: false, name: this._compileErrorResponseTopic( webhook, event, @@ -289,6 +285,7 @@ class WebhookManager { this._resetWebhookErrorCounter(webhook.id); this._eventPublisher.publish({ + isPublic: false, name: `hook-sent/${event.name}`, userID: event.userID, }); diff --git a/src/types.js b/src/types.js index 230d8057..6a16aac0 100644 --- a/src/types.js +++ b/src/types.js @@ -80,7 +80,7 @@ export type EventData = { isPublic: boolean, name: string, ttl?: number, - userID?: ?string, + userID: string, }; export type GrantType = From e9ad603f2eae961b1267ea0b2120056f78778186 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 20 Jan 2017 17:00:18 +0200 Subject: [PATCH 279/504] fix eventsController endpoints routes --- src/controllers/EventsController.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index 690df0d2..d300aaf9 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -44,7 +44,7 @@ class EventsController extends Controller { } @httpVerb('get') - @route('/v1/events/:eventNamePrefix?') + @route('/v1/events/:eventNamePrefix?*') @serverSentEvents() async getEvents(eventNamePrefix: ?string): Promise<*> { const subscriptionID = this._eventManager.subscribe( @@ -58,7 +58,7 @@ class EventsController extends Controller { } @httpVerb('get') - @route('/v1/devices/events/:eventNamePrefix?') + @route('/v1/devices/events/:eventNamePrefix?*') @serverSentEvents() async getMyEvents(eventNamePrefix: ?string): Promise<*> { const subscriptionID = this._eventManager.subscribe( @@ -75,7 +75,7 @@ class EventsController extends Controller { } @httpVerb('get') - @route('/v1/devices/:deviceID/events/:eventName?/') + @route('/v1/devices/:deviceID/events/:eventNamePrefix?*') @serverSentEvents() async getDeviceEvents( deviceID: string, From e4a428848bcb9f654ae4b0a74451b39d52cb9ce9 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 21 Jan 2017 20:03:10 -0800 Subject: [PATCH 280/504] Added local compile. This is only tested on windows... I need to add a readme to describe the setup process --- flow-typed/npm/express_v4.x.x.js | 2 +- package.json | 2 + src/RouteConfig.js | 4 +- src/controllers/DevicesController.js | 32 +++- src/decorators/allowUpload.js | 14 +- src/managers/FirmwareCompilationManager.js | 207 +++++++++++++++++++++ src/settings.js | 2 + test/DevicesController.test.js | 3 +- 8 files changed, 253 insertions(+), 13 deletions(-) create mode 100644 src/managers/FirmwareCompilationManager.js diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 52b2ac9b..7f17c606 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -31,7 +31,7 @@ declare class express$Request extends http$IncomingMessage mixins express$Reques baseUrl: string; body: Object; cookies: {[cookie: string]: string}; - files: {[key: string]: Array<$File>}, + files: Array<$File>, fresh: boolean; hostname: boolean; ip: string; diff --git a/package.json b/package.json index 7b42c39e..5c52b991 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ "morgan": "^1.7.0", "multer": "^1.2.1", "request": "*", + "rimraf": "^2.5.4", + "rmfr": "^1.0.1", "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev", "ursa": "^0.9.4", "uuid": "^3.0.1" diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 6302539e..d6901828 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -97,7 +97,9 @@ export default ( maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), allowedUploads - ? injectFilesMiddleware.fields(allowedUploads) + ? allowedUploads.length + ? injectFilesMiddleware.fields(allowedUploads) + : injectFilesMiddleware.any() : defaultMiddleware, async (request: $Request, response: $Response): Promise => { const argumentNames = (route.match(/:[\w]*/g) || []).map( diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index c40510a0..41faad29 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -3,14 +3,20 @@ import type { Device, DeviceRepository } from '../types'; import type { DeviceAPIType } from '../lib/deviceToAPI'; - +import nullthrows from 'nullthrows'; import Controller from './Controller'; import HttpError from '../lib/HttpError'; +import FirmwareCompilationManager from '../managers/FirmwareCompilationManager'; import allowUpload from '../decorators/allowUpload'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; import deviceToAPI from '../lib/deviceToAPI'; +type CompileConfig = { + platform_id?: string, + product_id?: string, +}; + class DevicesController extends Controller { _deviceRepository: DeviceRepository; @@ -29,10 +35,30 @@ class DevicesController extends Controller { return this.ok({ ok: true }); } + @httpVerb('get') + @route('/v1/binaries/:binaryID') + async getAppFirmware(binaryID: string): Promise<*> { + return this.ok(FirmwareCompilationManager.getBinaryForID(binaryID)); + } + @httpVerb('post') @route('/v1/binaries') - compileSources() { // eslint-disable-line class-methods-use-this - throw new HttpError('not supported in the current server version'); + @allowUpload() + async compileSources(postBody: CompileConfig): Promise<*> { + const response = await FirmwareCompilationManager.compileSource( + nullthrows(postBody.platform_id || postBody.product_id), + this.request.files, + ); + + if (!response) { + throw new HttpError('Error during compilation'); + } + + return this.ok({ + ...response, + binary_url: `/v1/binaries/${response.binary_id}`, + ok: true, + }); } @httpVerb('delete') diff --git a/src/decorators/allowUpload.js b/src/decorators/allowUpload.js index cff27734..f9f876e0 100644 --- a/src/decorators/allowUpload.js +++ b/src/decorators/allowUpload.js @@ -5,16 +5,18 @@ import type Controller from '../controllers/Controller'; /* eslint-disable no-param-reassign */ export default ( - fileName: string, + fileName: ?string = undefined, maxCount: number = 0, ): Decorator => (target: Controller, name: $Keys, descriptor: Descriptor): Descriptor => { const allowedUploads = (target: any)[name].allowedUploads || []; - allowedUploads.push({ - maxCount, - name: fileName, - }); - + if (fileName) { + allowedUploads.push({ + maxCount, + name: fileName, + }); + } + (target: any)[name].allowedUploads = allowedUploads; return descriptor; }; diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js new file mode 100644 index 00000000..be054708 --- /dev/null +++ b/src/managers/FirmwareCompilationManager.js @@ -0,0 +1,207 @@ +// @flow + +import type { File } from 'express'; + +import fs from 'fs'; +import rmfr from 'rmfr'; +import {spawn} from 'child_process'; +import path from 'path'; +import crypto from 'crypto'; +import {knownPlatforms} from 'spark-protocol'; +import settings from '../settings'; + +const USER_APP_PATH = path.join( + settings.FIRMWARE_REPOSITORY_DIRECTORY, + 'user/applications', +); +const BIN_PATH = path.join( + settings.BUILD_DIRECTORY, + 'bin', +); +const MAKE_PATH = path.join( + settings.FIRMWARE_REPOSITORY_DIRECTORY, + 'main', +); + +type CompilationResponse = { + binary_id: string, + expires_at: Date, + errors?: Array, + sizeInfo: string, +}; + +const FILE_NAME_BY_KEY = new Map(); +const getKey = () => crypto + .randomBytes(24) + .toString('hex') + .substring(0, 24); + +const getUniqueKey = () => { + let key = getKey(); + while (FILE_NAME_BY_KEY.has(key)) { + key = getKey(); + } + return key; +}; + +class FirmwareCompilationManager { + static firmwareDirectorExists = (): boolean => { + return fs.existsSync(settings.FIRMWARE_REPOSITORY_DIRECTORY); + }; + + static getBinaryForID = (id: string): ?Buffer => { + const binaryPath = path.join(BIN_PATH, id); + if (!fs.existsSync(binaryPath)) { + return null; + } + + const binFileName = fs.readdirSync(binaryPath) + .find(file => file.endsWith('.bin')); + + if (!binFileName) { + return null; + } + + return fs.readFileSync(path.join(binaryPath, binFileName)); + } + + static compileSource = async ( + platformID: string, + files: Array, + ): Promise => { + if (!FirmwareCompilationManager.firmwareDirectorExists()) { + return null; + } + + let platformName = knownPlatforms[platformID]; + if (!platformName) { + return null; + } + + platformName = platformName.toLowerCase(); + const appFolder = + `${platformName}_firmware_${(new Date).getTime()}`.toLowerCase(); + const appPath = path.join(USER_APP_PATH, appFolder); + fs.mkdirSync(appPath); + + files.forEach(file => { + const fileName = file.originalname; + const fileExtension = path.extname(fileName); + let iterator = 0; + let combinedPath = path.join(appPath, fileName); + + while (fs.existsSync(combinedPath)) { + combinedPath = path.join( + appPath, + path.basename(fileName, fileExtension) + + `_${iterator++}` + + fileExtension, + ); + } + + fs.writeFileSync(combinedPath, file.buffer); + }); + + let id = getUniqueKey(); + const binPath = path.join(BIN_PATH, id); + const makeProcess = spawn( + 'make', + [ + `APP=${appFolder}`, + `PLATFORM_ID=${platformID}`, + `TARGET_DIR=${path.relative(MAKE_PATH, binPath).replace(/\\/g,'/')}`, + ], + {cwd: MAKE_PATH}, + ); + + const errors = []; + makeProcess.stderr.on('data', data => { + console.log(`${data}`) + errors.push(`${data}`); + }); + + let sizeInfo = 'not implemented' + makeProcess.stdout.on('data', data => { + const output = `${data}`; + + if (output.includes('text\t')) { + sizeInfo = output; + } + }); + + await new Promise(resolve => makeProcess.on('exit', () => resolve())); + + const date = new Date(); + date.setDate(date.getDate() + 1) + const config = { + binary_id: id, + errors, + // expire in one day + expires_at: date, + + // TODO: this variable has a bunch of extra crap including file names. + // we should filter out the string to only show the file sizes + sizeInfo, + }; + + FirmwareCompilationManager.addFirmwareCleanupTask( + appPath, + config, + ); + + return config; + }; + + static addFirmwareCleanupTask = ( + appFolderPath: string, + config: CompilationResponse, + ) => { + const configPath = path.join(appFolderPath, 'config.json'); + if (!fs.existsSync(configPath)) { + fs.writeFileSync(configPath, JSON.stringify(config)); + } + const currentDate = new Date(); + const difference = + new Date(config.expires_at).getTime() - currentDate.getTime(); + setTimeout( + () => rmfr(appFolderPath), + difference, + ); + } +} + +// Delete all expired binaries or queue them up to eventually be deleted. +if (!fs.existsSync(settings.BUILD_DIRECTORY)) { + fs.mkdirSync(settings.BUILD_DIRECTORY); +} +if (!fs.existsSync(BIN_PATH)) { + fs.mkdirSync(BIN_PATH); +} + +fs.readdirSync(USER_APP_PATH).forEach(file => { + const appFolder = path.join(USER_APP_PATH, file); + const configPath = path.join(appFolder, 'config.json'); + if (!fs.existsSync(configPath)) { + return; + } + + const configString = fs.readFileSync(configPath, 'utf8'); + if (!configString) { + return; + } + const config = JSON.parse(configString); + if (config.expires_at < new Date()) { + // TODO - clean up artifacts in the firmware folder. Every binary will have + // files in firmare/build/target/user & firmware/build/target/user-part + // It might make the most sense to just create a custom MAKE file to do this + rmfr(configPath); + rmfr(path.join(BIN_PATH, config.binary_id)) + } else { + FirmwareCompilationManager.addFirmwareCleanupTask( + appFolder, + config, + ); + } +}); + +export default FirmwareCompilationManager; diff --git a/src/settings.js b/src/settings.js index 352c9f78..28a8c59d 100644 --- a/src/settings.js +++ b/src/settings.js @@ -22,8 +22,10 @@ const path = require('path'); module.exports = { + BUILD_DIRECTORY: path.join(__dirname, './data/build'), DEVICE_DIRECTORY: path.join(__dirname, './data/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, './data/knownApps'), + FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../../spark-firmware'), SERVER_KEY_FILENAME: 'default_key.pem', SERVER_KEYS_DIRECTORY: path.join(__dirname, './data'), USERS_DIRECTORY: path.join(__dirname, './data/users'), diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index f0251473..0019d3c0 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -61,13 +61,12 @@ test.before(async () => { } }); -test('should throw an error for compile source code endpoint', async t => { +test.only('should throw an error for compile source code endpoint', async t => { const response = await request(app) .post('/v1/binaries') .query({ access_token: userToken }); t.is(response.status, 400); - t.is(response.body.error, 'not supported in the current server version'); }); test.serial('should return device details', async t => { From 9205f3412a7f32fac8708addeb15785a2aca811b Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 21 Jan 2017 20:08:00 -0800 Subject: [PATCH 281/504] Re-enable all tests --- test/DevicesController.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 0019d3c0..dd123128 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -61,7 +61,7 @@ test.before(async () => { } }); -test.only('should throw an error for compile source code endpoint', async t => { +test('should throw an error for compile source code endpoint', async t => { const response = await request(app) .post('/v1/binaries') .query({ access_token: userToken }); From 0af324c70a146fbb4665b3e65d4c37ebef56464e Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 23 Jan 2017 19:20:15 +0200 Subject: [PATCH 282/504] add stub productsController and OuathClientsController.js --- src/OAuthModel.js | 7 +-- src/app.js | 2 + src/controllers/OauthClientsController.js | 31 ++++++++++++++ src/controllers/ProductsController.js | 52 +++++++++++++++++++++++ src/defaultBindings.js | 12 ++++++ 5 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 src/controllers/OauthClientsController.js create mode 100644 src/controllers/ProductsController.js diff --git a/src/OAuthModel.js b/src/OAuthModel.js index 75388d8d..caeb1e0c 100644 --- a/src/OAuthModel.js +++ b/src/OAuthModel.js @@ -42,10 +42,8 @@ class OauthModel { client.clientId === clientId && client.clientSecret === clientSecret, ); - getUser = async (username: string, password: string): Promise => { - return await this._userRepository.validateLogin(username, password); - } - + getUser = async (username: string, password: string): Promise => + await this._userRepository.validateLogin(username, password); saveToken = ( tokenObject: TokenObject, @@ -60,7 +58,6 @@ class OauthModel { }; }; - // todo figure out this function validateScope = (user: User, client: Client, scope: string): string => 'true'; } diff --git a/src/app.js b/src/app.js index 85b57b20..3d2a3f86 100644 --- a/src/app.js +++ b/src/app.js @@ -57,6 +57,8 @@ export default ( // before DevicesController 'EventsController', 'DevicesController', + 'OauthClientsController', + 'ProductsController', 'ProvisioningController', 'UsersController', 'WebhooksController', diff --git a/src/controllers/OauthClientsController.js b/src/controllers/OauthClientsController.js new file mode 100644 index 00000000..793a6db7 --- /dev/null +++ b/src/controllers/OauthClientsController.js @@ -0,0 +1,31 @@ +// @flow + +import Controller from './Controller'; +import HttpError from '../lib/HttpError'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; + +class OauthClientsController extends Controller { + @httpVerb('post') + @route('/v1/products/:productIDorSlug/clients/') + // eslint-disable-next-line class-methods-use-this + async createClient(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } + + @httpVerb('put') + @route('/v1/products/:productIDorSlug/clients/:clientID') + // eslint-disable-next-line class-methods-use-this + async editClient(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } + + @httpVerb('delete') + @route('/v1/products/:productIDorSlug/clients/:clientID') + // eslint-disable-next-line class-methods-use-this + async deleteClient(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } +} + +export default OauthClientsController; diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js new file mode 100644 index 00000000..42770768 --- /dev/null +++ b/src/controllers/ProductsController.js @@ -0,0 +1,52 @@ +// @flow + +import Controller from './Controller'; +import HttpError from '../lib/HttpError'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; + +class ProductsController extends Controller { + @httpVerb('get') + @route('/v1/products') + // eslint-disable-next-line class-methods-use-this + async getProducts(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } + + @httpVerb('post') + @route('/v1/products') + // eslint-disable-next-line class-methods-use-this + async createProduct(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } + + @httpVerb('get') + @route('/v1/products/:productIdOrSlug') + // eslint-disable-next-line class-methods-use-this + async getProductDetails(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } + + @httpVerb('post') + @route('/v1/products/:productIdOrSlug/device_claims') + // eslint-disable-next-line class-methods-use-this + async generateClaimCode(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } + + @httpVerb('delete') + @route('/v1/products/:productIdOrSlug/devices/:deviceID') + // eslint-disable-next-line class-methods-use-this + async removeDeviceFromProduct(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } + + @httpVerb('delete') + @route('/v1/products/:productIdOrSlug/team/:username') + // eslint-disable-next-line class-methods-use-this + async removeTeamMember(): Promise<*> { + throw new HttpError('not supported in the current server version'); + } +} + +export default ProductsController; diff --git a/src/defaultBindings.js b/src/defaultBindings.js index 495b6d53..8ec02716 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -7,6 +7,8 @@ import { Transient } from 'constitute'; import DeviceClaimsController from './controllers/DeviceClaimsController'; import DevicesController from './controllers/DevicesController'; import EventsController from './controllers/EventsController'; +import OauthClientsController from './controllers/OauthClientsController'; +import ProductsController from './controllers/ProductsController'; import ProvisioningController from './controllers/ProvisioningController'; import UsersController from './controllers/UsersController'; import WebhooksController from './controllers/WebhooksController'; @@ -49,6 +51,16 @@ export default (container: Container) => { EventsController, Transient.with(['EventManager']), ); + container.bindClass( + 'OauthClientsController', + OauthClientsController, + Transient.with([]), + ); + container.bindClass( + 'ProductsController', + ProductsController, + Transient.with([]), + ); container.bindClass( 'ProvisioningController', ProvisioningController, From c6168fcdbf46d6709737d903544f7d3bb76a0f60 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 24 Jan 2017 00:53:37 +0200 Subject: [PATCH 283/504] fix eslint-errors add eslint precommit hook --- package.json | 5 +- src/OAuthModel.js | 1 + src/RouteConfig.js | 22 ++++--- src/app.js | 4 +- src/controllers/DevicesController.js | 2 +- src/decorators/allowUpload.js | 2 +- src/interfaces.js | 22 ------- src/lib/deviceToAPI.js | 2 +- src/lib/logger.js | 24 +++++--- src/main.js | 22 +++---- src/managers/FirmwareCompilationManager.js | 57 ++++++++++--------- src/managers/WebhookManager.js | 9 ++- .../DeviceFirmwareFileRepository.js | 2 +- src/repository/DeviceRepository.js | 40 ++++++------- src/repository/UserFileRepository.js | 11 ++-- src/repository/WebhookFileRepository.js | 5 +- src/settings.js | 4 +- src/types.js | 1 - test/DevicesController.test.js | 1 + test/ProvisioningController.test.js | 1 + test/UserFileRepository.test.js | 1 + test/UsersController.test.js | 1 + test/WebhooksController.test.js | 1 + test/setup/DeviceServerMock.js | 4 +- test/setup/SparkCoreMock.js | 4 +- test/setup/TestData.js | 10 ++-- test/setup/getDefaultContainer.js | 4 +- test/setup/settings.js | 9 +-- test/setup/testApp.js | 1 - 29 files changed, 130 insertions(+), 142 deletions(-) delete mode 100644 src/interfaces.js diff --git a/package.json b/package.json index 5c52b991..f1fe78cc 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,10 @@ "test": "ava --serial", "test:watch": "ava --watch --serial" }, - "pre-commit": "test", + "pre-commit": [ + "lint", + "test" + ], "ava": { "verbose": true, "babel": "inherit", diff --git a/src/OAuthModel.js b/src/OAuthModel.js index caeb1e0c..df4fcb08 100644 --- a/src/OAuthModel.js +++ b/src/OAuthModel.js @@ -58,6 +58,7 @@ class OauthModel { }; }; + // eslint-disable-next-line no-unused-vars validateScope = (user: User, client: Client, scope: string): string => 'true'; } diff --git a/src/RouteConfig.js b/src/RouteConfig.js index d6901828..09f2bf37 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -7,9 +7,8 @@ import type { Middleware, NextFunction, } from 'express'; -import type {Container} from 'constitute'; +import type { Container } from 'constitute'; import type { Settings } from './types'; -import type Controller from './controllers/Controller'; import OAuthServer from 'express-oauth-server'; import multer from 'multer'; @@ -55,9 +54,6 @@ const serverSentEventsMiddleware = (): Middleware => next(); }; -const defaultMiddleware = - (request: $Request, response: $Response, next: NextFunction): mixed => next(); - export default ( app: $Application, container: Container, @@ -69,7 +65,14 @@ export default ( allowBearerTokensInQueryString: true, model: new OAuthModel(container.constitute('UserRepository')), }); - const injectFilesMiddleware = multer(); + + // eslint-disable-next-line no-confusing-arrow + const filesMiddleware = (allowedUploads: ?Array<{ + maxCount: number, + name: string, + }> = []): Middleware => allowedUploads.length + ? multer().fields(allowedUploads) + : multer().any(); app.post(settings.loginRoute, oauth.token()); @@ -90,17 +93,12 @@ export default ( if (!httpVerb) { return; } - (app: any)[httpVerb]( route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), - allowedUploads - ? allowedUploads.length - ? injectFilesMiddleware.fields(allowedUploads) - : injectFilesMiddleware.any() - : defaultMiddleware, + maybe(filesMiddleware(allowedUploads), allowedUploads), async (request: $Request, response: $Response): Promise => { const argumentNames = (route.match(/:[\w]*/g) || []).map( (argumentName: string): string => argumentName.replace(':', ''), diff --git a/src/app.js b/src/app.js index 3d2a3f86..a5596619 100644 --- a/src/app.js +++ b/src/app.js @@ -7,8 +7,8 @@ import type { Middleware, NextFunction, } from 'express'; -import type {Container} from 'constitute'; -import type {Settings} from './types'; +import type { Container } from 'constitute'; +import type { Settings } from './types'; import bodyParser from 'body-parser'; import express from 'express'; diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 41faad29..bfc9e669 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -167,7 +167,7 @@ class DevicesController extends Controller { this.user.id, !!parseInt(postBody.signal, 10), ); - return this.ok({id: deviceID, ok: true}); + return this.ok({ id: deviceID, ok: true }); } throw new HttpError('Did not update device'); diff --git a/src/decorators/allowUpload.js b/src/decorators/allowUpload.js index f9f876e0..7543cc0e 100644 --- a/src/decorators/allowUpload.js +++ b/src/decorators/allowUpload.js @@ -16,7 +16,7 @@ export default ( name: fileName, }); } - + (target: any)[name].allowedUploads = allowedUploads; return descriptor; }; diff --git a/src/interfaces.js b/src/interfaces.js deleted file mode 100644 index 37087ea0..00000000 --- a/src/interfaces.js +++ /dev/null @@ -1,22 +0,0 @@ - -class Abstract { - constructor() { - if (new.target === Abstract) { - throw new TypeError("Cannot construct Abstract instances directly"); - } - } -} - -class IDeviceFirmwareRepository extends Abstract {} -class IDeviceRepository extends Abstract {} -class IEventManager extends Abstract {} -class IUserRepository extends Abstract {} -class IWebhookRepository extends Abstract {} - -export { - IDeviceFirmwareRepository, - IDeviceRepository, - IEventManager, - IUserRepository, - IWebhookRepository, -}; diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js index 9ac6c046..c0324e62 100644 --- a/src/lib/deviceToAPI.js +++ b/src/lib/deviceToAPI.js @@ -26,8 +26,8 @@ const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ connected: device.connected, current_build_target: device.currentBuildTarget, functions: device.functions, - imei: device.imei, id: device.deviceID, + imei: device.imei, last_app: device.lastFlashedAppName, last_heard: device.lastHeard, last_iccid: device.last_iccid, diff --git a/src/lib/logger.js b/src/lib/logger.js index 15ed0884..0bd2c049 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -14,13 +14,21 @@ * along with this program. If not, see . * * You can download the source here: https://github.com/spark/spark-server +* +* @flow +* */ -module.exports = { - log: function() { - console.log.apply(console, arguments); - }, - error: function() { - console.error.apply(console, arguments); - }, -}; +class Logger { + static log() { + // eslint-disable-next-line prefer-rest-params + console.log(...arguments); + } + + static error() { + // eslint-disable-next-line prefer-rest-params + console.error(...arguments); + } +} + +export default Logger; diff --git a/src/main.js b/src/main.js index c3af255d..eefac822 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,6 @@ // @flow -import {Container} from 'constitute'; +import { Container } from 'constitute'; import os from 'os'; import arrayFlatten from 'array-flatten'; import logger from './lib/logger'; @@ -13,7 +13,7 @@ const NODE_PORT = process.env.NODE_PORT || 8080; process.on('uncaughtException', (exception: Error) => { logger.error( 'uncaughtException', - { message : exception.message, stack : exception.stack }, + { message: exception.message, stack: exception.stack }, ); // logging with MetaData process.exit(1); // exit with failure }); @@ -43,14 +43,16 @@ app.listen( ); const addresses = arrayFlatten( - Object.entries(os.networkInterfaces()).map(([name, nic]) => - (nic: any) - .filter(address => - address.family === 'IPv4' && - address.address !== '127.0.0.1', - ) - .map(address => address.address), - ), + Object.entries(os.networkInterfaces()).map( + // eslint-disable-next-line no-unused-vars + ([name, nic]: Array): Array => + (nic: any) + .filter((address: Object): boolean => + address.family === 'IPv4' && + address.address !== '127.0.0.1', + ) + .map((address: Object): boolean => address.address), + ), ); addresses.forEach((address: string): void => console.log(`Your device server IP address is: ${address}`), diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js index be054708..81c33364 100644 --- a/src/managers/FirmwareCompilationManager.js +++ b/src/managers/FirmwareCompilationManager.js @@ -2,12 +2,12 @@ import type { File } from 'express'; +import crypto from 'crypto'; import fs from 'fs'; -import rmfr from 'rmfr'; -import {spawn} from 'child_process'; import path from 'path'; -import crypto from 'crypto'; -import {knownPlatforms} from 'spark-protocol'; +import rmfr from 'rmfr'; +import { spawn } from 'child_process'; +import { knownPlatforms } from 'spark-protocol'; import settings from '../settings'; const USER_APP_PATH = path.join( @@ -31,12 +31,13 @@ type CompilationResponse = { }; const FILE_NAME_BY_KEY = new Map(); -const getKey = () => crypto + +const getKey = (): string => crypto .randomBytes(24) .toString('hex') .substring(0, 24); -const getUniqueKey = () => { +const getUniqueKey = (): string => { let key = getKey(); while (FILE_NAME_BY_KEY.has(key)) { key = getKey(); @@ -45,9 +46,8 @@ const getUniqueKey = () => { }; class FirmwareCompilationManager { - static firmwareDirectorExists = (): boolean => { - return fs.existsSync(settings.FIRMWARE_REPOSITORY_DIRECTORY); - }; + static firmwareDirectorExists = (): boolean => + fs.existsSync(settings.FIRMWARE_REPOSITORY_DIRECTORY); static getBinaryForID = (id: string): ?Buffer => { const binaryPath = path.join(BIN_PATH, id); @@ -56,14 +56,14 @@ class FirmwareCompilationManager { } const binFileName = fs.readdirSync(binaryPath) - .find(file => file.endsWith('.bin')); + .find((file: string): boolean => file.endsWith('.bin')); if (!binFileName) { return null; } return fs.readFileSync(path.join(binaryPath, binFileName)); - } + }; static compileSource = async ( platformID: string, @@ -80,11 +80,11 @@ class FirmwareCompilationManager { platformName = platformName.toLowerCase(); const appFolder = - `${platformName}_firmware_${(new Date).getTime()}`.toLowerCase(); + `${platformName}_firmware_${(new Date()).getTime()}`.toLowerCase(); const appPath = path.join(USER_APP_PATH, appFolder); fs.mkdirSync(appPath); - files.forEach(file => { + files.forEach((file: File) => { const fileName = file.originalname; const fileExtension = path.extname(fileName); let iterator = 0; @@ -93,35 +93,34 @@ class FirmwareCompilationManager { while (fs.existsSync(combinedPath)) { combinedPath = path.join( appPath, - path.basename(fileName, fileExtension) + - `_${iterator++}` + - fileExtension, + `${path.basename(fileName, fileExtension)}` + + `_${iterator++}${fileExtension}`, // eslint-disable-line no-plusplus ); } fs.writeFileSync(combinedPath, file.buffer); }); - let id = getUniqueKey(); + const id = getUniqueKey(); const binPath = path.join(BIN_PATH, id); const makeProcess = spawn( 'make', [ `APP=${appFolder}`, `PLATFORM_ID=${platformID}`, - `TARGET_DIR=${path.relative(MAKE_PATH, binPath).replace(/\\/g,'/')}`, + `TARGET_DIR=${path.relative(MAKE_PATH, binPath).replace(/\\/g, '/')}`, ], - {cwd: MAKE_PATH}, + { cwd: MAKE_PATH }, ); const errors = []; - makeProcess.stderr.on('data', data => { - console.log(`${data}`) + makeProcess.stderr.on('data', (data: string) => { + console.log(`${data}`); errors.push(`${data}`); }); - let sizeInfo = 'not implemented' - makeProcess.stdout.on('data', data => { + let sizeInfo = 'not implemented'; + makeProcess.stdout.on('data', (data: string) => { const output = `${data}`; if (output.includes('text\t')) { @@ -129,10 +128,12 @@ class FirmwareCompilationManager { } }); - await new Promise(resolve => makeProcess.on('exit', () => resolve())); + await new Promise((resolve: () => void): void => + makeProcess.on('exit', (): void => resolve()), + ); const date = new Date(); - date.setDate(date.getDate() + 1) + date.setDate(date.getDate() + 1); const config = { binary_id: id, errors, @@ -164,7 +165,7 @@ class FirmwareCompilationManager { const difference = new Date(config.expires_at).getTime() - currentDate.getTime(); setTimeout( - () => rmfr(appFolderPath), + (): void => rmfr(appFolderPath), difference, ); } @@ -178,7 +179,7 @@ if (!fs.existsSync(BIN_PATH)) { fs.mkdirSync(BIN_PATH); } -fs.readdirSync(USER_APP_PATH).forEach(file => { +fs.readdirSync(USER_APP_PATH).forEach((file: string) => { const appFolder = path.join(USER_APP_PATH, file); const configPath = path.join(appFolder, 'config.json'); if (!fs.existsSync(configPath)) { @@ -195,7 +196,7 @@ fs.readdirSync(USER_APP_PATH).forEach(file => { // files in firmare/build/target/user & firmware/build/target/user-part // It might make the most sense to just create a custom MAKE file to do this rmfr(configPath); - rmfr(path.join(BIN_PATH, config.binary_id)) + rmfr(path.join(BIN_PATH, config.binary_id)); } else { FirmwareCompilationManager.addFirmwareCleanupTask( appFolder, diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index c3229a79..5299739b 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -250,7 +250,10 @@ class WebhookManager { event: Event, requestOptions: RequestOptions, ): Promise<*> => new Promise( - (resolve, reject) => request( + ( + resolve: (responseBody: string | Buffer | Object) => void, + reject: (error: Error) => void, + ): void => request( requestOptions, ( error: ?Error, @@ -340,7 +343,7 @@ class WebhookManager { _compileJsonTemplate = (template?: ?Object, variables: Object): ?Object => { if (!template) { - return; + return undefined; } const compiledTemplate = this._compileTemplate( @@ -348,7 +351,7 @@ class WebhookManager { variables, ); if (!compiledTemplate) { - return; + return undefined; } return JSON.parse(compiledTemplate); diff --git a/src/repository/DeviceFirmwareFileRepository.js b/src/repository/DeviceFirmwareFileRepository.js index 14f0693d..4e1ead09 100644 --- a/src/repository/DeviceFirmwareFileRepository.js +++ b/src/repository/DeviceFirmwareFileRepository.js @@ -9,7 +9,7 @@ class DeviceFirmwareFileRepository { this._fileManager = new FileManager(path, false); } - @memoizeGet([], {promise: false}) + @memoizeGet([], { promise: false }) getByName(appName: string): ?Buffer { return this._fileManager.getFileBuffer(`${appName}.bin`); } diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index dbe6c058..7f70a3ff 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -10,11 +10,9 @@ import type { } from '../types'; import type DeviceFirmwareRepository from './DeviceFirmwareFileRepository'; -import fs from 'fs'; import Moniker from 'moniker'; import ursa from 'ursa'; import HttpError from '../lib/HttpError'; -import settings from '../settings'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); @@ -133,23 +131,25 @@ class DeviceRepository { getAll = async (userID: string): Promise> => { const devicesAttributes = await this._deviceAttributeRepository.getAll(userID); - const devicePromises = devicesAttributes.map(async attributes => { - const device = this._deviceServer.getDevice(attributes.deviceID); - - const pingResponse = device - ? device.ping() - : { - connected: false, - lastPing: null, + const devicePromises = devicesAttributes.map( + async (attributes: DeviceAttributes): Promise => { + const device = this._deviceServer.getDevice(attributes.deviceID); + + const pingResponse = device + ? device.ping() + : { + connected: false, + lastPing: null, + }; + + return { + ...attributes, + connected: pingResponse.connected, + lastFlashedAppName: null, + lastHeard: pingResponse.lastPing, }; - - return { - ...attributes, - connected: pingResponse.connected, - lastFlashedAppName: null, - lastHeard: pingResponse.lastPing, - }; - }); + }, + ); return Promise.all(devicePromises); }; @@ -205,7 +205,7 @@ class DeviceRepository { flashBinary = async ( deviceID: string, file: File, - ) => { + ): Promise => { const device = this._deviceServer.getDevice(deviceID); if (!device) { throw new HttpError('Could not get device for ID', 404); @@ -218,7 +218,7 @@ class DeviceRepository { deviceID: string, userID: string, appName: string, - ) => { + ): Promise => { if (await !this._deviceAttributeRepository.doesUserHaveAccess( deviceID, userID, diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js index fead5db7..5a382b94 100644 --- a/src/repository/UserFileRepository.js +++ b/src/repository/UserFileRepository.js @@ -47,13 +47,13 @@ class UserFileRepository { this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); return modelToSave; - }; + } @memoizeSet() async update(model: User): Promise { this._fileManager.writeFile(`${model.id}.json`, model); return model; - }; + } @memoizeGet() async getAll(): Promise> { @@ -93,13 +93,12 @@ class UserFileRepository { // This isn't a good one to memoize as we can't key off user ID and there // isn't a good way to clear the cache. - getByAccessToken = async (accessToken: string): Promise => { - return (await this.getAll()).find((user: User): boolean => + getByAccessToken = async (accessToken: string): Promise => + (await this.getAll()).find((user: User): boolean => user.accessTokens.some((tokenObject: TokenObject): boolean => tokenObject.accessToken === accessToken, ), ); - } deleteAccessToken = async (userID: string, token: string): Promise<*> => { const user = await this.getById(userID); @@ -116,7 +115,7 @@ class UserFileRepository { }; await this.update(userToSave); - } + }; @memoizeSet(['id']) async deleteById(id: string): Promise { diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js index 97e1a79c..c3c54efd 100644 --- a/src/repository/WebhookFileRepository.js +++ b/src/repository/WebhookFileRepository.js @@ -3,7 +3,7 @@ import type { Webhook, WebhookMutator } from '../types'; import uuid from 'uuid'; -import { JSONFileManager, memoizeGet, memoizeSet } from 'spark-protocol'; +import { JSONFileManager, memoizeGet, memoizeSet } from 'spark-protocol'; import HttpError from '../lib/HttpError'; class WebhookFileRepository { @@ -28,7 +28,7 @@ class WebhookFileRepository { this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); return modelToSave; - }; + } @memoizeSet(['id']) async deleteById(id: string): Promise { @@ -60,6 +60,7 @@ class WebhookFileRepository { return webhook; }; + // eslint-disable-next-line no-unused-vars update = async (model: WebhookMutator): Promise => { throw new HttpError('Not implemented'); }; diff --git a/src/settings.js b/src/settings.js index 28a8c59d..957e5476 100644 --- a/src/settings.js +++ b/src/settings.js @@ -20,7 +20,7 @@ */ const path = require('path'); - +/* eslint-disable sorting/sort-object-props */ module.exports = { BUILD_DIRECTORY: path.join(__dirname, './data/build'), DEVICE_DIRECTORY: path.join(__dirname, './data/deviceKeys'), @@ -50,5 +50,5 @@ module.exports = { serverKeyPassEnvVar: null, PORT: 5683, - HOST: "localhost", + HOST: 'localhost', }; diff --git a/src/types.js b/src/types.js index ebff5ce5..1a69ba44 100644 --- a/src/types.js +++ b/src/types.js @@ -1,7 +1,6 @@ // @flow import type { File } from 'express'; -import type DeviceFirmwareRepository from './repository/DeviceFirmwareFileRepository'; export type Webhook = { auth?: { password: string, username: string }, diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index dd123128..b5d4fddd 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js index 2145c585..4a9ff042 100644 --- a/test/ProvisioningController.test.js +++ b/test/ProvisioningController.test.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import test from 'ava'; import request from 'supertest-as-promised'; import ouathClients from '../src/oauthClients.json'; diff --git a/test/UserFileRepository.test.js b/test/UserFileRepository.test.js index 223b12d2..e8b2d28b 100644 --- a/test/UserFileRepository.test.js +++ b/test/UserFileRepository.test.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import test from 'ava'; import sinon from 'sinon'; import TestData from './setup/TestData'; diff --git a/test/UsersController.test.js b/test/UsersController.test.js index 6288d960..2ab2af1f 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import type { TokenObject, UserCredentials } from '../src/types'; import test from 'ava'; diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index ecd5a0ab..445fd053 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -1,3 +1,4 @@ +/* eslint-disable */ import type { Webhook, WebhookMutator } from '../src/types'; import test from 'ava'; diff --git a/test/setup/DeviceServerMock.js b/test/setup/DeviceServerMock.js index c8ef8cae..6234295b 100644 --- a/test/setup/DeviceServerMock.js +++ b/test/setup/DeviceServerMock.js @@ -3,9 +3,7 @@ import SparkCoreMock from './SparkCoreMock'; class DeviceServerMock { - getDevice(): SparkCoreMock { - return new SparkCoreMock(); - } + getDevice = (): SparkCoreMock => new SparkCoreMock(); } export default DeviceServerMock; diff --git a/test/setup/SparkCoreMock.js b/test/setup/SparkCoreMock.js index 6d2c887a..775e0c33 100644 --- a/test/setup/SparkCoreMock.js +++ b/test/setup/SparkCoreMock.js @@ -1,9 +1,7 @@ // @flow class SparkCoreMock { - onApiMessage() { - return true; - } + onApiMessage = (): boolean => true; getVariableValue = (): any => 0; diff --git a/test/setup/TestData.js b/test/setup/TestData.js index 8065892d..fdc53e96 100644 --- a/test/setup/TestData.js +++ b/test/setup/TestData.js @@ -7,12 +7,10 @@ const uuidSet = new Set(); const privateKeys = new Set(); class TestData { - static getUser = (): {password: string, username: string} => { - return { - password: 'password', - username: `testUser+${TestData.getID()}@test.com`, - }; - }; + static getUser = (): { password: string, username: string } => ({ + password: 'password', + username: `testUser+${TestData.getID()}@test.com`, + }); static getID = (): string => { let newID = uuid(); diff --git a/test/setup/getDefaultContainer.js b/test/setup/getDefaultContainer.js index c770c551..d03f5889 100644 --- a/test/setup/getDefaultContainer.js +++ b/test/setup/getDefaultContainer.js @@ -1,6 +1,6 @@ // @flow -import {Container} from 'constitute'; +import { Container } from 'constitute'; import defaultBindings from '../../src/defaultBindings'; import settings from './settings'; import DeviceServerMock from './DeviceServerMock'; @@ -21,4 +21,4 @@ container.bindValue('WEBHOOKS_DIRECTORY', settings.WEBHOOKS_DIRECTORY); container.bindAlias('DeviceServer', DeviceServerMock); -export default () => container; +export default (): Container => container; diff --git a/test/setup/settings.js b/test/setup/settings.js index 898a30ba..006f4a25 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -1,11 +1,8 @@ // @flow import path from 'path'; -import DeviceFirmwareFileRepository from '../../src/repository/DeviceFirmwareFileRepository'; -import WebhookFileRepository from '../../src/repository/WebhookFileRepository'; -import UsersFileRepository from '../../src/repository/UserFileRepository'; -import { DeviceAttributeFileRepository, DeviceKeyFileRepository } from 'spark-protocol'; +/* eslint-disable sorting/sort-object-props */ export default { DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'), @@ -29,10 +26,10 @@ export default { * Your server crypto keys! */ cryptoSalt: 'aes-128-cbc', - serverKeyFile: "default_key.pem", + serverKeyFile: 'default_key.pem', serverKeyPassFile: null, serverKeyPassEnvVar: null, PORT: 5683, - HOST: "localhost", + HOST: 'localhost', }; diff --git a/test/setup/testApp.js b/test/setup/testApp.js index 75f3c507..57404f60 100644 --- a/test/setup/testApp.js +++ b/test/setup/testApp.js @@ -1,6 +1,5 @@ // @flow -import {Container} from 'constitute'; import createApp from '../../src/app'; import settings from './settings'; import getDefaultContainer from './getDefaultContainer'; From 16b6e1401c44b0b7c43db5379df16f309c315d9c Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 24 Jan 2017 13:03:45 +0200 Subject: [PATCH 284/504] disable no-confusing-arrow eslint rule --- .eslintrc | 1 + src/RouteConfig.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 9e755d04..431dd96c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,6 +37,7 @@ "import/no-unresolved": 2, "import/prefer-default-export": 0, "newline-per-chained-call": 0, + "no-confusing-arrow": 0, "no-console": 0, "no-duplicate-imports": 0, "no-mixed-operators": 0, diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 09f2bf37..57f6b56d 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -66,7 +66,6 @@ export default ( model: new OAuthModel(container.constitute('UserRepository')), }); - // eslint-disable-next-line no-confusing-arrow const filesMiddleware = (allowedUploads: ?Array<{ maxCount: number, name: string, From 4fc03a1725b447302989ff32d6c0d6f88feda7a2 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Tue, 24 Jan 2017 07:54:06 -0800 Subject: [PATCH 285/504] Adding stubs for ProductsController.js --- src/controllers/ProductsController.js | 138 ++++++++++++++++++++++++++ src/types.js | 13 +++ 2 files changed, 151 insertions(+) create mode 100644 src/controllers/ProductsController.js diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js new file mode 100644 index 00000000..6217d9d0 --- /dev/null +++ b/src/controllers/ProductsController.js @@ -0,0 +1,138 @@ +// @flow + +import type { DeviceRepository } from '../types'; + +import Controller from './Controller'; +import httpVerb from '../decorators/httpVerb'; +import route from '../decorators/route'; +import deviceToAPI from '../lib/deviceToAPI'; +import HttpError from '../lib/HttpError'; + +class ProductsController extends Controller { + _deviceRepository: DeviceRepository; + + constructor(deviceRepository: DeviceRepository) { + super(); + + this._deviceRepository = deviceRepository; + } + + @httpVerb('get') + @route('/v1/products') + async getAll( + ): Promise<*> { + throw new HttpError('Not implemented'); + } + + @httpVerb('get') + @route('/v1/products/:productIdOrSlug') + async getProduct( + productIdOrSlug: string, + ): Promise<*> { + throw new HttpError('Not implemented'); + } + @httpVerb('get') + @route('/v1/products/:productIdOrSlug/firmware') + async getFirmware( + productIdOrSlug: string, + ): Promise<*> { + throw new HttpError('Not implemented'); + } + + // {version: number, name: 'current', binary: File, title: string, description: string} + @httpVerb('post') + @route('/v1/products/:productIdOrSlug/firmware') + async getFirmware( + productIdOrSlug: string, + ): Promise<*> { + /* + { + "updated_at": "2017-01-23T05:55:11.592Z", + "uploaded_on": "2017-01-23T05:55:11.592Z", + "uploaded_by": { + "__v": 3, + "_id": "aaa", + "access_token": "asdf", + "access_token_expires_at": "2015-05-14T02:46:54.216Z", + "created_at": "2014-12-05T14:17:32.000Z", + "updated_at": "2017-01-14T15:45:26.877Z", + "username": "foo@gmail.com", + "tos": { + "accepted": true + }, + "subscription_ids": [ + 3632, + 3633, + 7312 + ] + }, + "version": 1, + "product_id": 647, + "size": 40648, + "name": "p1_firmware_1485150795661.bin", + "title": "Test", + "description": "test", + "_id": "asdf", + "current": false, + "device_count": 0 + } + */ + throw new HttpError('Not implemented'); + } + + @httpVerb('get') + @route('/v1/products/:productIdOrSlug/devices') + async getDevices( + productIdOrSlug: string, + ): Promise<*> { + throw new HttpError('Not implemented'); + } + + @httpVerb('put') + @route('/v1/products/:productIdOrSlug/devices/:deviceID') + async setFirmwareVersion( + productIdOrSlug: string, + deviceID: string, + body: {desired_firmware_version: number}, + ): Promise<*> { + /* + { + "desired_firmware_version": 1, + "updated_at": "2017-01-23T05:59:35.809Z", + "id": "some_device_id" + } + */ + throw new HttpError('Not implemented'); + } + + @httpVerb('get') + @route('/v1/products/:productIdOrSlug/config') + async getConfig( + productIdOrSlug: string, + ): Promise<*> { + /* + { + "product_configuration": [ + { + "org_id": "57a0a70786ddb6f9501032d6", + "__v": 0, + "id": "57a0a71086ddb6f9501032d8", + "product_id": 647 + } + ] + } + */ + throw new HttpError('Not implemented'); + } + + @httpVerb('get') + @route('/v1/products/:productIdOrSlug/events/:eventPrefix?*') + async getEvents( + productIdOrSlug: string, + eventName: string, + ): Promise<*> { + throw new HttpError('Not implemented'); + } +} + +export default ProductsController; diff --git a/src/types.js b/src/types.js index ebff5ce5..029ff578 100644 --- a/src/types.js +++ b/src/types.js @@ -189,3 +189,16 @@ export type RequestOptions = { strictSSL?: boolean, url: string, }; + +export type Product = { + config_id: string, + description: string, + hardware_version: string, + id: string, + name: string, + organization: string, + product_id: number, + requires_activation_codes: boolean, + slug: string, + type: 'Consumer' | 'Hobbyist' | 'Industrial', +} From 70ecb93b33b1a60124ca0f16767b404d23a22782 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 25 Jan 2017 18:53:43 +0200 Subject: [PATCH 286/504] fix flow --- flow-typed/npm/express_v4.x.x.js | 2 +- package.json | 209 +++++++++++---------- src/RouteConfig.js | 3 +- src/controllers/DevicesController.js | 4 +- src/lib/eventToApi.js | 2 +- src/main.js | 2 +- src/managers/FirmwareCompilationManager.js | 2 +- src/managers/WebhookManager.js | 7 +- src/types.js | 2 +- 9 files changed, 118 insertions(+), 115 deletions(-) diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js index 7f17c606..29b7cf1b 100644 --- a/flow-typed/npm/express_v4.x.x.js +++ b/flow-typed/npm/express_v4.x.x.js @@ -31,7 +31,7 @@ declare class express$Request extends http$IncomingMessage mixins express$Reques baseUrl: string; body: Object; cookies: {[cookie: string]: string}; - files: Array<$File>, + files: { [key: string]: Array<$File> } | Array<$File>, fresh: boolean; hostname: boolean; ip: string; diff --git a/package.json b/package.json index 376f257d..319c0364 100644 --- a/package.json +++ b/package.json @@ -1,104 +1,105 @@ -{ - "name": "spark-server", - "version": "0.1.1", - "license": "AGPL-3.0", - "repository": { - "type": "git", - "url": "https://github.com/spark/spark-server" - }, - "homepage": "https://github.com/spark/spark-server", - "bugs": "https://github.com/spark/spark-server/issues", - "author": { - "name": "David Middlecamp", - "email": "david@spark.io", - "url": "https://www.spark.io/" - }, - "contributors": [ - { - "name": "Kenneth Lim", - "email": "kennethlimcp@gmail.com", - "url": "https://github.com/kennethlimcp" - }, - { - "name": "Emily Rose", - "email": "emily@spark.io", - "url": "https://github.com/emilyrose" - } - ], - "scripts": { - "build": "babel ./src --out-dir ./build", - "build:clean": "rimraf ./build", - "lint": "eslint --fix -- .", - "prebuild": "npm run build:clean", - "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", - "start:prod": "npm run build && node ./build/main.js", - "test": "ava --serial", - "test:watch": "ava --watch --serial" - }, - "pre-commit": [ - "lint", - "test" - ], - "ava": { - "verbose": true, - "babel": "inherit", - "files": [ - "test/*.test.js", - "!test/__test_data__/*" - ], - "require": [ - "babel-register" - ] - }, - "dependencies": { - "array-flatten": "^2.1.1", - "basic-auth-parser": "0.0.2", - "binary-version-reader": "^0.5.0", - "body-parser": "^1.15.2", - "constitute": "^1.6.2", - "express": "^4.14.0", - "express-oauth-server": "^2.0.0-b1", - "hogan.js": "^3.0.2", - "lodash": "^4.17.4", - "moment": "*", - "moniker": "^0.1.2", - "morgan": "^1.7.0", - "multer": "^1.2.1", - "request": "*", - "rimraf": "^2.5.4", - "rmfr": "^1.0.1", - "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev", - "ursa": "^0.9.4", - "uuid": "^3.0.1" - }, - "devDependencies": { - "ava": "^0.17.0", - "babel-cli": "^6.18.0", - "babel-eslint": "^7.1.1", - "babel-plugin-transform-class-properties": "^6.19.0", - "babel-plugin-transform-decorators": "^6.13.0", - "babel-plugin-transform-decorators-legacy": "^1.3.4", - "babel-plugin-transform-es2015-destructuring": "^6.19.0", - "babel-plugin-transform-es2015-spread": "^6.8.0", - "babel-plugin-transform-flow-strip-types": "^6.18.0", - "babel-plugin-transform-runtime": "^6.15.0", - "babel-preset-es2015": "^6.18.0", - "babel-preset-latest": "^6.16.0", - "babel-preset-stage-0": "^6.16.0", - "babel-preset-stage-1": "^6.16.0", - "babel-register": "^6.18.0", - "eslint": "^3.11.0", - "eslint-config-airbnb-base": "^10.0.1", - "eslint-plugin-flowtype": "^2.28.2", - "eslint-plugin-import": "^2.2.0", - "eslint-plugin-sorting": "^0.3.0", - "flow-bin": "^0.37.0", - "nodemon": "^1.11.0", - "pre-commit": "^1.2.2", - "rimraf": "^2.5.4", - "sinon": "^1.17.7", - "supertest": "^2.0.1", - "supertest-as-promised": "^4.0.2", - "uglifyjs": "^2.4.10" - } -} +{ + "name": "spark-server", + "version": "0.1.1", + "license": "AGPL-3.0", + "repository": { + "type": "git", + "url": "https://github.com/spark/spark-server" + }, + "homepage": "https://github.com/spark/spark-server", + "bugs": "https://github.com/spark/spark-server/issues", + "author": { + "name": "David Middlecamp", + "email": "david@spark.io", + "url": "https://www.spark.io/" + }, + "contributors": [ + { + "name": "Kenneth Lim", + "email": "kennethlimcp@gmail.com", + "url": "https://github.com/kennethlimcp" + }, + { + "name": "Emily Rose", + "email": "emily@spark.io", + "url": "https://github.com/emilyrose" + } + ], + "scripts": { + "build": "babel ./src --out-dir ./build", + "build:clean": "rimraf ./build", + "lint": "eslint --fix -- .", + "prebuild": "npm run build:clean", + "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", + "start:prod": "npm run build && node ./build/main.js", + "test": "ava --serial", + "test:watch": "ava --watch --serial" + }, + "pre-commit": [ + "lint", + "test" + ], + "ava": { + "verbose": true, + "babel": "inherit", + "files": [ + "test/*.test.js", + "!test/__test_data__/*" + ], + "require": [ + "babel-register" + ] + }, + "dependencies": { + "array-flatten": "^2.1.1", + "basic-auth-parser": "0.0.2", + "binary-version-reader": "^0.5.0", + "body-parser": "^1.15.2", + "constitute": "^1.6.2", + "express": "^4.14.0", + "express-oauth-server": "^2.0.0-b1", + "hogan.js": "^3.0.2", + "lodash": "^4.17.4", + "moment": "*", + "moniker": "^0.1.2", + "morgan": "^1.7.0", + "multer": "^1.2.1", + "nullthrows": "^1.0.0", + "request": "*", + "rimraf": "^2.5.4", + "rmfr": "^1.0.1", + "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev", + "ursa": "^0.9.4", + "uuid": "^3.0.1" + }, + "devDependencies": { + "ava": "^0.17.0", + "babel-cli": "^6.18.0", + "babel-eslint": "^7.1.1", + "babel-plugin-transform-class-properties": "^6.19.0", + "babel-plugin-transform-decorators": "^6.13.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-es2015-destructuring": "^6.19.0", + "babel-plugin-transform-es2015-spread": "^6.8.0", + "babel-plugin-transform-flow-strip-types": "^6.18.0", + "babel-plugin-transform-runtime": "^6.15.0", + "babel-preset-es2015": "^6.18.0", + "babel-preset-latest": "^6.16.0", + "babel-preset-stage-0": "^6.16.0", + "babel-preset-stage-1": "^6.16.0", + "babel-register": "^6.18.0", + "eslint": "^3.11.0", + "eslint-config-airbnb-base": "^10.0.1", + "eslint-plugin-flowtype": "^2.28.2", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-sorting": "^0.3.0", + "flow-bin": "^0.37.0", + "nodemon": "^1.11.0", + "pre-commit": "^1.2.2", + "rimraf": "^2.5.4", + "sinon": "^1.17.7", + "supertest": "^2.0.1", + "supertest-as-promised": "^4.0.2", + "uglifyjs": "^2.4.10" + } +} diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 57f6b56d..584b009c 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -11,6 +11,7 @@ import type { Container } from 'constitute'; import type { Settings } from './types'; import OAuthServer from 'express-oauth-server'; +import nullthrows from 'nullthrows'; import multer from 'multer'; import OAuthModel from './OAuthModel'; import HttpError from './lib/HttpError'; @@ -69,7 +70,7 @@ export default ( const filesMiddleware = (allowedUploads: ?Array<{ maxCount: number, name: string, - }> = []): Middleware => allowedUploads.length + }> = []): Middleware => nullthrows(allowedUploads).length ? multer().fields(allowedUploads) : multer().any(); diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index bfc9e669..14930702 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -47,7 +47,7 @@ class DevicesController extends Controller { async compileSources(postBody: CompileConfig): Promise<*> { const response = await FirmwareCompilationManager.compileSource( nullthrows(postBody.platform_id || postBody.product_id), - this.request.files, + (this.request.files: any), ); if (!response) { @@ -150,7 +150,7 @@ class DevicesController extends Controller { const file = this.request.files && - this.request.files.file[0]; + (this.request.files: any).file[0]; if (file && file.originalname.endsWith('.bin')) { const flashStatus = await this._deviceRepository diff --git a/src/lib/eventToApi.js b/src/lib/eventToApi.js index a755c3f7..89eeabaf 100644 --- a/src/lib/eventToApi.js +++ b/src/lib/eventToApi.js @@ -4,7 +4,7 @@ import type { Event } from '../types'; export type EventAPIType = {| coreid: ?string, - data: ?Object, + data: ?string, published_at: Date, ttl: number, |}; diff --git a/src/main.js b/src/main.js index eefac822..bf60f1bf 100644 --- a/src/main.js +++ b/src/main.js @@ -45,7 +45,7 @@ app.listen( const addresses = arrayFlatten( Object.entries(os.networkInterfaces()).map( // eslint-disable-next-line no-unused-vars - ([name, nic]: Array): Array => + ([name, nic]: [string, mixed]): Array => (nic: any) .filter((address: Object): boolean => address.family === 'IPv4' && diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js index 81c33364..8455b258 100644 --- a/src/managers/FirmwareCompilationManager.js +++ b/src/managers/FirmwareCompilationManager.js @@ -128,7 +128,7 @@ class FirmwareCompilationManager { } }); - await new Promise((resolve: () => void): void => + await new Promise((resolve: () => void): events$EventEmitter => makeProcess.on('exit', (): void => resolve()), ); diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 5299739b..a237e849 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -12,6 +12,7 @@ import type { EventPublisher } from 'spark-protocol'; import hogan from 'hogan.js'; import HttpError from '../lib/HttpError'; import logger from '../lib/logger'; +import nullthrows from 'nullthrows'; import request from 'request'; import throttle from 'lodash/throttle'; @@ -191,7 +192,7 @@ class WebhookManager { method: webhook.requestType, qs: requestQuery, strictSSL: webhook.rejectUnauthorized, - url: requestUrl, + url: nullthrows(requestUrl), }; const responseBody = await this._callWebhook( @@ -281,7 +282,7 @@ class WebhookManager { return; } if (response.statusCode >= 400) { - onResponseError(response.statusMessage); + onResponseError((response: any).statusMessage); return; } @@ -322,7 +323,7 @@ class WebhookManager { _getRequestData = ( customData: ?Object, event: Event, - noDefaults: boolean, + noDefaults?: boolean, ): ?Object => { const defaultEventData = { coreid: event.deviceID, diff --git a/src/types.js b/src/types.js index aa445e80..c89ec3b0 100644 --- a/src/types.js +++ b/src/types.js @@ -74,7 +74,7 @@ export type Event = EventData & { }; export type EventData = { - data?: string, + data?: ?string, deviceID?: ?string, isPublic: boolean, name: string, From cd48ac0b4e2f927a8c8fbdd0144f72897581b106 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 25 Jan 2017 19:17:31 +0200 Subject: [PATCH 287/504] fix eventData.data type --- src/controllers/EventsController.js | 2 +- src/types.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index d300aaf9..c3b8b349 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -98,7 +98,7 @@ class EventsController extends Controller { @route('/v1/devices/events') async publish(postBody: { name: string, - data: ?string, + data?: string, private: boolean, ttl?: number, }): Promise<*> { diff --git a/src/types.js b/src/types.js index c89ec3b0..aa445e80 100644 --- a/src/types.js +++ b/src/types.js @@ -74,7 +74,7 @@ export type Event = EventData & { }; export type EventData = { - data?: ?string, + data?: string, deviceID?: ?string, isPublic: boolean, name: string, From f5dde1e55c5155b176dcab35467eb1a473bd16cc Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 26 Jan 2017 07:09:07 -0800 Subject: [PATCH 288/504] Updating firmware manager so that if the user does not have the spark-firmware repo installed, the site will still run. Used mkdirp for creating recursive folders if they don't already exist. --- src/managers/FirmwareCompilationManager.js | 80 ++++++++++++---------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js index 81c33364..b7845740 100644 --- a/src/managers/FirmwareCompilationManager.js +++ b/src/managers/FirmwareCompilationManager.js @@ -5,11 +5,15 @@ import type { File } from 'express'; import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; +import mkdirp from 'mkdirp'; import rmfr from 'rmfr'; import { spawn } from 'child_process'; import { knownPlatforms } from 'spark-protocol'; import settings from '../settings'; +const IS_COMPILATION_ENABLED = + fs.existsSync(settings.FIRMWARE_REPOSITORY_DIRECTORY); + const USER_APP_PATH = path.join( settings.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications', @@ -50,6 +54,10 @@ class FirmwareCompilationManager { fs.existsSync(settings.FIRMWARE_REPOSITORY_DIRECTORY); static getBinaryForID = (id: string): ?Buffer => { + if (!FirmwareCompilationManager.firmwareDirectorExists()) { + return null; + } + const binaryPath = path.join(BIN_PATH, id); if (!fs.existsSync(binaryPath)) { return null; @@ -82,7 +90,7 @@ class FirmwareCompilationManager { const appFolder = `${platformName}_firmware_${(new Date()).getTime()}`.toLowerCase(); const appPath = path.join(USER_APP_PATH, appFolder); - fs.mkdirSync(appPath); + mkdirp.sync(appPath); files.forEach((file: File) => { const fileName = file.originalname; @@ -128,9 +136,9 @@ class FirmwareCompilationManager { } }); - await new Promise((resolve: () => void): void => - makeProcess.on('exit', (): void => resolve()), - ); + await new Promise((resolve: () => void): void => { + makeProcess.on('exit', (): void => resolve()); + }); const date = new Date(); date.setDate(date.getDate() + 1); @@ -171,38 +179,40 @@ class FirmwareCompilationManager { } } -// Delete all expired binaries or queue them up to eventually be deleted. -if (!fs.existsSync(settings.BUILD_DIRECTORY)) { - fs.mkdirSync(settings.BUILD_DIRECTORY); -} -if (!fs.existsSync(BIN_PATH)) { - fs.mkdirSync(BIN_PATH); -} - -fs.readdirSync(USER_APP_PATH).forEach((file: string) => { - const appFolder = path.join(USER_APP_PATH, file); - const configPath = path.join(appFolder, 'config.json'); - if (!fs.existsSync(configPath)) { - return; - } - - const configString = fs.readFileSync(configPath, 'utf8'); - if (!configString) { - return; +if (IS_COMPILATION_ENABLED) { + // Delete all expired binaries or queue them up to eventually be deleted. + if (!fs.existsSync(settings.BUILD_DIRECTORY)) { + mkdirp.sync(settings.BUILD_DIRECTORY); } - const config = JSON.parse(configString); - if (config.expires_at < new Date()) { - // TODO - clean up artifacts in the firmware folder. Every binary will have - // files in firmare/build/target/user & firmware/build/target/user-part - // It might make the most sense to just create a custom MAKE file to do this - rmfr(configPath); - rmfr(path.join(BIN_PATH, config.binary_id)); - } else { - FirmwareCompilationManager.addFirmwareCleanupTask( - appFolder, - config, - ); + if (!fs.existsSync(BIN_PATH)) { + mkdirp.sync(BIN_PATH); } -}); + + fs.readdirSync(USER_APP_PATH).forEach((file: string) => { + const appFolder = path.join(USER_APP_PATH, file); + const configPath = path.join(appFolder, 'config.json'); + if (!fs.existsSync(configPath)) { + return; + } + + const configString = fs.readFileSync(configPath, 'utf8'); + if (!configString) { + return; + } + const config = JSON.parse(configString); + if (config.expires_at < new Date()) { + // TODO - clean up artifacts in the firmware folder. Every binary will have + // files in firmare/build/target/user & firmware/build/target/user-part + // It might make the most sense to just create a custom MAKE file to do this + rmfr(configPath); + rmfr(path.join(BIN_PATH, config.binary_id)); + } else { + FirmwareCompilationManager.addFirmwareCleanupTask( + appFolder, + config, + ); + } + }); +} export default FirmwareCompilationManager; From eadb9c9e699049bd4e32c930d4af8dbe1645d1c3 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Thu, 26 Jan 2017 17:31:58 +0200 Subject: [PATCH 289/504] fix nits --- src/managers/FirmwareCompilationManager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js index b7845740..4196f207 100644 --- a/src/managers/FirmwareCompilationManager.js +++ b/src/managers/FirmwareCompilationManager.js @@ -50,11 +50,11 @@ const getUniqueKey = (): string => { }; class FirmwareCompilationManager { - static firmwareDirectorExists = (): boolean => + static firmwareDirectoryExists = (): boolean => fs.existsSync(settings.FIRMWARE_REPOSITORY_DIRECTORY); static getBinaryForID = (id: string): ?Buffer => { - if (!FirmwareCompilationManager.firmwareDirectorExists()) { + if (!FirmwareCompilationManager.firmwareDirectoryExists()) { return null; } @@ -77,7 +77,7 @@ class FirmwareCompilationManager { platformID: string, files: Array, ): Promise => { - if (!FirmwareCompilationManager.firmwareDirectorExists()) { + if (!FirmwareCompilationManager.firmwareDirectoryExists()) { return null; } @@ -136,7 +136,7 @@ class FirmwareCompilationManager { } }); - await new Promise((resolve: () => void): void => { + await new Promise((resolve: () => void) => { makeProcess.on('exit', (): void => resolve()); }); From bc7f0f0aa05f2b0c724f151112bb6deb98d71178 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 27 Jan 2017 14:58:27 +0200 Subject: [PATCH 290/504] remove supertest-as-promised --- package.json | 1 - test/DevicesController.test.js | 2 +- test/ProvisioningController.test.js | 2 +- test/UsersController.test.js | 2 +- test/WebhooksController.test.js | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 319c0364..b269f38e 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,6 @@ "rimraf": "^2.5.4", "sinon": "^1.17.7", "supertest": "^2.0.1", - "supertest-as-promised": "^4.0.2", "uglifyjs": "^2.4.10" } } diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index b5d4fddd..9187a023 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -1,6 +1,6 @@ /* eslint-disable */ import test from 'ava'; -import request from 'supertest-as-promised'; +import request from 'supertest'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js index 4a9ff042..54122ad6 100644 --- a/test/ProvisioningController.test.js +++ b/test/ProvisioningController.test.js @@ -1,6 +1,6 @@ /* eslint-disable */ import test from 'ava'; -import request from 'supertest-as-promised'; +import request from 'supertest'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; diff --git a/test/UsersController.test.js b/test/UsersController.test.js index 2ab2af1f..a2a84372 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -2,7 +2,7 @@ import type { TokenObject, UserCredentials } from '../src/types'; import test from 'ava'; -import request from 'supertest-as-promised'; +import request from 'supertest'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index 445fd053..be675cc8 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -2,7 +2,7 @@ import type { Webhook, WebhookMutator } from '../src/types'; import test from 'ava'; -import request from 'supertest-as-promised'; +import request from 'supertest'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import settings from './setup/settings'; From f534be4ce8f2af7a6b333fe8cdf471039895bcc8 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 27 Jan 2017 21:29:00 +0200 Subject: [PATCH 291/504] add DeviceClaimsController test --- test/DeviceClaimsController.test.js | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 test/DeviceClaimsController.test.js diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js new file mode 100644 index 00000000..68229a75 --- /dev/null +++ b/test/DeviceClaimsController.test.js @@ -0,0 +1,76 @@ +/* eslint-disable */ +import test from 'ava'; +import request from 'supertest'; +import ouathClients from '../src/oauthClients.json'; +import app from './setup/testApp'; +import TestData from './setup/TestData'; + +const container = app.container; +let DEVICE_ID = null; +let testUser; +let userToken; +let deviceToApiAttributes; + +test.before(async () => { + const USER_CREDENTIALS = TestData.getUser(); + DEVICE_ID = TestData.getID(); + + const userResponse = await request(app) + .post('/v1/users') + .send(USER_CREDENTIALS); + + testUser = await container.constitute('UserRepository') + .getByUsername(USER_CREDENTIALS.username); + + const tokenResponse = await request(app) + .post('/oauth/token') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + client_id: ouathClients[0].clientId, + client_secret: ouathClients[0].clientSecret, + grant_type: 'password', + password: USER_CREDENTIALS.password, + username: USER_CREDENTIALS.username, + }); + + userToken = tokenResponse.body.access_token; + + if (!userToken) { + throw new Error('test user creation fails'); + } + + const provisionResponse = await request(app) + .post(`/v1/provisioning/${DEVICE_ID}`) + .query({ access_token: userToken }) + .send({ publicKey: TestData.getPublicKey() }); + + deviceToApiAttributes = provisionResponse.body; + + if (!deviceToApiAttributes.id) { + throw new Error('test device creation fails'); + } +}); + + +test( + 'should return claimCode, and user\'s devices ids', + async t => { + const response = await request(app) + .post(`/v1/device_claims`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ access_token: userToken }); + + t.is(response.status, 200); + t.truthy(response.body.claim_code); + t.truthy( + response.body.device_ids && + response.body.device_ids[0] === DEVICE_ID + ); + }, +); + +test.after.always(async (): Promise => { + await container.constitute('UserRepository').deleteById(testUser.id); + await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID); + await container.constitute('DeviceKeyRepository').delete(DEVICE_ID); +}); From 68bd7343fe8462703579b7959bc4171daafd9bd3 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 27 Jan 2017 21:58:47 +0200 Subject: [PATCH 292/504] UserController: don't return userObject for create endpoint(to math with official cloud), fix tests --- src/controllers/UsersController.js | 5 +++-- test/DevicesController.test.js | 24 ++++++------------------ test/ProvisioningController.test.js | 25 +++++++------------------ test/UsersController.test.js | 15 ++++++--------- test/WebhooksController.test.js | 12 ++++-------- test/setup/TestData.js | 4 +++- 6 files changed, 29 insertions(+), 56 deletions(-) diff --git a/src/controllers/UsersController.js b/src/controllers/UsersController.js index 7500fc7e..cf165135 100644 --- a/src/controllers/UsersController.js +++ b/src/controllers/UsersController.js @@ -32,10 +32,11 @@ class UsersController extends Controller { throw new HttpError('user with the username already exists'); } - const newUser = await this._userRepository.createWithCredentials( + await this._userRepository.createWithCredentials( userCredentials, ); - return this.ok(newUser); + + return this.ok({ ok: true }); } catch (error) { return this.bad(error.message); } diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 9187a023..ae940006 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -5,33 +5,23 @@ import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; -let USER_CREDENTIALS = { - password: 'password', - username: 'deviceTestUser@test.com', -}; -let DEVICE_ID = null; -let TEST_PUBLIC_KEY = - '-----BEGIN PUBLIC KEY-----\n' + - 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsxJFqlUOxK5bsEfTtBCe9sXBa' + - '43q9QoSPFXEG5qY/+udOpf2SKacgfUVdUbK4WOkLou7FQ+DffpwztBk5fWM9qfzF' + - 'EQRVMS8xwS4JqqD7slXwuPWFpS9SGy9kLNy/pl1dtGm556wVX431Dg7UBKiXuNGR' + - '7E8d2hfgeyiTtsWfUQIDAQAB\n' + - '-----END PUBLIC KEY-----\n'; +const container = app.container; +let DEVICE_ID = null; let testUser; let userToken; let deviceToApiAttributes; test.before(async () => { - USER_CREDENTIALS = TestData.getUser(); + const USER_CREDENTIALS = TestData.getUser(); DEVICE_ID = TestData.getID(); - TEST_PUBLIC_KEY = TestData.getPublicKey(); const userResponse = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); - testUser = userResponse.body; + testUser = await container.constitute('UserRepository') + .getByUsername(USER_CREDENTIALS.username); const tokenResponse = await request(app) .post('/oauth/token') @@ -53,7 +43,7 @@ test.before(async () => { const provisionResponse = await request(app) .post(`/v1/provisioning/${DEVICE_ID}`) .query({ access_token: userToken }) - .send({ publicKey: TEST_PUBLIC_KEY }); + .send({ publicKey: TestData.getPublicKey() }); deviceToApiAttributes = provisionResponse.body; @@ -137,8 +127,6 @@ test.serial('should claim device', async t => { // TODO write test for checking the error if device belongs to somebody else // TODO write tests for updateDevice & callFunction -// Used to get implementations -const container = app.container; test.after.always(async (): Promise => { await container.constitute('UserRepository').deleteById(testUser.id); await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID); diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js index 54122ad6..c990aa15 100644 --- a/test/ProvisioningController.test.js +++ b/test/ProvisioningController.test.js @@ -5,32 +5,23 @@ import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; -let USER_CREDENTIALS = { - password: 'password', - username: 'provisionTestUser@test.com', -}; - -let DEVICE_ID = '350023001951353337343733'; -let TEST_PUBLIC_KEY = - '-----BEGIN PUBLIC KEY-----\n' + - 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsxJFqlUOxK5bsEfTtBCe9sXBa' + - '43q9QoSPFXEG5qY/+udOpf2SKacgfUVdUbK4WOkLou7FQ+DffpwztBk5fWM9qfzF' + - 'EQRVMS8xwS4JqqD7slXwuPWFpS9SGy9kLNy/pl1dtGm556wVX431Dg7UBKiXuNGR' + - '7E8d2hfgeyiTtsWfUQIDAQAB\n' + - '-----END PUBLIC KEY-----\n'; - +const container = app.container; +let DEVICE_ID; +let TEST_PUBLIC_KEY; let testUser; let userToken; test.before(async () => { - USER_CREDENTIALS = TestData.getUser(); + const USER_CREDENTIALS = TestData.getUser(); DEVICE_ID = TestData.getID(); TEST_PUBLIC_KEY = TestData.getPublicKey(); + const userResponse = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); - testUser = userResponse.body; + testUser = await container.constitute('UserRepository') + .getByUsername(USER_CREDENTIALS.username); const tokenResponse = await request(app) .post('/oauth/token') @@ -79,8 +70,6 @@ test('should throw an error if public key is not provided', async t => { t.is(response.body.error, 'No key provided'); }); -// Used to get implementations -const container = app.container; test.after.always(async (): Promise => { await container.constitute('UserRepository').deleteById(testUser.id); await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID); diff --git a/test/UsersController.test.js b/test/UsersController.test.js index a2a84372..af2a1540 100644 --- a/test/UsersController.test.js +++ b/test/UsersController.test.js @@ -7,21 +7,20 @@ import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; -let USER_CREDENTIALS: UserCredentials = { - password: 'password', - username: 'newUser@test.com', -}; - +const container = app.container; +let USER_CREDENTIALS; let user; let userToken; -test.serial('should return a new user object', async t => { +test.serial('should create new user', async t => { USER_CREDENTIALS = TestData.getUser(); + const response = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); - user = response.body; + user = await container.constitute('UserRepository') + .getByUsername(USER_CREDENTIALS.username); t.is(response.status, 200); t.truthy(user.username === USER_CREDENTIALS.username); @@ -83,8 +82,6 @@ test.serial('should delete the access token for the user', async t => { )); }); -// Used to get implementations -const container = app.container; test.after.always(async (): Promise => { await container.constitute('UserRepository').deleteById(user.id); }); diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index be675cc8..faa5440c 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -8,11 +8,7 @@ import app from './setup/testApp'; import settings from './setup/settings'; import TestData from './setup/TestData'; -let USER_CREDENTIALS = { - password: 'password', - username: 'webhookTestUser@test.com', -}; - +const container = app.container; const WEBHOOK_MODEL: WebhookMutator = { event: 'testEvent', requestType: 'GET', @@ -24,12 +20,13 @@ let userToken; let testWebhook; test.before(async () => { - USER_CREDENTIALS = TestData.getUser(); + const USER_CREDENTIALS = TestData.getUser(); const userResponse = await request(app) .post('/v1/users') .send(USER_CREDENTIALS); - testUser = userResponse.body; + testUser = await container.constitute('UserRepository') + .getByUsername(USER_CREDENTIALS.username); const tokenResponse = await request(app) .post('/oauth/token') @@ -160,7 +157,6 @@ test.serial('should delete webhook', async t => { )); }); -const container = app.container; test.after.always(async (): Promise => { await container.constitute('WebhookRepository').deleteById(testWebhook.id); await container.constitute('UserRepository').deleteById(testUser.id); diff --git a/test/setup/TestData.js b/test/setup/TestData.js index fdc53e96..75bef489 100644 --- a/test/setup/TestData.js +++ b/test/setup/TestData.js @@ -1,5 +1,7 @@ // @flow +import type { UserCredentials } from '../../src/types'; + import uuid from 'uuid'; import ursa from 'ursa'; @@ -7,7 +9,7 @@ const uuidSet = new Set(); const privateKeys = new Set(); class TestData { - static getUser = (): { password: string, username: string } => ({ + static getUser = (): UserCredentials => ({ password: 'password', username: `testUser+${TestData.getID()}@test.com`, }); From c0fbb0cbbffc2ea56058e9046e51e89176547e34 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Fri, 27 Jan 2017 22:23:01 +0200 Subject: [PATCH 293/504] add device belongs to somebody else error test --- test/DevicesController.test.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index ae940006..9a7bcbbc 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -1,6 +1,7 @@ /* eslint-disable */ import test from 'ava'; import request from 'supertest'; +import sinon from 'sinon'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; @@ -124,7 +125,30 @@ test.serial('should claim device', async t => { t.is(getDeviceResponse.status, 200); }); -// TODO write test for checking the error if device belongs to somebody else + +test.serial( + 'should throw an error if device belongs to somebody else', + async t => { + const deviceAttributesStub = sinon.stub( + container.constitute('DeviceAttributeRepository'), + 'getById', + ).returns({ ownerID: TestData.getID()}); + + const claimDeviceResponse = await request(app) + .post('/v1/devices') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + id: DEVICE_ID, + }); + + deviceAttributesStub.restore(); + + t.is(claimDeviceResponse.status, 400); + t.is(claimDeviceResponse.body.error, 'The device belongs to someone else.'); + }, +); + // TODO write tests for updateDevice & callFunction test.after.always(async (): Promise => { From a3a8fc7009f483eb35365f2cf223da00b39b17ff Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 01:06:33 +0200 Subject: [PATCH 294/504] add function call endpoint tests --- test/DevicesController.test.js | 82 +++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 9a7bcbbc..f2b4d9c9 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -149,7 +149,87 @@ test.serial( }, ); -// TODO write tests for updateDevice & callFunction +test.serial( + 'should return function call result and device attributes', + async t => { + const device = { + callFunction: (functionName, functionArguments) => + functionArguments.arg === 'on' ? 1 : -1, + ping: () => ({ + connected: true, + lastPing: new Date(), + }), + }; + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const callFunctionResponse1 = await request(app) + .post(`/v1/devices/${DEVICE_ID}/testFunction`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + arg: 'on', + }); + + const callFunctionResponse2 = await request(app) + .post(`/v1/devices/${DEVICE_ID}/testFunction`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + arg: 'off', + }); + + deviceServerStub.restore(); + + t.is(callFunctionResponse1.status, 200); + t.is(callFunctionResponse1.body.return_value, 1); + t.is(callFunctionResponse1.body.connected, true); + t.is(callFunctionResponse1.body.id, DEVICE_ID); + + t.is(callFunctionResponse2.body.return_value, -1); + }, +); + +test.serial( + 'should throw an error if function doesn\'t exist', + async t => { + const device = { + callFunction: (functionName, functionArguments) => { + if(functionName !== 'testFunction') { + throw new Error(`Unknown Function ${functionName}`) + } + return 1; + }, + ping: () => ({ + connected: true, + lastPing: new Date(), + }), + }; + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const callFunctionResponse = await request(app) + .post(`/v1/devices/${DEVICE_ID}/wrongTestFunction`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + arg: 'on', + }); + + deviceServerStub.restore(); + + t.is(callFunctionResponse.status, 404); + t.is(callFunctionResponse.body.error, 'Function not found'); + }, +); + +// TODO write tests for updateDevice test.after.always(async (): Promise => { await container.constitute('UserRepository').deleteById(testUser.id); From 4a119ff717896b5c7ebe837bbcffa475d0f1b228 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 01:36:06 +0200 Subject: [PATCH 295/504] add getVariable endpoint tests --- test/DevicesController.test.js | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index f2b4d9c9..cf54c0af 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -229,6 +229,59 @@ test.serial( }, ); +test.serial( + 'should return variable value', + async t => { + const device = { + getVariableValue: () => 'resultValue', + }; + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const getVariableResponse = await request(app) + .get(`/v1/devices/${DEVICE_ID}/varName/`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .query({ access_token: userToken }); + + deviceServerStub.restore(); + + t.is(getVariableResponse.status, 200); + t.is(getVariableResponse.body.result, 'resultValue'); + }, +); + +test.serial( + 'should throw an error if variable not found', + async t => { + const device = { + getVariableValue: (variableName) => { + if(variableName !== 'testVariable') { + throw new Error(`Variable not found`) + } + return 1; + }, + }; + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const getVariableResponse = await request(app) + .get(`/v1/devices/${DEVICE_ID}/varName/`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .query({ access_token: userToken }); + + deviceServerStub.restore(); + + t.is(getVariableResponse.status, 404); + t.is(getVariableResponse.body.error, 'Variable not found'); + }, +); + // TODO write tests for updateDevice test.after.always(async (): Promise => { From 386a0bd6b625d6b537147fbba39cd2afea3489ae Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 01:47:10 +0200 Subject: [PATCH 296/504] fix nits in callFunction and getVariable tests --- test/DevicesController.test.js | 47 ++++++++++++++++------------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index cf54c0af..68ee63d5 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -152,9 +152,11 @@ test.serial( test.serial( 'should return function call result and device attributes', async t => { + const testFunctionName = 'testFunction'; + const testArgument = 'testArgument'; const device = { callFunction: (functionName, functionArguments) => - functionArguments.arg === 'on' ? 1 : -1, + functionName === testFunctionName && functionArguments.argument, ping: () => ({ connected: true, lastPing: new Date(), @@ -166,39 +168,31 @@ test.serial( 'getDevice', ).returns(device); - const callFunctionResponse1 = await request(app) - .post(`/v1/devices/${DEVICE_ID}/testFunction`) + const callFunctionResponse = await request(app) + .post(`/v1/devices/${DEVICE_ID}/${testFunctionName}`) .set('Content-Type', 'application/x-www-form-urlencoded') .send({ access_token: userToken, - arg: 'on', + argument: testArgument, }); - const callFunctionResponse2 = await request(app) - .post(`/v1/devices/${DEVICE_ID}/testFunction`) - .set('Content-Type', 'application/x-www-form-urlencoded') - .send({ - access_token: userToken, - arg: 'off', - }); deviceServerStub.restore(); - t.is(callFunctionResponse1.status, 200); - t.is(callFunctionResponse1.body.return_value, 1); - t.is(callFunctionResponse1.body.connected, true); - t.is(callFunctionResponse1.body.id, DEVICE_ID); - - t.is(callFunctionResponse2.body.return_value, -1); + t.is(callFunctionResponse.status, 200); + t.is(callFunctionResponse.body.return_value, testArgument); + t.is(callFunctionResponse.body.connected, true); + t.is(callFunctionResponse.body.id, DEVICE_ID); }, ); test.serial( 'should throw an error if function doesn\'t exist', async t => { + const testFunctionName = 'testFunction'; const device = { callFunction: (functionName, functionArguments) => { - if(functionName !== 'testFunction') { + if(functionName !== testFunctionName) { throw new Error(`Unknown Function ${functionName}`) } return 1; @@ -215,11 +209,10 @@ test.serial( ).returns(device); const callFunctionResponse = await request(app) - .post(`/v1/devices/${DEVICE_ID}/wrongTestFunction`) + .post(`/v1/devices/${DEVICE_ID}/wrong${testFunctionName}`) .set('Content-Type', 'application/x-www-form-urlencoded') .send({ access_token: userToken, - arg: 'on', }); deviceServerStub.restore(); @@ -232,8 +225,11 @@ test.serial( test.serial( 'should return variable value', async t => { + const testVariableName = 'testVariable'; + const testVariableResult = 'resultValue'; const device = { - getVariableValue: () => 'resultValue', + getVariableValue: (variableName) => + variableName === testVariableName && testVariableResult, }; const deviceServerStub = sinon.stub( @@ -242,23 +238,24 @@ test.serial( ).returns(device); const getVariableResponse = await request(app) - .get(`/v1/devices/${DEVICE_ID}/varName/`) + .get(`/v1/devices/${DEVICE_ID}/${testVariableName}/`) .set('Content-Type', 'application/x-www-form-urlencoded') .query({ access_token: userToken }); deviceServerStub.restore(); t.is(getVariableResponse.status, 200); - t.is(getVariableResponse.body.result, 'resultValue'); + t.is(getVariableResponse.body.result, testVariableResult); }, ); test.serial( 'should throw an error if variable not found', async t => { + const testVariableName = 'testVariable'; const device = { getVariableValue: (variableName) => { - if(variableName !== 'testVariable') { + if(variableName !== testVariableName) { throw new Error(`Variable not found`) } return 1; @@ -271,7 +268,7 @@ test.serial( ).returns(device); const getVariableResponse = await request(app) - .get(`/v1/devices/${DEVICE_ID}/varName/`) + .get(`/v1/devices/${DEVICE_ID}/wrong${testVariableName}/`) .set('Content-Type', 'application/x-www-form-urlencoded') .query({ access_token: userToken }); From 7d2ccded1355adbcd8ac75c2adcd3d94511835e9 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 02:04:35 +0200 Subject: [PATCH 297/504] add rename device test --- test/DevicesController.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 68ee63d5..8fe8f4b4 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -279,6 +279,24 @@ test.serial( }, ); +test.serial( + 'should rename device', + async t => { + const newDeviceName = 'newDeviceName'; + + const renameDeviceResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + name: newDeviceName, + }); + + t.is(renameDeviceResponse.status, 200); + t.is(renameDeviceResponse.body.name, newDeviceName); + }, +); + // TODO write tests for updateDevice test.after.always(async (): Promise => { From 96aea079c57630a5fd4c294ddf81f0d9f6dfef7e Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 02:13:19 +0200 Subject: [PATCH 298/504] add raise your hand test --- src/controllers/DevicesController.js | 2 +- test/DevicesController.test.js | 29 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 14930702..d8527b00 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -124,7 +124,7 @@ class DevicesController extends Controller { app_id?: string, name?: string, file_type?: 'binary', - signal: boolean, + signal?: boolean, }, ): Promise<*> { // 1 rename device diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 8fe8f4b4..e147f7c9 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -297,6 +297,35 @@ test.serial( }, ); +test.serial( + 'should call raise your hand function on device', + async t => { + const raiseYourHandSpy = sinon.spy(); + const device = { + raiseYourHand: raiseYourHandSpy, + }; + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const raiseYourHandResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + signal: true, + }); + + deviceServerStub.restore(); + + t.is(raiseYourHandResponse.status, 200); + t.truthy(raiseYourHandSpy.called); + t.is(raiseYourHandResponse.body.id, DEVICE_ID); + }, +); + // TODO write tests for updateDevice test.after.always(async (): Promise => { From 0256b6a229908dd5da10e79ee859d24853ebc702 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 02:55:50 +0200 Subject: [PATCH 299/504] add tests for flashing known apps endpoint --- src/repository/DeviceRepository.js | 2 +- test/DevicesController.test.js | 67 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/repository/DeviceRepository.js b/src/repository/DeviceRepository.js index 7f70a3ff..c2a88e9b 100644 --- a/src/repository/DeviceRepository.js +++ b/src/repository/DeviceRepository.js @@ -229,7 +229,7 @@ class DeviceRepository { const knownFirmware = this._deviceFirmwareRepository.getByName(appName); if (!knownFirmware) { - throw new HttpError(`No firmware ${appName} found`); + throw new HttpError(`No firmware ${appName} found`, 404); } const device = this._deviceServer.getDevice(deviceID); diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index e147f7c9..a54dda38 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -326,6 +326,73 @@ test.serial( }, ); +test.serial( + 'should start device flashing process with known application', + async t => { + const knownAppName = 'knownAppName'; + const knownAppBuffer = new Buffer(knownAppName); + const flashStatus = 'update finished'; + const device = { + flash: () => flashStatus, + }; + const flashSpy = sinon.spy(device, 'flash'); + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + + const deviceFirmwareStub = sinon.stub( + container.constitute('DeviceFirmwareRepository'), + 'getByName', + ).returns(knownAppBuffer); + + const flashKnownAppResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + app_id: knownAppName, + }); + + deviceServerStub.restore(); + deviceFirmwareStub.restore(); + + t.is(flashKnownAppResponse.status, 200); + t.truthy(flashSpy.calledWith(knownAppBuffer)); + t.is(flashKnownAppResponse.body.status, flashStatus); + t.is(flashKnownAppResponse.body.id, DEVICE_ID); + }, +); + +test.serial( + 'should throws an error if known application not found', + async t => { + const knownAppName = 'knownAppName'; + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns({}); + + const flashKnownAppResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + app_id: knownAppName, + }); + + deviceServerStub.restore(); + + t.is(flashKnownAppResponse.status, 404); + t.is( + flashKnownAppResponse.body.error, + `No firmware ${knownAppName} found`, + ); + }, +); + // TODO write tests for updateDevice test.after.always(async (): Promise => { From 297275b5b7be888e648dba09be0fac73eb266da5 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 03:18:18 +0200 Subject: [PATCH 300/504] add todos --- test/DevicesController.test.js | 2 +- test/EventsController.test.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test/EventsController.test.js diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index a54dda38..e0ee2a0f 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -393,7 +393,7 @@ test.serial( }, ); -// TODO write tests for updateDevice +// TODO write tests custom firmware flash tests test.after.always(async (): Promise => { await container.constitute('UserRepository').deleteById(testUser.id); diff --git a/test/EventsController.test.js b/test/EventsController.test.js new file mode 100644 index 00000000..3d930c59 --- /dev/null +++ b/test/EventsController.test.js @@ -0,0 +1,5 @@ +/* eslint-disable */ +// TODO write EventsController tests +import test from 'ava'; + +test('eventsController test', t => {}); \ No newline at end of file From 07a8b6a2cf87d557bdceed4a532fac83f2c477ce Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 28 Jan 2017 23:35:42 +0200 Subject: [PATCH 301/504] add update custom firmware tests, fix raise your hand signal type and error --- src/controllers/DevicesController.js | 19 ++++-- test/DevicesController.test.js | 86 ++++++++++++++++++++++++++-- test/setup/TestData.js | 49 ++++++++++++++++ test/setup/settings.js | 1 + 4 files changed, 147 insertions(+), 8 deletions(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index d8527b00..8515c75b 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -124,7 +124,7 @@ class DevicesController extends Controller { app_id?: string, name?: string, file_type?: 'binary', - signal?: boolean, + signal?: '1' | '0', }, ): Promise<*> { // 1 rename device @@ -134,10 +134,10 @@ class DevicesController extends Controller { this.user.id, postBody.name, ); - return this.ok({ name: updatedAttributes.name, ok: true }); } - // 2 flash device with known app + + // 2 flash device with known application if (postBody.app_id) { const flashStatus = await this._deviceRepository.flashKnownApp( deviceID, @@ -148,6 +148,11 @@ class DevicesController extends Controller { return this.ok({ id: deviceID, status: flashStatus }); } + // 3 flash device with custom application + if (this.request.files && !(this.request.files: any).file) { + throw new Error('Firmware file not provided'); + } + const file = this.request.files && (this.request.files: any).file[0]; @@ -159,14 +164,20 @@ class DevicesController extends Controller { return this.ok({ id: deviceID, status: flashStatus }); } + // 4 // If signal exists then we want to toggle nyan mode. This just makes the // LED change colors. - if (!Number.isNaN(postBody.signal)) { + if (postBody.signal) { + if (postBody.signal !== ('1' || '0')) { + throw new HttpError('Wrong signal value'); + } + await this._deviceRepository.raiseYourHand( deviceID, this.user.id, !!parseInt(postBody.signal, 10), ); + return this.ok({ id: deviceID, ok: true }); } diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index e0ee2a0f..5a4fc494 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -2,13 +2,15 @@ import test from 'ava'; import request from 'supertest'; import sinon from 'sinon'; +import path from 'path'; import ouathClients from '../src/oauthClients.json'; import app from './setup/testApp'; import TestData from './setup/TestData'; - const container = app.container; -let DEVICE_ID = null; +let customFirmwareFilePath; +let customFirmwareBuffer; +let DEVICE_ID; let testUser; let userToken; let deviceToApiAttributes; @@ -16,6 +18,9 @@ let deviceToApiAttributes; test.before(async () => { const USER_CREDENTIALS = TestData.getUser(); DEVICE_ID = TestData.getID(); + const { filePath, fileBuffer } = await TestData.createCustomFirmwareBinary(); + customFirmwareFilePath = filePath; + customFirmwareBuffer = fileBuffer; const userResponse = await request(app) .post('/v1/users') @@ -315,7 +320,7 @@ test.serial( .set('Content-Type', 'application/x-www-form-urlencoded') .send({ access_token: userToken, - signal: true, + signal: '1', }); deviceServerStub.restore(); @@ -326,6 +331,22 @@ test.serial( }, ); +test.serial( + 'should throw an error if signal is wrong value', + async t => { + const raiseYourHandResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + signal: 'some wrong value', + }); + + t.is(raiseYourHandResponse.status, 400); + t.truthy(raiseYourHandResponse.body.error, 'Wrong signal value'); + }, +); + test.serial( 'should start device flashing process with known application', async t => { @@ -393,9 +414,66 @@ test.serial( }, ); -// TODO write tests custom firmware flash tests +test.serial( + 'should start device flashing process with custom application', + async t => { + const flashStatus = 'update finished'; + const device = { + flash: () => flashStatus, + }; + const flashSpy = sinon.spy(device, 'flash'); + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const flashCustomFirmwareResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .attach('file', customFirmwareFilePath) + .query({ access_token: userToken }); + + deviceServerStub.restore(); + + t.is(flashCustomFirmwareResponse.status, 200); + t.truthy(flashSpy.calledWith(customFirmwareBuffer)); + t.is(flashCustomFirmwareResponse.body.status, flashStatus); + t.is(flashCustomFirmwareResponse.body.id, DEVICE_ID); + }, +); + +test.serial( + 'should throw an error if custom firmware file not provided', + async t => { + const flashCustomFirmwareResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .field('file_type', 'binary') + .query({ access_token: userToken }); + + t.is(flashCustomFirmwareResponse.status, 400); + t.is(flashCustomFirmwareResponse.body.error, 'Firmware file not provided'); + }, +); + +test.serial( + 'should throw an error if custom firmware file type not binary', + async t => { + const flashCustomFirmwareResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + // send random not binary file + .attach('file', path.join(__dirname, 'DevicesController.test.js')) + .query({ access_token: userToken }); + + t.is(flashCustomFirmwareResponse.status, 400); + t.is(flashCustomFirmwareResponse.body.error, 'Did not update device'); + }, +); test.after.always(async (): Promise => { + await TestData.deleteCustomFirmwareBinary(customFirmwareFilePath); await container.constitute('UserRepository').deleteById(testUser.id); await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID); await container.constitute('DeviceKeyRepository').delete(DEVICE_ID); diff --git a/test/setup/TestData.js b/test/setup/TestData.js index 75bef489..285932d8 100644 --- a/test/setup/TestData.js +++ b/test/setup/TestData.js @@ -2,13 +2,62 @@ import type { UserCredentials } from '../../src/types'; +import crypto from 'crypto'; import uuid from 'uuid'; import ursa from 'ursa'; +import fs from 'fs'; +import settings from './settings'; + const uuidSet = new Set(); const privateKeys = new Set(); +type CreateCustomFirmwareResult = { + filePath: string, + fileBuffer: Buffer, +}; + class TestData { + static createCustomFirmwareBinary = (): Promise => + new Promise(( + resolve: (result: CreateCustomFirmwareResult) => void, + reject: (error: Error) => void, + ) => { + const filePath = + `${settings.CUSTOM_FIRMWARE_DIRECTORY}/customApp-${TestData.getID()}.bin`; + const fileBuffer = crypto.randomBytes(100); + + fs.writeFile( + filePath, + fileBuffer, + (error: ?Error) => { + if (error) { + reject(error); + return; + } + resolve({ fileBuffer, filePath }); + }, + ); + }); + + static deleteCustomFirmwareBinary = (filePath: string): Promise => + new Promise(( + resolve: () => void, + reject: (error: Error) => void, + ) => { + fs.unlink( + filePath, + (error: ?Error) => { + if (error) { + reject(error); + return; + } + + resolve(); + }, + ); + }); + static getUser = (): UserCredentials => ({ password: 'password', username: `testUser+${TestData.getID()}@test.com`, diff --git a/test/setup/settings.js b/test/setup/settings.js index 006f4a25..ce75fb6f 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -4,6 +4,7 @@ import path from 'path'; /* eslint-disable sorting/sort-object-props */ export default { + CUSTOM_FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__'), DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'), SERVER_KEY_FILENAME: 'default_key.pem', From 958bb6e6a5372df471e341586afed143b705e21e Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sun, 29 Jan 2017 21:27:47 +0200 Subject: [PATCH 302/504] fix raise your hand values bug --- src/controllers/DevicesController.js | 2 +- test/DevicesController.test.js | 33 ++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 8515c75b..76cd1eda 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -168,7 +168,7 @@ class DevicesController extends Controller { // If signal exists then we want to toggle nyan mode. This just makes the // LED change colors. if (postBody.signal) { - if (postBody.signal !== ('1' || '0')) { + if (!['1', '0'].includes(postBody.signal)) { throw new HttpError('Wrong signal value'); } diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 5a4fc494..83423ff7 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -303,7 +303,7 @@ test.serial( ); test.serial( - 'should call raise your hand function on device', + 'should start raise your hand on device', async t => { const raiseYourHandSpy = sinon.spy(); const device = { @@ -326,7 +326,36 @@ test.serial( deviceServerStub.restore(); t.is(raiseYourHandResponse.status, 200); - t.truthy(raiseYourHandSpy.called); + t.truthy(raiseYourHandSpy.calledWith(true)); + t.is(raiseYourHandResponse.body.id, DEVICE_ID); + }, +); + +test.serial( + 'should stop raise your hand on device', + async t => { + const raiseYourHandSpy = sinon.spy(); + const device = { + raiseYourHand: raiseYourHandSpy, + }; + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const raiseYourHandResponse = await request(app) + .put(`/v1/devices/${DEVICE_ID}`) + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ + access_token: userToken, + signal: '0', + }); + + deviceServerStub.restore(); + + t.is(raiseYourHandResponse.status, 200); + t.truthy(raiseYourHandSpy.calledWith(false)); t.is(raiseYourHandResponse.body.id, DEVICE_ID); }, ); From e996026421f9c9be2f164eb961a203d215f2b6dd Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 30 Jan 2017 14:51:53 +0200 Subject: [PATCH 303/504] rename DeviceRepository to DeviceManager --- src/controllers/DeviceClaimsController.js | 6 ++-- src/controllers/DevicesController.js | 31 +++++++++---------- src/controllers/ProductsController.js | 8 ++--- src/controllers/ProvisioningController.js | 6 ++-- src/defaultBindings.js | 28 ++++++++--------- .../DeviceManager.js} | 6 ++-- src/types.js | 2 +- 7 files changed, 43 insertions(+), 44 deletions(-) rename src/{repository/DeviceRepository.js => managers/DeviceManager.js} (98%) diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js index 560bdef5..1c965d0d 100644 --- a/src/controllers/DeviceClaimsController.js +++ b/src/controllers/DeviceClaimsController.js @@ -1,6 +1,6 @@ // @flow -import type { Device, DeviceRepository } from '../types'; +import type { Device, DeviceManager } from '../types'; import type { ClaimCodeManager } from 'spark-protocol'; import Controller from './Controller'; @@ -8,11 +8,11 @@ import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; class DeviceClaimsController extends Controller { - _deviceRepository: DeviceRepository; + _deviceRepository: DeviceManager; _claimCodeManager: ClaimCodeManager; constructor( - deviceRepository: DeviceRepository, + deviceRepository: DeviceManager, claimCodeManager: ClaimCodeManager, ) { super(); diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js index 76cd1eda..6e7473d5 100644 --- a/src/controllers/DevicesController.js +++ b/src/controllers/DevicesController.js @@ -1,6 +1,6 @@ // @flow -import type { Device, DeviceRepository } from '../types'; +import type { Device, DeviceManager } from '../types'; import type { DeviceAPIType } from '../lib/deviceToAPI'; import nullthrows from 'nullthrows'; @@ -18,19 +18,19 @@ type CompileConfig = { }; class DevicesController extends Controller { - _deviceRepository: DeviceRepository; + _deviceManager: DeviceManager; - constructor(deviceRepository: DeviceRepository) { + constructor(deviceManager: DeviceManager) { super(); - this._deviceRepository = deviceRepository; + this._deviceManager = deviceManager; } @httpVerb('post') @route('/v1/devices') async claimDevice(postBody: { id: string }): Promise<*> { const deviceID = postBody.id; - await this._deviceRepository.claimDevice(deviceID, this.user.id); + await this._deviceManager.claimDevice(deviceID, this.user.id); return this.ok({ ok: true }); } @@ -64,7 +64,7 @@ class DevicesController extends Controller { @httpVerb('delete') @route('/v1/devices/:deviceID') async unclaimDevice(deviceID: string): Promise<*> { - await this._deviceRepository.unclaimDevice(deviceID, this.user.id); + await this._deviceManager.unclaimDevice(deviceID, this.user.id); return this.ok({ ok: true }); } @@ -72,7 +72,7 @@ class DevicesController extends Controller { @route('/v1/devices') async getDevices(): Promise<*> { try { - const devices = await this._deviceRepository.getAll(this.user.id); + const devices = await this._deviceManager.getAll(this.user.id); return this.ok(devices.map((device: Device): DeviceAPIType => deviceToAPI(device)), ); @@ -85,7 +85,7 @@ class DevicesController extends Controller { @httpVerb('get') @route('/v1/devices/:deviceID') async getDevice(deviceID: string): Promise<*> { - const device = await this._deviceRepository.getDetailsByID( + const device = await this._deviceManager.getDetailsByID( deviceID, this.user.id, ); @@ -99,7 +99,7 @@ class DevicesController extends Controller { varName: string, ): Promise<*> { try { - const varValue = await this._deviceRepository.getVariableValue( + const varValue = await this._deviceManager.getVariableValue( deviceID, this.user.id, varName, @@ -129,7 +129,7 @@ class DevicesController extends Controller { ): Promise<*> { // 1 rename device if (postBody.name) { - const updatedAttributes = await this._deviceRepository.renameDevice( + const updatedAttributes = await this._deviceManager.renameDevice( deviceID, this.user.id, postBody.name, @@ -139,7 +139,7 @@ class DevicesController extends Controller { // 2 flash device with known application if (postBody.app_id) { - const flashStatus = await this._deviceRepository.flashKnownApp( + const flashStatus = await this._deviceManager.flashKnownApp( deviceID, this.user.id, postBody.app_id, @@ -158,7 +158,7 @@ class DevicesController extends Controller { (this.request.files: any).file[0]; if (file && file.originalname.endsWith('.bin')) { - const flashStatus = await this._deviceRepository + const flashStatus = await this._deviceManager .flashBinary(deviceID, file); return this.ok({ id: deviceID, status: flashStatus }); @@ -172,7 +172,7 @@ class DevicesController extends Controller { throw new HttpError('Wrong signal value'); } - await this._deviceRepository.raiseYourHand( + await this._deviceManager.raiseYourHand( deviceID, this.user.id, !!parseInt(postBody.signal, 10), @@ -192,14 +192,14 @@ class DevicesController extends Controller { postBody: Object, ): Promise<*> { try { - const result = await this._deviceRepository.callFunction( + const result = await this._deviceManager.callFunction( deviceID, this.user.id, functionName, postBody, ); - const device = await this._deviceRepository.getByID( + const device = await this._deviceManager.getByID( deviceID, this.user.id, ); @@ -209,7 +209,6 @@ class DevicesController extends Controller { if (errorMessage.indexOf('Unknown Function') >= 0) { throw new HttpError('Function not found', 404); } - console.log(error); throw error; } } diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js index 3081baa3..95e85ca2 100644 --- a/src/controllers/ProductsController.js +++ b/src/controllers/ProductsController.js @@ -1,7 +1,7 @@ // @flow -/* eslint-disable */ +/* eslint-disable */ -import type { DeviceRepository } from '../types'; +import type { DeviceManager } from '../types'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; @@ -9,9 +9,9 @@ import route from '../decorators/route'; import HttpError from '../lib/HttpError'; class ProductsController extends Controller { - _deviceRepository: DeviceRepository; + _deviceRepository: DeviceManager; - constructor(deviceRepository: DeviceRepository) { + constructor(deviceRepository: DeviceManager) { super(); this._deviceRepository = deviceRepository; diff --git a/src/controllers/ProvisioningController.js b/src/controllers/ProvisioningController.js index 0de9d43b..0d048263 100644 --- a/src/controllers/ProvisioningController.js +++ b/src/controllers/ProvisioningController.js @@ -1,6 +1,6 @@ // @flow -import type { DeviceRepository } from '../types'; +import type { DeviceManager } from '../types'; import Controller from './Controller'; import httpVerb from '../decorators/httpVerb'; @@ -9,9 +9,9 @@ import deviceToAPI from '../lib/deviceToAPI'; import HttpError from '../lib/HttpError'; class ProvisioningController extends Controller { - _deviceRepository: DeviceRepository; + _deviceRepository: DeviceManager; - constructor(deviceRepository: DeviceRepository) { + constructor(deviceRepository: DeviceManager) { super(); this._deviceRepository = deviceRepository; diff --git a/src/defaultBindings.js b/src/defaultBindings.js index 8ec02716..b400e68b 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -15,7 +15,7 @@ import WebhooksController from './controllers/WebhooksController'; import WebhookManager from './managers/WebhookManager'; import EventManager from './managers/EventManager'; import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository'; -import DeviceRepository from './repository/DeviceRepository'; +import DeviceManager from './managers/DeviceManager'; import UserFileRepository from './repository/UserFileRepository'; import WebhookFileRepository from './repository/WebhookFileRepository'; import settings from './settings'; @@ -37,14 +37,14 @@ export default (container: Container) => { 'DeviceClaimsController', DeviceClaimsController, Transient.with([ - 'DeviceRepository', + 'DeviceManager', 'ClaimCodeManager', ]), ); container.bindClass( 'DevicesController', DevicesController, - Transient.with(['DeviceRepository']), + Transient.with(['DeviceManager']), ); container.bindClass( 'EventsController', @@ -64,7 +64,7 @@ export default (container: Container) => { container.bindClass( 'ProvisioningController', ProvisioningController, - Transient.with(['DeviceRepository']), + Transient.with(['DeviceManager']), ); container.bindClass( 'UsersController', @@ -78,6 +78,16 @@ export default (container: Container) => { ); // managers + container.bindClass( + 'DeviceManager', + DeviceManager, + [ + 'DeviceAttributeRepository', + 'DeviceFirmwareRepository', + 'DeviceKeyRepository', + 'DeviceServer', + ], + ); container.bindClass( 'EventManager', EventManager, @@ -95,16 +105,6 @@ export default (container: Container) => { DeviceFirmwareFileRepository, ['FIRMWARE_DIRECTORY'], ); - container.bindClass( - 'DeviceRepository', - DeviceRepository, - [ - 'DeviceAttributeRepository', - 'DeviceFirmwareRepository', - 'DeviceKeyRepository', - 'DeviceServer', - ], - ); container.bindClass( 'UserRepository', UserFileRepository, diff --git a/src/repository/DeviceRepository.js b/src/managers/DeviceManager.js similarity index 98% rename from src/repository/DeviceRepository.js rename to src/managers/DeviceManager.js index c2a88e9b..fd85062e 100644 --- a/src/repository/DeviceRepository.js +++ b/src/managers/DeviceManager.js @@ -8,7 +8,7 @@ import type { DeviceAttributes, Repository, } from '../types'; -import type DeviceFirmwareRepository from './DeviceFirmwareFileRepository'; +import type DeviceFirmwareRepository from '../repository/DeviceFirmwareFileRepository'; import Moniker from 'moniker'; import ursa from 'ursa'; @@ -16,7 +16,7 @@ import HttpError from '../lib/HttpError'; const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); -class DeviceRepository { +class DeviceManager { _deviceAttributeRepository: DeviceAttributeRepository; _deviceFirmwareRepository: DeviceFirmwareRepository; _deviceKeyRepository: Repository; @@ -313,4 +313,4 @@ class DeviceRepository { } } -export default DeviceRepository; +export default DeviceManager; diff --git a/src/types.js b/src/types.js index aa445e80..a9bd7662 100644 --- a/src/types.js +++ b/src/types.js @@ -157,7 +157,7 @@ export type DeviceAttributeRepository = Repository & { doesUserHaveAccess(deviceID: string, userID: string): Promise, }; -export type DeviceRepository = { +export type DeviceManager = { callFunction( deviceID: string, userID: string, From e8855f6bf0b9b4e06c42016c19d8536f4645fc92 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 30 Jan 2017 07:21:11 -0800 Subject: [PATCH 304/504] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc06142f..a39e31d3 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ to get your core id. You'll need this id later 8) Change server keys to local cloud key + IP Address ``` -particle keys server ..\spark-server\default_key.pub.pem IP_ADDRESS +particle keys server ..\spark-server\src\data\default_key.pub.pem IP_ADDRESS ``` **Note You can go back to using the particle cloud by [downlading the public key here](https://s3.amazonaws.com/spark-website/cloud_public.der).** You'll need to run `particle config particle`, `particle keys server cloud_public.der`, and `particle keys doctor your_core_id` while your device is in DFU mode. From 9d7bdbd0301cfebb5d3059124f49f4452e1b4fe0 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 30 Jan 2017 17:27:53 +0200 Subject: [PATCH 305/504] fix renaming nit --- src/controllers/DeviceClaimsController.js | 8 ++++---- src/controllers/ProductsController.js | 6 +++--- src/controllers/ProvisioningController.js | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js index 1c965d0d..71767945 100644 --- a/src/controllers/DeviceClaimsController.js +++ b/src/controllers/DeviceClaimsController.js @@ -8,16 +8,16 @@ import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; class DeviceClaimsController extends Controller { - _deviceRepository: DeviceManager; + _deviceManager: DeviceManager; _claimCodeManager: ClaimCodeManager; constructor( - deviceRepository: DeviceManager, + deviceManager: DeviceManager, claimCodeManager: ClaimCodeManager, ) { super(); - this._deviceRepository = deviceRepository; + this._deviceManager = deviceManager; this._claimCodeManager = claimCodeManager; } @@ -28,7 +28,7 @@ class DeviceClaimsController extends Controller { this.user.id, ); - const devices = await this._deviceRepository.getAll(this.user.id); + const devices = await this._deviceManager.getAll(this.user.id); const deviceIDs = devices.map( (device: Device): string => device.deviceID, ); diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js index 95e85ca2..0d8ed59d 100644 --- a/src/controllers/ProductsController.js +++ b/src/controllers/ProductsController.js @@ -9,12 +9,12 @@ import route from '../decorators/route'; import HttpError from '../lib/HttpError'; class ProductsController extends Controller { - _deviceRepository: DeviceManager; + _deviceManager: DeviceManager; - constructor(deviceRepository: DeviceManager) { + constructor(deviceManager: DeviceManager) { super(); - this._deviceRepository = deviceRepository; + this._deviceManager = deviceManager; } @httpVerb('get') diff --git a/src/controllers/ProvisioningController.js b/src/controllers/ProvisioningController.js index 0d048263..4a80bf84 100644 --- a/src/controllers/ProvisioningController.js +++ b/src/controllers/ProvisioningController.js @@ -9,12 +9,12 @@ import deviceToAPI from '../lib/deviceToAPI'; import HttpError from '../lib/HttpError'; class ProvisioningController extends Controller { - _deviceRepository: DeviceManager; + _deviceManager: DeviceManager; - constructor(deviceRepository: DeviceManager) { + constructor(deviceManager: DeviceManager) { super(); - this._deviceRepository = deviceRepository; + this._deviceManager = deviceManager; } @httpVerb('post') @@ -27,7 +27,7 @@ class ProvisioningController extends Controller { throw new HttpError('No key provided'); } - const device = await this._deviceRepository.provision( + const device = await this._deviceManager.provision( coreID, this.user.id, postBody.publicKey, From 1c133ee6aba8227fce333795e653425cf91397e2 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 30 Jan 2017 17:49:32 +0200 Subject: [PATCH 306/504] move data folder --- src/settings.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/settings.js b/src/settings.js index 957e5476..b2a42d5d 100644 --- a/src/settings.js +++ b/src/settings.js @@ -22,14 +22,14 @@ const path = require('path'); /* eslint-disable sorting/sort-object-props */ module.exports = { - BUILD_DIRECTORY: path.join(__dirname, './data/build'), - DEVICE_DIRECTORY: path.join(__dirname, './data/deviceKeys'), - FIRMWARE_DIRECTORY: path.join(__dirname, './data/knownApps'), + BUILD_DIRECTORY: path.join(__dirname, '../data/build'), + DEVICE_DIRECTORY: path.join(__dirname, '../data/deviceKeys'), + FIRMWARE_DIRECTORY: path.join(__dirname, '../data/knownApps'), FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../../spark-firmware'), SERVER_KEY_FILENAME: 'default_key.pem', - SERVER_KEYS_DIRECTORY: path.join(__dirname, './data'), - USERS_DIRECTORY: path.join(__dirname, './data/users'), - WEBHOOKS_DIRECTORY: path.join(__dirname, './data/webhooks'), + SERVER_KEYS_DIRECTORY: path.join(__dirname, '../data'), + USERS_DIRECTORY: path.join(__dirname, '../data/users'), + WEBHOOKS_DIRECTORY: path.join(__dirname, '../data/webhooks'), accessTokenLifetime: 7776000, // 90 days, baseUrl: 'http://localhost', From 280767f4421e09816e4c1945e47fae564848eb2b Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Mon, 30 Jan 2017 08:07:23 -0800 Subject: [PATCH 307/504] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a39e31d3..2b705e35 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ to get your core id. You'll need this id later 8) Change server keys to local cloud key + IP Address ``` -particle keys server ..\spark-server\src\data\default_key.pub.pem IP_ADDRESS +particle keys server ..\spark-server\data\default_key.pub.pem IP_ADDRESS ``` **Note You can go back to using the particle cloud by [downlading the public key here](https://s3.amazonaws.com/spark-website/cloud_public.der).** You'll need to run `particle config particle`, `particle keys server cloud_public.der`, and `particle keys doctor your_core_id` while your device is in DFU mode. From 28979407de00412c5126e56e5a46f85196a6df3d Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 31 Jan 2017 18:19:30 +0200 Subject: [PATCH 308/504] remove naming device on provision. --- package.json | 1 - src/managers/DeviceManager.js | 4 ---- 2 files changed, 5 deletions(-) diff --git a/package.json b/package.json index b269f38e..3e5e6c6b 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "hogan.js": "^3.0.2", "lodash": "^4.17.4", "moment": "*", - "moniker": "^0.1.2", "morgan": "^1.7.0", "multer": "^1.2.1", "nullthrows": "^1.0.0", diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js index fd85062e..8b565f8b 100644 --- a/src/managers/DeviceManager.js +++ b/src/managers/DeviceManager.js @@ -10,12 +10,9 @@ import type { } from '../types'; import type DeviceFirmwareRepository from '../repository/DeviceFirmwareFileRepository'; -import Moniker from 'moniker'; import ursa from 'ursa'; import HttpError from '../lib/HttpError'; -const NAME_GENERATOR = Moniker.generator([Moniker.adjective, Moniker.noun]); - class DeviceManager { _deviceAttributeRepository: DeviceAttributeRepository; _deviceFirmwareRepository: DeviceFirmwareRepository; @@ -260,7 +257,6 @@ class DeviceManager { ); const attributes = { deviceID, - name: NAME_GENERATOR.choose(), ...existingAttributes, ownerID: userID, registrar: userID, From b1c232ec7627393c432a6bd39a6d765d633b7f76 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 3 Feb 2017 12:13:26 -0800 Subject: [PATCH 309/504] Fixed issue where express requests wouldn't timeout. --- src/RouteConfig.js | 16 ++++++++++++++-- src/settings.js | 1 + src/types.js | 5 +++-- test/setup/settings.js | 1 + 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 584b009c..da08e307 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -134,8 +134,20 @@ export default ( ); if (functionResult.then) { - const result = await functionResult; - response.status(result.status).json(result.data); + console.log('foooooobar'); + const result = await Promise.race([ + functionResult, + new Promise( + (resolve: () => void, reject: () => void): number => + setTimeout( + () => reject(new Error('timeout')), + settings.API_TIMEOUT * 1000, + ), + ), + ]); + response + .status(nullthrows(result).status) + .json(nullthrows(result).data); } else { response.status(functionResult.status).json(functionResult.data); } diff --git a/src/settings.js b/src/settings.js index b2a42d5d..9108a913 100644 --- a/src/settings.js +++ b/src/settings.js @@ -51,4 +51,5 @@ module.exports = { PORT: 5683, HOST: 'localhost', + API_TIMEOUT: 30, // Timeout for API requests. }; diff --git a/src/types.js b/src/types.js index a9bd7662..8643a8af 100644 --- a/src/types.js +++ b/src/types.js @@ -136,19 +136,20 @@ export type UserRepository = Repository & { }; export type Settings = { + API_TIMEOUT: number, + HOST: string, + PORT: number, accessTokenLifetime: number, baseUrl: string, coreFlashTimeout: number, coreRequestTimeout: number, coreSignalTimeout: number, cryptoSalt: string, - HOST: string, isCoreOnlineTimeout: number, loginRoute: string, logRequests: boolean, maxHooksPerDevice: number, maxHooksPerUser: number, - PORT: number, serverKeyPassEnvVar: ?string, serverKeyPassFile: ?string, }; diff --git a/test/setup/settings.js b/test/setup/settings.js index ce75fb6f..698c4cb8 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -31,6 +31,7 @@ export default { serverKeyPassFile: null, serverKeyPassEnvVar: null, + API_TIMEOUT: 30, PORT: 5683, HOST: 'localhost', }; From e6105adb655c52a386ac5beb3fd9dd402b0bb742 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 3 Feb 2017 12:14:31 -0800 Subject: [PATCH 310/504] Remove console.log --- src/RouteConfig.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/RouteConfig.js b/src/RouteConfig.js index da08e307..dfe9a4be 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -134,7 +134,6 @@ export default ( ); if (functionResult.then) { - console.log('foooooobar'); const result = await Promise.race([ functionResult, new Promise( From 554c3428ceb81c4078d75b9e2e209d4828054afc Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sat, 4 Feb 2017 15:04:52 +0200 Subject: [PATCH 311/504] handle timeouts fixes --- src/RouteConfig.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/RouteConfig.js b/src/RouteConfig.js index dfe9a4be..66d5d6d4 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -136,13 +136,15 @@ export default ( if (functionResult.then) { const result = await Promise.race([ functionResult, - new Promise( - (resolve: () => void, reject: () => void): number => - setTimeout( - () => reject(new Error('timeout')), - settings.API_TIMEOUT * 1000, - ), - ), + !serverSentEvents + ? new Promise( + (resolve: () => void, reject: () => void): number => + setTimeout( + (): void => reject(new Error('timeout')), + settings.API_TIMEOUT * 1000, + ), + ) + : null, ]); response .status(nullthrows(result).status) @@ -166,9 +168,10 @@ export default ( }); (app: any).use(( - error: string, + error: Error, request: $Request, response: $Response, + next: NextFunction, // eslint-disable-line no-unused-vars ) => { response .status(400) From a46072e5015ae446117d6dc83221a304bca357d6 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sun, 5 Feb 2017 21:04:35 +0200 Subject: [PATCH 312/504] make eslint throws on warnings --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e5e6c6b..a8e5241a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "scripts": { "build": "babel ./src --out-dir ./build", "build:clean": "rimraf ./build", - "lint": "eslint --fix -- .", + "lint": "eslint --fix --max-warnings 0 -- .", "prebuild": "npm run build:clean", "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data", "start:prod": "npm run build && node ./build/main.js", From 2de75812c2eb2e0b093346882edf595e828c7e1f Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 7 Feb 2017 19:00:01 +0200 Subject: [PATCH 313/504] fix: return attributes for disconnected devices. --- src/managers/DeviceManager.js | 13 +++--- test/DevicesController.test.js | 75 ++++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js index 8b565f8b..3b66a1c6 100644 --- a/src/managers/DeviceManager.js +++ b/src/managers/DeviceManager.js @@ -102,26 +102,25 @@ class DeviceManager { userID: string, ): Promise => { const device = this._deviceServer.getDevice(deviceID); - if (!device) { - throw new HttpError('No device found', 404); - } const [attributes, description] = await Promise.all([ this._deviceAttributeRepository.getById(deviceID, userID), - device.getDescription(), + device && device.getDescription(), ]); if (!attributes) { throw new HttpError('No device found', 404); } + console.log(device); + return ({ ...attributes, - connected: true, - functions: description.state.f, + connected: !!device, + functions: description ? description.state.f : null, lastFlashedAppName: null, lastHeard: new Date(), - variables: description.state.v, + variables: description ? description.state.v : null, }); }; diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 83423ff7..446d9738 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -66,16 +66,73 @@ test('should throw an error for compile source code endpoint', async t => { t.is(response.status, 400); }); -test.serial('should return device details', async t => { - const response = await request(app) - .get(`/v1/devices/${DEVICE_ID}`) - .query({ access_token: userToken }); - t.is(response.status, 200); - t.is(response.body.id, deviceToApiAttributes.id); - t.is(response.body.name, deviceToApiAttributes.name); - t.is(response.body.ownerID, deviceToApiAttributes.ownerID); -}); +test.serial( + 'should return device details for connected device', + async t => { + + const testFunctions = ['testFunction']; + const testVariables = ['testVariable1', 'testVariable2']; + const device = { + getDescription: () => ({ + state : { + f: testFunctions, + v: testVariables, + }, + }), + }; + + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(device); + + const response = await request(app) + .get(`/v1/devices/${DEVICE_ID}`) + .query({ access_token: userToken }); + + deviceServerStub.restore(); + + + t.is(response.status, 200); + t.is(response.body.connected, true); + t.is( + JSON.stringify(response.body.functions), + JSON.stringify(testFunctions), + ); + t.is(response.body.id, deviceToApiAttributes.id); + t.is(response.body.name, deviceToApiAttributes.name); + t.is(response.body.ownerID, deviceToApiAttributes.ownerID); + t.is( + JSON.stringify(response.body.variables), + JSON.stringify(testVariables), + ); + }, +); + +test.serial( + 'should return device details for disconnected device', + async t => { + const deviceServerStub = sinon.stub( + container.constitute('DeviceServer'), + 'getDevice', + ).returns(null); + + const response = await request(app) + .get(`/v1/devices/${DEVICE_ID}`) + .query({ access_token: userToken }); + + deviceServerStub.restore(); + + t.is(response.status, 200); + t.is(response.body.connected, false); + t.is(response.body.functions, null); + t.is(response.body.id, deviceToApiAttributes.id); + t.is(response.body.name, deviceToApiAttributes.name); + t.is(response.body.ownerID, deviceToApiAttributes.ownerID); + t.is(response.body.variables, null); + }, +); test.serial('should throw an error if device not found', async t => { const response = await request(app) From e99daec448b6f7b88e103238c0764f83d893cd96 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 7 Feb 2017 19:04:21 +0200 Subject: [PATCH 314/504] fix pipeEvent line break --- src/controllers/EventsController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js index c3b8b349..58a7a0f7 100644 --- a/src/controllers/EventsController.js +++ b/src/controllers/EventsController.js @@ -35,7 +35,7 @@ class EventsController extends Controller { _pipeEvent(event: Event) { try { - this.response.write(`event: ${event.name} \n\n`); + this.response.write(`event: ${event.name}\n`); this.response.write(`data: ${JSON.stringify(eventToApi(event))}\n\n`); } catch (error) { logger.error(`pipeEvents - write error: ${error}`); From d188f6074ba24fc0f138237129bb47d64940d01f Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Tue, 7 Feb 2017 19:21:01 +0200 Subject: [PATCH 315/504] clean settings.js --- src/RouteConfig.js | 6 +++--- src/app.js | 2 +- src/managers/DeviceManager.js | 2 -- src/settings.js | 28 ++++++++-------------------- src/types.js | 25 ++++++++++++------------- test/setup/settings.js | 24 +++++------------------- 6 files changed, 29 insertions(+), 58 deletions(-) diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 66d5d6d4..5e6759fc 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -62,7 +62,7 @@ export default ( settings: Settings, ) => { const oauth = new OAuthServer({ - accessTokenLifetime: settings.accessTokenLifetime, + ACCESS_TOKEN_LIFETIME: settings.ACCESS_TOKEN_LIFETIME, allowBearerTokensInQueryString: true, model: new OAuthModel(container.constitute('UserRepository')), }); @@ -74,7 +74,7 @@ export default ( ? multer().fields(allowedUploads) : multer().any(); - app.post(settings.loginRoute, oauth.token()); + app.post(settings.LOGIN_ROUTE, oauth.token()); controllers.forEach((controllerName: string) => { const controller = container.constitute(controllerName); @@ -141,7 +141,7 @@ export default ( (resolve: () => void, reject: () => void): number => setTimeout( (): void => reject(new Error('timeout')), - settings.API_TIMEOUT * 1000, + settings.API_TIMEOUT, ), ) : null, diff --git a/src/app.js b/src/app.js index a5596619..3e726b29 100644 --- a/src/app.js +++ b/src/app.js @@ -40,7 +40,7 @@ export default ( return next(); }; - if (settings.logRequests) { + if (settings.LOG_REQUESTS) { app.use(morgan('combined')); } diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js index 3b66a1c6..e2e1fc46 100644 --- a/src/managers/DeviceManager.js +++ b/src/managers/DeviceManager.js @@ -112,8 +112,6 @@ class DeviceManager { throw new HttpError('No device found', 404); } - console.log(device); - return ({ ...attributes, connected: !!device, diff --git a/src/settings.js b/src/settings.js index 9108a913..86d4633e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -19,9 +19,10 @@ * */ -const path = require('path'); +import path from 'path'; + /* eslint-disable sorting/sort-object-props */ -module.exports = { +export default { BUILD_DIRECTORY: path.join(__dirname, '../data/build'), DEVICE_DIRECTORY: path.join(__dirname, '../data/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, '../data/knownApps'), @@ -31,25 +32,12 @@ module.exports = { USERS_DIRECTORY: path.join(__dirname, '../data/users'), WEBHOOKS_DIRECTORY: path.join(__dirname, '../data/webhooks'), - accessTokenLifetime: 7776000, // 90 days, - baseUrl: 'http://localhost', - coreFlashTimeout: 90000, - coreRequestTimeout: 30000, - coreSignalTimeout: 30000, - isCoreOnlineTimeout: 2000, - loginRoute: '/oauth/token', - logRequests: true, - maxHooksPerDevice: 10, - maxHooksPerUser: 20, - - /** - * Your server crypto keys! - */ - cryptoSalt: 'aes-128-cbc', - serverKeyPassFile: null, - serverKeyPassEnvVar: null, + ACCESS_TOKEN_LIFETIME: 7776000, // 90 days, + API_TIMEOUT: 30000, // Timeout for API requests. + CRYPTO_SALT: 'aes-128-cbc', + LOG_REQUESTS: true, + LOGIN_ROUTE: '/oauth/token', PORT: 5683, HOST: 'localhost', - API_TIMEOUT: 30, // Timeout for API requests. }; diff --git a/src/types.js b/src/types.js index 8643a8af..318a9668 100644 --- a/src/types.js +++ b/src/types.js @@ -136,22 +136,21 @@ export type UserRepository = Repository & { }; export type Settings = { + ACCESS_TOKEN_LIFETIME: number, API_TIMEOUT: number, + BUILD_DIRECTORY: string, + CRYPTO_SALT: string, + DEVICE_DIRECTORY: string, + FIRMWARE_DIRECTORY: string, + FIRMWARE_REPOSITORY_DIRECTORY: string, HOST: string, + LOG_REQUESTS: boolean, + LOGIN_ROUTE: string, PORT: number, - accessTokenLifetime: number, - baseUrl: string, - coreFlashTimeout: number, - coreRequestTimeout: number, - coreSignalTimeout: number, - cryptoSalt: string, - isCoreOnlineTimeout: number, - loginRoute: string, - logRequests: boolean, - maxHooksPerDevice: number, - maxHooksPerUser: number, - serverKeyPassEnvVar: ?string, - serverKeyPassFile: ?string, + SERVER_KEY_FILENAME: string, + SERVER_KEYS_DIRECTORY: string, + USERS_DIRECTORY: string, + WEBHOOKS_DIRECTORY: string, }; export type DeviceAttributeRepository = Repository & { diff --git a/test/setup/settings.js b/test/setup/settings.js index 698c4cb8..e012d8a5 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -12,26 +12,12 @@ export default { USERS_DIRECTORY: path.join(__dirname, '../__test_data__/users'), WEBHOOKS_DIRECTORY: path.join(__dirname, '../__test_data__/webhooks'), - accessTokenLifetime: 7776000, // 90 days, - baseUrl: 'http://localhost', - coreFlashTimeout: 90000, - coreRequestTimeout: 30000, - coreSignalTimeout: 30000, - isCoreOnlineTimeout: 2000, - loginRoute: '/oauth/token', - logRequests: false, - maxHooksPerDevice: 10, - maxHooksPerUser: 20, + ACCESS_TOKEN_LIFETIME: 7776000, // 90 days, + API_TIMEOUT: 30000, + CRYPTO_SALT: 'aes-128-cbc', + LOG_REQUESTS: false, + LOGIN_ROUTE: '/oauth/token', - /** - * Your server crypto keys! - */ - cryptoSalt: 'aes-128-cbc', - serverKeyFile: 'default_key.pem', - serverKeyPassFile: null, - serverKeyPassEnvVar: null, - - API_TIMEOUT: 30, PORT: 5683, HOST: 'localhost', }; From ecf2fc3db03e7e869889674eec2e55f5d0d15040 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Wed, 8 Feb 2017 12:37:34 +0200 Subject: [PATCH 316/504] fix lastHeard --- src/managers/DeviceManager.js | 26 ++++++-------------------- src/types.js | 2 +- test/DevicesController.test.js | 6 ++++++ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js index e2e1fc46..7066b80c 100644 --- a/src/managers/DeviceManager.js +++ b/src/managers/DeviceManager.js @@ -82,18 +82,11 @@ class DeviceManager { const device = this._deviceServer.getDevice(attributes.deviceID); - const pingResponse = device - ? device.ping() - : { - connected: false, - lastPing: null, - }; - return { ...attributes, - connected: pingResponse.connected, + connected: device && device.ping().connected || false, lastFlashedAppName: null, - lastHeard: pingResponse.lastPing, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, }; }; @@ -114,10 +107,10 @@ class DeviceManager { return ({ ...attributes, - connected: !!device, + connected: device && device.ping().connected || false, functions: description ? description.state.f : null, lastFlashedAppName: null, - lastHeard: new Date(), + lastHeard: device && device.ping().lastPing || attributes.lastHeard, variables: description ? description.state.v : null, }); }; @@ -129,18 +122,11 @@ class DeviceManager { async (attributes: DeviceAttributes): Promise => { const device = this._deviceServer.getDevice(attributes.deviceID); - const pingResponse = device - ? device.ping() - : { - connected: false, - lastPing: null, - }; - return { ...attributes, - connected: pingResponse.connected, + connected: device && device.ping().connected || false, lastFlashedAppName: null, - lastHeard: pingResponse.lastPing, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, }; }, ); diff --git a/src/types.js b/src/types.js index 318a9668..e99c5930 100644 --- a/src/types.js +++ b/src/types.js @@ -60,6 +60,7 @@ export type DeviceAttributes = { ip: string, isCellular: boolean, last_iccid?: string, + lastHeard: Date, name: string, ownerID: ?string, particleProductId: number, @@ -113,7 +114,6 @@ export type Device = DeviceAttributes & { connected: boolean, functions?: Array, lastFlashedAppName: ?string, - lastHeard: ?Date, variables?: Object, }; diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js index 446d9738..055f064e 100644 --- a/test/DevicesController.test.js +++ b/test/DevicesController.test.js @@ -73,6 +73,7 @@ test.serial( const testFunctions = ['testFunction']; const testVariables = ['testVariable1', 'testVariable2']; + const lastHeard = new Date(); const device = { getDescription: () => ({ state : { @@ -80,6 +81,10 @@ test.serial( v: testVariables, }, }), + ping: () => ({ + connected: true, + lastPing: lastHeard, + }), }; const deviceServerStub = sinon.stub( @@ -107,6 +112,7 @@ test.serial( JSON.stringify(response.body.variables), JSON.stringify(testVariables), ); + t.is(response.body.last_heard, lastHeard.toISOString()); }, ); From fdec603dc072a405bf88314d0ad803cca9f947aa Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 9 Feb 2017 17:08:42 -0800 Subject: [PATCH 317/504] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b705e35..77588213 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ cd spark-server/ npm install node main.js ``` - +## You'll need to prepare your system for node-gyp https://github.com/nodejs/node-gyp > **Windows Setup** > You'll need to install Python 2.7 and OpenSSL 1.0.2 or older. From 576d7eded3b5171a439b443492b8073dd4e886c3 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 9 Feb 2017 17:09:01 -0800 Subject: [PATCH 318/504] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 77588213..fc903db2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ cd spark-server/ npm install node main.js ``` -## You'll need to prepare your system for node-gyp https://github.com/nodejs/node-gyp +### You'll need to prepare your system for node-gyp +**https://github.com/nodejs/node-gyp** > **Windows Setup** > You'll need to install Python 2.7 and OpenSSL 1.0.2 or older. From f2c458f243db838446e614ac1e9288fae83a795f Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Thu, 9 Feb 2017 17:09:32 -0800 Subject: [PATCH 319/504] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fc903db2..744fcfe4 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ An API compatible open source server for interacting with devices speaking the [ Quick Install ============== +### You'll need to prepare your system for node-gyp. This is used in the URSA package. +**https://github.com/nodejs/node-gyp** ``` git clone https://github.com/spark/spark-server.git @@ -21,8 +23,7 @@ cd spark-server/ npm install node main.js ``` -### You'll need to prepare your system for node-gyp -**https://github.com/nodejs/node-gyp** + > **Windows Setup** > You'll need to install Python 2.7 and OpenSSL 1.0.2 or older. From 9deb1b89e6ec50d75dbd1c5d08521e8be7eb6382 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 10 Feb 2017 08:40:35 -0800 Subject: [PATCH 320/504] Trying to fix packages when installing this package. --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 3e5e6c6b..eceae636 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ }, "dependencies": { "array-flatten": "^2.1.1", + "babel": "^6.5.2", + "babel-loader": "^6.2.10", "basic-auth-parser": "0.0.2", "binary-version-reader": "^0.5.0", "body-parser": "^1.15.2", From 40cc5c172860515aace7740e3093a84ef7137d48 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 12:09:30 -0800 Subject: [PATCH 321/504] Trying to fix npm install --- package.json | 5 +++-- src/managers/DeviceManager.js | 4 ++-- test/setup/settings.js | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 468b22fe..5dc1eccb 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,9 @@ }, "dependencies": { "array-flatten": "^2.1.1", - "babel": "^6.5.2", + "babel-cli": "^6.18.0", "babel-loader": "^6.2.10", + "babel-runtime": "^6.22.0", "basic-auth-parser": "0.0.2", "binary-version-reader": "^0.5.0", "body-parser": "^1.15.2", @@ -62,6 +63,7 @@ "express-oauth-server": "^2.0.0-b1", "hogan.js": "^3.0.2", "lodash": "^4.17.4", + "mkdirp": "^0.5.1", "moment": "*", "morgan": "^1.7.0", "multer": "^1.2.1", @@ -75,7 +77,6 @@ }, "devDependencies": { "ava": "^0.17.0", - "babel-cli": "^6.18.0", "babel-eslint": "^7.1.1", "babel-plugin-transform-class-properties": "^6.19.0", "babel-plugin-transform-decorators": "^6.13.0", diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js index 7066b80c..c3c84b80 100644 --- a/src/managers/DeviceManager.js +++ b/src/managers/DeviceManager.js @@ -105,14 +105,14 @@ class DeviceManager { throw new HttpError('No device found', 404); } - return ({ + return { ...attributes, connected: device && device.ping().connected || false, functions: description ? description.state.f : null, lastFlashedAppName: null, lastHeard: device && device.ping().lastPing || attributes.lastHeard, variables: description ? description.state.v : null, - }); + }; }; getAll = async (userID: string): Promise> => { diff --git a/test/setup/settings.js b/test/setup/settings.js index e012d8a5..5d2a3b60 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -4,9 +4,11 @@ import path from 'path'; /* eslint-disable sorting/sort-object-props */ export default { + BUILD_DIRECTORY: path.join(__dirname, '../__test_data__/build'), CUSTOM_FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__'), DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'), + FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../__test_data__/firmware'), SERVER_KEY_FILENAME: 'default_key.pem', SERVER_KEYS_DIRECTORY: path.join(__dirname, '../__test_data__'), USERS_DIRECTORY: path.join(__dirname, '../__test_data__/users'), From 3eac9b685415f5b58a8010a839b6ba843fa8fdde Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:23:30 -0800 Subject: [PATCH 322/504] Added exports so this library can be used as a npm package. --- package.json | 4 +++- src/OAuthModel.js | 10 +++++++--- src/RouteConfig.js | 9 +++++---- src/defaultBindings.js | 19 +++++++++---------- src/exports.js | 13 +++++++++++++ 5 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 src/exports.js diff --git a/package.json b/package.json index 5dc1eccb..a86d0e76 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,10 @@ "url": "https://github.com/emilyrose" } ], + "main": "./dist/exports.js", "scripts": { "build": "babel ./src --out-dir ./build", + "build:watch": "babel ./src --out-dir ./dist --watch", "build:clean": "rimraf ./build", "lint": "eslint --fix --max-warnings 0 -- .", "prebuild": "npm run build:clean", @@ -52,7 +54,7 @@ }, "dependencies": { "array-flatten": "^2.1.1", - "babel-cli": "^6.18.0", + "babel-cli": "^6.22.2", "babel-loader": "^6.2.10", "babel-runtime": "^6.22.0", "basic-auth-parser": "0.0.2", diff --git a/src/OAuthModel.js b/src/OAuthModel.js index df4fcb08..73f67821 100644 --- a/src/OAuthModel.js +++ b/src/OAuthModel.js @@ -7,7 +7,11 @@ import type { UserRepository, } from './types'; -import ouathClients from './oauthClients.json'; +const OAUTH_CLIENTS = [{ + clientId: 'CLI2', + clientSecret: 'client_secret_here', + grants: ['password'], +}]; class OauthModel { _userRepository: UserRepository; @@ -37,8 +41,8 @@ class OauthModel { }; }; - getClient = (clientId: string, clientSecret: string): Client => - ouathClients.find((client: Client): boolean => + getClient = (clientId: string, clientSecret: string): ?Client => + OAUTH_CLIENTS.find((client: Client): boolean => client.clientId === clientId && client.clientSecret === clientSecret, ); diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 5e6759fc..61e408b1 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -106,14 +106,15 @@ export default ( const values = argumentNames .map((argument: string): string => request.params[argument]); - const controllerInstance = container.constitute(controllerName); + let controllerInstance = container.constitute(controllerName); // In order parallel requests on the controller, the state // (request/response/user) must be added to the controller. if (controllerInstance === controller) { - throw new Error( - '`Transient.with` must be used when binding controllers', - ); + // throw new Error( + // '`Transient.with` must be used when binding controllers', + // ); + controllerInstance = Object.create(controllerInstance); } controllerInstance.request = request; diff --git a/src/defaultBindings.js b/src/defaultBindings.js index b400e68b..9ca61adc 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -3,7 +3,6 @@ import type { Container } from 'constitute'; import { defaultBindings } from 'spark-protocol'; -import { Transient } from 'constitute'; import DeviceClaimsController from './controllers/DeviceClaimsController'; import DevicesController from './controllers/DevicesController'; import EventsController from './controllers/EventsController'; @@ -36,45 +35,45 @@ export default (container: Container) => { container.bindClass( 'DeviceClaimsController', DeviceClaimsController, - Transient.with([ + [ 'DeviceManager', 'ClaimCodeManager', - ]), + ], ); container.bindClass( 'DevicesController', DevicesController, - Transient.with(['DeviceManager']), + ['DeviceManager'], ); container.bindClass( 'EventsController', EventsController, - Transient.with(['EventManager']), + ['EventManager'], ); container.bindClass( 'OauthClientsController', OauthClientsController, - Transient.with([]), + [], ); container.bindClass( 'ProductsController', ProductsController, - Transient.with([]), + [], ); container.bindClass( 'ProvisioningController', ProvisioningController, - Transient.with(['DeviceManager']), + ['DeviceManager'], ); container.bindClass( 'UsersController', UsersController, - Transient.with(['UserRepository']), + ['UserRepository'], ); container.bindClass( 'WebhooksController', WebhooksController, - Transient.with(['WebhookManager']), + ['WebhookManager'], ); // managers diff --git a/src/exports.js b/src/exports.js new file mode 100644 index 00000000..008a4897 --- /dev/null +++ b/src/exports.js @@ -0,0 +1,13 @@ +// @flow + +import logger from './lib/logger'; +import createApp from './app'; +import defaultBindings from './defaultBindings'; +import settings from './settings'; + +export { + createApp, + defaultBindings, + logger, + settings, +}; From e2ffce9d9d800f1361e679205b80d72f9f8ff4c4 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:31:18 -0800 Subject: [PATCH 323/504] Fixing path for export. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a86d0e76..b6c52ac7 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "url": "https://github.com/emilyrose" } ], - "main": "./dist/exports.js", + "main": "./src/exports.js", "scripts": { "build": "babel ./src --out-dir ./build", "build:watch": "babel ./src --out-dir ./dist --watch", From 95cd27a25da9ec12adc3929fffa46a1f346d9706 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:37:42 -0800 Subject: [PATCH 324/504] Added dist folder so this repo can be consumed by other repos. --- .eslintignore | 1 + dist/OAuthModel.js | 130 ++++ dist/RouteConfig.js | 225 ++++++ dist/app.js | 56 ++ dist/controllers/Controller.js | 36 + dist/controllers/DeviceClaimsController.js | 136 ++++ dist/controllers/DevicesController.js | 531 ++++++++++++++ dist/controllers/EventsController.js | 289 ++++++++ dist/controllers/OauthClientsController.js | 183 +++++ dist/controllers/ProductsController.js | 432 ++++++++++++ dist/controllers/ProvisioningController.js | 147 ++++ dist/controllers/UsersController.js | 243 +++++++ dist/controllers/WebhooksController.js | 271 ++++++++ dist/controllers/types.js | 1 + dist/decorators/allowUpload.js | 23 + dist/decorators/anonymous.js | 13 + dist/decorators/httpVerb.js | 13 + dist/decorators/route.js | 13 + dist/decorators/serverSentEvents.js | 13 + dist/decorators/types.js | 1 + dist/defaultBindings.js | 102 +++ dist/exports.js | 29 + dist/lib/HttpError.js | 45 ++ dist/lib/PasswordHasher.js | 86 +++ dist/lib/deviceToAPI.js | 27 + dist/lib/eventToApi.js | 15 + dist/lib/logger.js | 63 ++ dist/main.js | 76 ++ dist/managers/DeviceManager.js | 652 ++++++++++++++++++ dist/managers/EventManager.js | 33 + dist/managers/FirmwareCompilationManager.js | 263 +++++++ dist/managers/WebhookManager.js | 533 ++++++++++++++ .../DeviceFirmwareFileRepository.js | 70 ++ dist/repository/UserFileRepository.js | 535 ++++++++++++++ dist/repository/WebhookFileRepository.js | 310 +++++++++ dist/settings.js | 51 ++ dist/types.js | 1 + package.json | 4 +- 38 files changed, 5650 insertions(+), 2 deletions(-) create mode 100644 dist/OAuthModel.js create mode 100644 dist/RouteConfig.js create mode 100644 dist/app.js create mode 100644 dist/controllers/Controller.js create mode 100644 dist/controllers/DeviceClaimsController.js create mode 100644 dist/controllers/DevicesController.js create mode 100644 dist/controllers/EventsController.js create mode 100644 dist/controllers/OauthClientsController.js create mode 100644 dist/controllers/ProductsController.js create mode 100644 dist/controllers/ProvisioningController.js create mode 100644 dist/controllers/UsersController.js create mode 100644 dist/controllers/WebhooksController.js create mode 100644 dist/controllers/types.js create mode 100644 dist/decorators/allowUpload.js create mode 100644 dist/decorators/anonymous.js create mode 100644 dist/decorators/httpVerb.js create mode 100644 dist/decorators/route.js create mode 100644 dist/decorators/serverSentEvents.js create mode 100644 dist/decorators/types.js create mode 100644 dist/defaultBindings.js create mode 100644 dist/exports.js create mode 100644 dist/lib/HttpError.js create mode 100644 dist/lib/PasswordHasher.js create mode 100644 dist/lib/deviceToAPI.js create mode 100644 dist/lib/eventToApi.js create mode 100644 dist/lib/logger.js create mode 100644 dist/main.js create mode 100644 dist/managers/DeviceManager.js create mode 100644 dist/managers/EventManager.js create mode 100644 dist/managers/FirmwareCompilationManager.js create mode 100644 dist/managers/WebhookManager.js create mode 100644 dist/repository/DeviceFirmwareFileRepository.js create mode 100644 dist/repository/UserFileRepository.js create mode 100644 dist/repository/WebhookFileRepository.js create mode 100644 dist/settings.js create mode 100644 dist/types.js diff --git a/.eslintignore b/.eslintignore index 5d21615e..07d7cfc2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ flow-typed # Don't check auto-generated stuff coverage build +dist binaries node_modules diff --git a/dist/OAuthModel.js b/dist/OAuthModel.js new file mode 100644 index 00000000..592e0e98 --- /dev/null +++ b/dist/OAuthModel.js @@ -0,0 +1,130 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const OAUTH_CLIENTS = [{ + clientId: 'CLI2', + clientSecret: 'client_secret_here', + grants: ['password'], +}]; + +const OauthModel = function OauthModel(userRepository) { + const _this = this; + + (0, _classCallCheck3.default)(this, OauthModel); + + this.getAccessToken = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(bearerToken) { + let user, + userTokenObject; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._userRepository.getByAccessToken(bearerToken); + + case 2: + user = _context.sent; + + if (user) { + _context.next = 5; + break; + } + + return _context.abrupt('return', null); + + case 5: + userTokenObject = user.accessTokens.find(tokenObject => tokenObject.accessToken === bearerToken); + + if (userTokenObject) { + _context.next = 8; + break; + } + + return _context.abrupt('return', null); + + case 8: + return _context.abrupt('return', { + accessToken: userTokenObject.accessToken, + user, + }); + + case 9: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x) { + return _ref.apply(this, arguments); + }; + }()); + + this.getClient = function (clientId, clientSecret) { + return OAUTH_CLIENTS.find(client => client.clientId === clientId && client.clientSecret === clientSecret); + }; + + this.getUser = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._userRepository.validateLogin(username, password); + + case 2: + return _context2.abrupt('return', _context2.sent); + + case 3: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x2, _x3) { + return _ref2.apply(this, arguments); + }; + }()); + + this.saveToken = function (tokenObject, client, user) { + _this._userRepository.saveAccessToken(user.id, tokenObject); + return { + accessToken: tokenObject.accessToken, + client, + user, + }; + }; + + this.validateScope = function (user, client, scope) { + return 'true'; + }; + + this._userRepository = userRepository; +} + +// eslint-disable-next-line no-unused-vars +; + +exports.default = OauthModel; diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js new file mode 100644 index 00000000..2efc9fc1 --- /dev/null +++ b/dist/RouteConfig.js @@ -0,0 +1,225 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); + +const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); + +const _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); + +const _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); + +const _create = require('babel-runtime/core-js/object/create'); + +const _create2 = _interopRequireDefault(_create); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names'); + +const _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames); + +const _expressOauthServer = require('express-oauth-server'); + +const _expressOauthServer2 = _interopRequireDefault(_expressOauthServer); + +const _nullthrows = require('nullthrows'); + +const _nullthrows2 = _interopRequireDefault(_nullthrows); + +const _multer = require('multer'); + +const _multer2 = _interopRequireDefault(_multer); + +const _OAuthModel = require('./OAuthModel'); + +const _OAuthModel2 = _interopRequireDefault(_OAuthModel); + +const _HttpError = require('./lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const maybe = function maybe(middleware, condition) { + return function (request, response, next) { + if (condition) { + middleware(request, response, next); + } else { + next(); + } + }; +}; + +const injectUserMiddleware = function injectUserMiddleware() { + return function (request, response, next) { + const oauthInfo = response.locals.oauth; + if (oauthInfo) { + const token = oauthInfo.token; + // eslint-disable-next-line no-param-reassign + request.user = token && token.user; + } + next(); + }; +}; + +// in old codebase there was _keepAlive() function in controllers , which +// prevents of closing server-sent-events stream if there aren't events for +// a long time, but according to the docs sse keep connection alive automatically. +// if there will be related issues in the future, we can return _keepAlive() back. +const serverSentEventsMiddleware = function serverSentEventsMiddleware() { + return function (request, response, next) { + request.socket.setNoDelay(); + response.writeHead(200, { + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + }); + + next(); + }; +}; + +exports.default = function (app, container, controllers, settings) { + const oauth = new _expressOauthServer2.default({ + ACCESS_TOKEN_LIFETIME: settings.ACCESS_TOKEN_LIFETIME, + allowBearerTokensInQueryString: true, + model: new _OAuthModel2.default(container.constitute('UserRepository')), + }); + + const filesMiddleware = function filesMiddleware() { + const allowedUploads = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + return (0, _nullthrows2.default)(allowedUploads).length ? (0, _multer2.default)().fields(allowedUploads) : (0, _multer2.default)().any(); + }; + + app.post(settings.LOGIN_ROUTE, oauth.token()); + + controllers.forEach((controllerName) => { + const controller = container.constitute(controllerName); + (0, _getOwnPropertyNames2.default)((0, _getPrototypeOf2.default)(controller)).forEach((functionName) => { + const mappedFunction = controller[functionName]; + let allowedUploads = mappedFunction.allowedUploads, + anonymous = mappedFunction.anonymous, + httpVerb = mappedFunction.httpVerb, + route = mappedFunction.route, + serverSentEvents = mappedFunction.serverSentEvents; + + + if (!httpVerb) { + return; + } + app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), maybe(filesMiddleware(allowedUploads), allowedUploads), (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) { + let argumentNames, + values, + controllerInstance, + _request$body, + access_token, + body, + functionResult, + result, + httpError; + + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + argumentNames = (route.match(/:[\w]*/g) || []).map(argumentName => argumentName.replace(':', '')); + values = argumentNames.map(argument => request.params[argument]); + controllerInstance = container.constitute(controllerName); + + // In order parallel requests on the controller, the state + // (request/response/user) must be added to the controller. + + if (controllerInstance === controller) { + // throw new Error( + // '`Transient.with` must be used when binding controllers', + // ); + controllerInstance = (0, _create2.default)(controllerInstance); + } + + controllerInstance.request = request; + controllerInstance.response = response; + controllerInstance.user = request.user; + + // Take access token out if it's posted. + _request$body = request.body, access_token = _request$body.access_token, body = (0, _objectWithoutProperties3.default)(_request$body, ['access_token']); + _context.prev = 8; + functionResult = mappedFunction.call(...[controllerInstance].concat((0, _toConsumableArray3.default)(values), [body])); + + if (!functionResult.then) { + _context.next = 17; + break; + } + + _context.next = 13; + return _promise2.default.race([functionResult, !serverSentEvents ? new _promise2.default((resolve, reject) => setTimeout(() => reject(new Error('timeout')), settings.API_TIMEOUT)) : null]); + + case 13: + result = _context.sent; + + response.status((0, _nullthrows2.default)(result).status).json((0, _nullthrows2.default)(result).data); + _context.next = 18; + break; + + case 17: + response.status(functionResult.status).json(functionResult.data); + + case 18: + _context.next = 24; + break; + + case 20: + _context.prev = 20; + _context.t0 = _context.catch(8); + httpError = new _HttpError2.default(_context.t0); + + response.status(httpError.status).json({ + error: httpError.message, + ok: false, + }); + + case 24: + case 'end': + return _context.stop(); + } + } + }, _callee, undefined, [[8, 20]]); + })); + + return function (_x2, _x3) { + return _ref.apply(this, arguments); + }; + }())); + }); + }); + + app.all('*', (request, response) => { + response.sendStatus(404); + }); + + app.use((error, request, response, next, // eslint-disable-line no-unused-vars + ) => { + response.status(400).json({ + error: error.code ? error.code : error, + ok: false, + }); + }); +}; diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 00000000..6c1dfc67 --- /dev/null +++ b/dist/app.js @@ -0,0 +1,56 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _bodyParser = require('body-parser'); + +const _bodyParser2 = _interopRequireDefault(_bodyParser); + +const _express = require('express'); + +const _express2 = _interopRequireDefault(_express); + +const _morgan = require('morgan'); + +const _morgan2 = _interopRequireDefault(_morgan); + +const _RouteConfig = require('./RouteConfig'); + +const _RouteConfig2 = _interopRequireDefault(_RouteConfig); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (container, settings) { + const app = (0, _express2.default)(); + + const setCORSHeaders = function setCORSHeaders(request, response, next) { + if (request.method === 'OPTIONS') { + response.set({ + 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Max-Age': '300', + }); + return response.sendStatus(204); + } + response.set({ 'Access-Control-Allow-Origin': '*' }); + return next(); + }; + + if (settings.LOG_REQUESTS) { + app.use((0, _morgan2.default)('combined')); + } + + app.use(_bodyParser2.default.json()); + app.use(_bodyParser2.default.urlencoded({ extended: true })); + app.use(setCORSHeaders); + + (0, _RouteConfig2.default)(app, container, ['DeviceClaimsController', + // to avoid routes collisions EventsController should be placed + // before DevicesController + 'EventsController', 'DevicesController', 'OauthClientsController', 'ProductsController', 'ProvisioningController', 'UsersController', 'WebhooksController'], settings); + + return app; +}; diff --git a/dist/controllers/Controller.js b/dist/controllers/Controller.js new file mode 100644 index 00000000..ecb1cbec --- /dev/null +++ b/dist/controllers/Controller.js @@ -0,0 +1,36 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +exports.default = undefined; + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const Controller = function Controller() { + (0, _classCallCheck3.default)(this, Controller); + + this.bad = function (message) { + const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + return { + data: { + error: message, + ok: false, + }, + status, + }; + }; + + this.ok = function (output) { + return { + data: output, + status: 200, + }; + }; +}; + +exports.default = Controller; diff --git a/dist/controllers/DeviceClaimsController.js b/dist/controllers/DeviceClaimsController.js new file mode 100644 index 00000000..6e465768 --- /dev/null +++ b/dist/controllers/DeviceClaimsController.js @@ -0,0 +1,136 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/device_claims'), (_class = (function (_Controller) { + (0, _inherits3.default)(DeviceClaimsController, _Controller); + + function DeviceClaimsController(deviceManager, claimCodeManager) { + (0, _classCallCheck3.default)(this, DeviceClaimsController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (DeviceClaimsController.__proto__ || (0, _getPrototypeOf2.default)(DeviceClaimsController)).call(this)); + + _this._deviceManager = deviceManager; + _this._claimCodeManager = claimCodeManager; + return _this; + } + + (0, _createClass3.default)(DeviceClaimsController, [{ + key: 'createClaimCode', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + let claimCode, + devices, + deviceIDs; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + claimCode = this._claimCodeManager.createClaimCode(this.user.id); + _context.next = 3; + return this._deviceManager.getAll(this.user.id); + + case 3: + devices = _context.sent; + deviceIDs = devices.map(device => device.deviceID); + return _context.abrupt('return', this.ok({ claim_code: claimCode, device_ids: deviceIDs })); + + case 6: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function createClaimCode() { + return _ref.apply(this, arguments); + } + + return createClaimCode; + }()), + }]); + return DeviceClaimsController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClaimCode', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClaimCode'), _class.prototype)), _class)); +exports.default = DeviceClaimsController; diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js new file mode 100644 index 00000000..a6cc6beb --- /dev/null +++ b/dist/controllers/DevicesController.js @@ -0,0 +1,531 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _dec10, + _dec11, + _dec12, + _dec13, + _dec14, + _dec15, + _dec16, + _dec17, + _dec18, + _dec19, + _dec20, + _desc, + _value, + _class; + +const _nullthrows = require('nullthrows'); + +const _nullthrows2 = _interopRequireDefault(_nullthrows); + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _FirmwareCompilationManager = require('../managers/FirmwareCompilationManager'); + +const _FirmwareCompilationManager2 = _interopRequireDefault(_FirmwareCompilationManager); + +const _allowUpload = require('../decorators/allowUpload'); + +const _allowUpload2 = _interopRequireDefault(_allowUpload); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +const _deviceToAPI = require('../lib/deviceToAPI'); + +const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = (function (_Controller) { + (0, _inherits3.default)(DevicesController, _Controller); + + function DevicesController(deviceManager) { + (0, _classCallCheck3.default)(this, DevicesController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (DevicesController.__proto__ || (0, _getPrototypeOf2.default)(DevicesController)).call(this)); + + _this._deviceManager = deviceManager; + return _this; + } + + (0, _createClass3.default)(DevicesController, [{ + key: 'claimDevice', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(postBody) { + let deviceID; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + deviceID = postBody.id; + _context.next = 3; + return this._deviceManager.claimDevice(deviceID, this.user.id); + + case 3: + return _context.abrupt('return', this.ok({ ok: true })); + + case 4: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function claimDevice(_x) { + return _ref.apply(this, arguments); + } + + return claimDevice; + }()), + }, { + key: 'getAppFirmware', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(binaryID) { + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + return _context2.abrupt('return', this.ok(_FirmwareCompilationManager2.default.getBinaryForID(binaryID))); + + case 1: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function getAppFirmware(_x2) { + return _ref2.apply(this, arguments); + } + + return getAppFirmware; + }()), + }, { + key: 'compileSources', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(postBody) { + let response; + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _FirmwareCompilationManager2.default.compileSource((0, _nullthrows2.default)(postBody.platform_id || postBody.product_id), this.request.files); + + case 2: + response = _context3.sent; + + if (response) { + _context3.next = 5; + break; + } + + throw new _HttpError2.default('Error during compilation'); + + case 5: + return _context3.abrupt('return', this.ok((0, _extends3.default)({}, response, { + binary_url: `/v1/binaries/${response.binary_id}`, + ok: true, + }))); + + case 6: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function compileSources(_x3) { + return _ref3.apply(this, arguments); + } + + return compileSources; + }()), + }, { + key: 'unclaimDevice', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) { + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return this._deviceManager.unclaimDevice(deviceID, this.user.id); + + case 2: + return _context4.abrupt('return', this.ok({ ok: true })); + + case 3: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function unclaimDevice(_x4) { + return _ref4.apply(this, arguments); + } + + return unclaimDevice; + }()), + }, { + key: 'getDevices', + value: (function () { + const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { + let devices; + return _regenerator2.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + _context5.prev = 0; + _context5.next = 3; + return this._deviceManager.getAll(this.user.id); + + case 3: + devices = _context5.sent; + return _context5.abrupt('return', this.ok(devices.map(device => (0, _deviceToAPI2.default)(device)))); + + case 7: + _context5.prev = 7; + _context5.t0 = _context5.catch(0); + return _context5.abrupt('return', this.ok([])); + + case 10: + case 'end': + return _context5.stop(); + } + } + }, _callee5, this, [[0, 7]]); + })); + + function getDevices() { + return _ref5.apply(this, arguments); + } + + return getDevices; + }()), + }, { + key: 'getDevice', + value: (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) { + let device; + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + _context6.next = 2; + return this._deviceManager.getDetailsByID(deviceID, this.user.id); + + case 2: + device = _context6.sent; + return _context6.abrupt('return', this.ok((0, _deviceToAPI2.default)(device))); + + case 4: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function getDevice(_x5) { + return _ref6.apply(this, arguments); + } + + return getDevice; + }()), + }, { + key: 'getVariableValue', + value: (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, varName) { + let varValue, + errorMessage; + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + _context7.prev = 0; + _context7.next = 3; + return this._deviceManager.getVariableValue(deviceID, this.user.id, varName); + + case 3: + varValue = _context7.sent; + return _context7.abrupt('return', this.ok({ result: varValue })); + + case 7: + _context7.prev = 7; + _context7.t0 = _context7.catch(0); + errorMessage = _context7.t0.message; + + if (!errorMessage.match('Variable not found')) { + _context7.next = 12; + break; + } + + throw new _HttpError2.default('Variable not found', 404); + + case 12: + throw _context7.t0; + + case 13: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this, [[0, 7]]); + })); + + function getVariableValue(_x6, _x7) { + return _ref7.apply(this, arguments); + } + + return getVariableValue; + }()), + }, { + key: 'updateDevice', + value: (function () { + const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) { + let updatedAttributes, + flashStatus, + file, + _flashStatus; + + return _regenerator2.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + if (!postBody.name) { + _context8.next = 5; + break; + } + + _context8.next = 3; + return this._deviceManager.renameDevice(deviceID, this.user.id, postBody.name); + + case 3: + updatedAttributes = _context8.sent; + return _context8.abrupt('return', this.ok({ name: updatedAttributes.name, ok: true })); + + case 5: + if (!postBody.app_id) { + _context8.next = 10; + break; + } + + _context8.next = 8; + return this._deviceManager.flashKnownApp(deviceID, this.user.id, postBody.app_id); + + case 8: + flashStatus = _context8.sent; + return _context8.abrupt('return', this.ok({ id: deviceID, status: flashStatus })); + + case 10: + if (!(this.request.files && !this.request.files.file)) { + _context8.next = 12; + break; + } + + throw new Error('Firmware file not provided'); + + case 12: + file = this.request.files && this.request.files.file[0]; + + if (!(file && file.originalname.endsWith('.bin'))) { + _context8.next = 18; + break; + } + + _context8.next = 16; + return this._deviceManager.flashBinary(deviceID, file); + + case 16: + _flashStatus = _context8.sent; + return _context8.abrupt('return', this.ok({ id: deviceID, status: _flashStatus })); + + case 18: + if (!postBody.signal) { + _context8.next = 24; + break; + } + + if (['1', '0'].includes(postBody.signal)) { + _context8.next = 21; + break; + } + + throw new _HttpError2.default('Wrong signal value'); + + case 21: + _context8.next = 23; + return this._deviceManager.raiseYourHand(deviceID, this.user.id, !!parseInt(postBody.signal, 10)); + + case 23: + return _context8.abrupt('return', this.ok({ id: deviceID, ok: true })); + + case 24: + throw new _HttpError2.default('Did not update device'); + + case 25: + case 'end': + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function updateDevice(_x8, _x9) { + return _ref8.apply(this, arguments); + } + + return updateDevice; + }()), + }, { + key: 'callDeviceFunction', + value: (function () { + const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) { + let result, + device, + errorMessage; + return _regenerator2.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + _context9.prev = 0; + _context9.next = 3; + return this._deviceManager.callFunction(deviceID, this.user.id, functionName, postBody); + + case 3: + result = _context9.sent; + _context9.next = 6; + return this._deviceManager.getByID(deviceID, this.user.id); + + case 6: + device = _context9.sent; + return _context9.abrupt('return', this.ok((0, _deviceToAPI2.default)(device, result))); + + case 10: + _context9.prev = 10; + _context9.t0 = _context9.catch(0); + errorMessage = _context9.t0.message; + + if (!(errorMessage.indexOf('Unknown Function') >= 0)) { + _context9.next = 15; + break; + } + + throw new _HttpError2.default('Function not found', 404); + + case 15: + throw _context9.t0; + + case 16: + case 'end': + return _context9.stop(); + } + } + }, _callee9, this, [[0, 10]]); + })); + + function callDeviceFunction(_x10, _x11, _x12) { + return _ref9.apply(this, arguments); + } + + return callDeviceFunction; + }()), + }]); + return DevicesController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype)), _class)); +exports.default = DevicesController; diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js new file mode 100644 index 00000000..5208feb2 --- /dev/null +++ b/dist/controllers/EventsController.js @@ -0,0 +1,289 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _stringify = require('babel-runtime/core-js/json/stringify'); + +const _stringify2 = _interopRequireDefault(_stringify); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _dec10, + _dec11, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _serverSentEvents = require('../decorators/serverSentEvents'); + +const _serverSentEvents2 = _interopRequireDefault(_serverSentEvents); + +const _logger = require('../lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _eventToApi = require('../lib/eventToApi'); + +const _eventToApi2 = _interopRequireDefault(_eventToApi); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec3 = (0, _serverSentEvents2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('post'), _dec11 = (0, _route2.default)('/v1/devices/events'), (_class = (function (_Controller) { + (0, _inherits3.default)(EventsController, _Controller); + + function EventsController(eventManager) { + (0, _classCallCheck3.default)(this, EventsController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (EventsController.__proto__ || (0, _getPrototypeOf2.default)(EventsController)).call(this)); + + _this._eventManager = eventManager; + return _this; + } + + (0, _createClass3.default)(EventsController, [{ + key: '_closeStream', + value: function _closeStream(subscriptionID) { + const _this2 = this; + + return new _promise2.default((resolve) => { + const closeStreamHandler = function closeStreamHandler() { + _this2._eventManager.unsubscribe(subscriptionID); + resolve(); + }; + + _this2.request.on('close', closeStreamHandler); + _this2.request.on('end', closeStreamHandler); + _this2.response.on('finish', closeStreamHandler); + _this2.response.on('end', closeStreamHandler); + }); + }, + }, { + key: '_pipeEvent', + value: function _pipeEvent(event) { + try { + this.response.write(`event: ${event.name}\n`); + this.response.write(`data: ${(0, _stringify2.default)((0, _eventToApi2.default)(event))}\n\n`); + } catch (error) { + _logger2.default.error(`pipeEvents - write error: ${error}`); + throw error; + } + }, + }, { + key: 'getEvents', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(eventNamePrefix) { + let subscriptionID; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { userID: this.user.id }); + _context.next = 3; + return this._closeStream(subscriptionID); + + case 3: + return _context.abrupt('return', this.ok()); + + case 4: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function getEvents(_x) { + return _ref.apply(this, arguments); + } + + return getEvents; + }()), + }, { + key: 'getMyEvents', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) { + let subscriptionID; + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { + mydevices: true, + userID: this.user.id, + }); + _context2.next = 3; + return this._closeStream(subscriptionID); + + case 3: + return _context2.abrupt('return', this.ok()); + + case 4: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function getMyEvents(_x2) { + return _ref2.apply(this, arguments); + } + + return getMyEvents; + }()), + }, { + key: 'getDeviceEvents', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, eventNamePrefix) { + let subscriptionID; + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { + deviceID, + userID: this.user.id, + }); + _context3.next = 3; + return this._closeStream(subscriptionID); + + case 3: + return _context3.abrupt('return', this.ok()); + + case 4: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function getDeviceEvents(_x3, _x4) { + return _ref3.apply(this, arguments); + } + + return getDeviceEvents; + }()), + }, { + key: 'publish', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(postBody) { + let eventData; + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + eventData = { + data: postBody.data, + isPublic: !postBody.private, + name: postBody.name, + ttl: postBody.ttl, + userID: this.user.id, + }; + + + this._eventManager.publish(eventData); + return _context4.abrupt('return', this.ok({ ok: true })); + + case 3: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function publish(_x5) { + return _ref4.apply(this, arguments); + } + + return publish; + }()), + }]); + return EventsController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class)); +exports.default = EventsController; diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js new file mode 100644 index 00000000..e9ff1c86 --- /dev/null +++ b/dist/controllers/OauthClientsController.js @@ -0,0 +1,183 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = (function (_Controller) { + (0, _inherits3.default)(OauthClientsController, _Controller); + + function OauthClientsController() { + (0, _classCallCheck3.default)(this, OauthClientsController); + return (0, _possibleConstructorReturn3.default)(this, (OauthClientsController.__proto__ || (0, _getPrototypeOf2.default)(OauthClientsController)).apply(this, arguments)); + } + + (0, _createClass3.default)(OauthClientsController, [{ + key: 'createClient', + + // eslint-disable-next-line class-methods-use-this + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function createClient() { + return _ref.apply(this, arguments); + } + + return createClient; + }()), + }, { + key: 'editClient', + + // eslint-disable-next-line class-methods-use-this + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function editClient() { + return _ref2.apply(this, arguments); + } + + return editClient; + }()), + }, { + key: 'deleteClient', + + // eslint-disable-next-line class-methods-use-this + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function deleteClient() { + return _ref3.apply(this, arguments); + } + + return deleteClient; + }()), + }]); + return OauthClientsController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class)); +exports.default = OauthClientsController; diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js new file mode 100644 index 00000000..0ec9cf30 --- /dev/null +++ b/dist/controllers/ProductsController.js @@ -0,0 +1,432 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _dec10, + _dec11, + _dec12, + _dec13, + _dec14, + _dec15, + _dec16, + _dec17, + _dec18, + _dec19, + _dec20, + _dec21, + _dec22, + _dec23, + _dec24, + _desc, + _value, + _class; +/* eslint-disable */ + +var _Controller2 = require('./Controller'); + +var _Controller3 = _interopRequireDefault(_Controller2); + +var _httpVerb = require('../decorators/httpVerb'); + +var _httpVerb2 = _interopRequireDefault(_httpVerb); + +var _route = require('../decorators/route'); + +var _route2 = _interopRequireDefault(_route); + +var _HttpError = require('../lib/HttpError'); + +var _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec7 = (0, _httpVerb2.default)('post'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/device_claims'), _dec9 = (0, _httpVerb2.default)('get'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec11 = (0, _httpVerb2.default)('post'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec15 = (0, _httpVerb2.default)('put'), _dec16 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec17 = (0, _httpVerb2.default)('delete'), _dec18 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec19 = (0, _httpVerb2.default)('get'), _dec20 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec21 = (0, _httpVerb2.default)('get'), _dec22 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec23 = (0, _httpVerb2.default)('delete'), _dec24 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) { + (0, _inherits3.default)(ProductsController, _Controller); + + function ProductsController(deviceManager) { + (0, _classCallCheck3.default)(this, ProductsController); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ProductsController.__proto__ || (0, _getPrototypeOf2.default)(ProductsController)).call(this)); + + _this._deviceManager = deviceManager; + return _this; + } + + (0, _createClass3.default)(ProductsController, [{ + key: 'getProducts', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function getProducts() { + return _ref.apply(this, arguments); + } + + return getProducts; + }() + }, { + key: 'createProduct', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function createProduct() { + return _ref2.apply(this, arguments); + } + + return createProduct; + }() + }, { + key: 'getProduct', + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function getProduct(_x) { + return _ref3.apply(this, arguments); + } + + return getProduct; + }() + }, { + key: 'generateClaimCode', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function generateClaimCode(_x2) { + return _ref4.apply(this, arguments); + } + + return generateClaimCode; + }() + }, { + key: 'getFirmware', + value: function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context5.stop(); + } + } + }, _callee5, this); + })); + + function getFirmware(_x3) { + return _ref5.apply(this, arguments); + } + + return getFirmware; + }() + + // {version: number, name: 'current', binary: File, title: string, description: string} + + }, { + key: 'getFirmware', + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function getFirmware(_x4) { + return _ref6.apply(this, arguments); + } + + return getFirmware; + }() + }, { + key: 'getDevices', + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this); + })); + + function getDevices(_x5) { + return _ref7.apply(this, arguments); + } + + return getDevices; + }() + }, { + key: 'setFirmwareVersion', + value: function () { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productIdOrSlug, deviceID, body) { + return _regenerator2.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function setFirmwareVersion(_x6, _x7, _x8) { + return _ref8.apply(this, arguments); + } + + return setFirmwareVersion; + }() + }, { + key: 'removeDeviceFromProduct', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIdOrSlug, deviceID) { + return _regenerator2.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context9.stop(); + } + } + }, _callee9, this); + })); + + function removeDeviceFromProduct(_x9, _x10) { + return _ref9.apply(this, arguments); + } + + return removeDeviceFromProduct; + }() + }, { + key: 'getConfig', + value: function () { + var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee10$(_context10) { + while (1) { + switch (_context10.prev = _context10.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context10.stop(); + } + } + }, _callee10, this); + })); + + function getConfig(_x11) { + return _ref10.apply(this, arguments); + } + + return getConfig; + }() + }, { + key: 'getEvents', + value: function () { + var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(productIdOrSlug, eventName) { + return _regenerator2.default.wrap(function _callee11$(_context11) { + while (1) { + switch (_context11.prev = _context11.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context11.stop(); + } + } + }, _callee11, this); + })); + + function getEvents(_x12, _x13) { + return _ref11.apply(this, arguments); + } + + return getEvents; + }() + }, { + key: 'removeTeamMember', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug, username) { + return _regenerator2.default.wrap(function _callee12$(_context12) { + while (1) { + switch (_context12.prev = _context12.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context12.stop(); + } + } + }, _callee12, this); + })); + + function removeTeamMember(_x14, _x15) { + return _ref12.apply(this, arguments); + } + + return removeTeamMember; + }() + }]); + return ProductsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'generateClaimCode', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'generateClaimCode'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec23, _dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class)); +exports.default = ProductsController; +/* eslint-enable */ diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js new file mode 100644 index 00000000..a65f7fdd --- /dev/null +++ b/dist/controllers/ProvisioningController.js @@ -0,0 +1,147 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +const _deviceToAPI = require('../lib/deviceToAPI'); + +const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/provisioning/:coreID'), (_class = (function (_Controller) { + (0, _inherits3.default)(ProvisioningController, _Controller); + + function ProvisioningController(deviceManager) { + (0, _classCallCheck3.default)(this, ProvisioningController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (ProvisioningController.__proto__ || (0, _getPrototypeOf2.default)(ProvisioningController)).call(this)); + + _this._deviceManager = deviceManager; + return _this; + } + + (0, _createClass3.default)(ProvisioningController, [{ + key: 'provision', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(coreID, postBody) { + let device; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (postBody.publicKey) { + _context.next = 2; + break; + } + + throw new _HttpError2.default('No key provided'); + + case 2: + _context.next = 4; + return this._deviceManager.provision(coreID, this.user.id, postBody.publicKey); + + case 4: + device = _context.sent; + return _context.abrupt('return', this.ok((0, _deviceToAPI2.default)(device))); + + case 6: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function provision(_x, _x2) { + return _ref.apply(this, arguments); + } + + return provision; + }()), + }]); + return ProvisioningController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'provision', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'provision'), _class.prototype)), _class)); +exports.default = ProvisioningController; diff --git a/dist/controllers/UsersController.js b/dist/controllers/UsersController.js new file mode 100644 index 00000000..86db684a --- /dev/null +++ b/dist/controllers/UsersController.js @@ -0,0 +1,243 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _desc, + _value, + _class; + +const _basicAuthParser3 = require('basic-auth-parser'); + +const _basicAuthParser4 = _interopRequireDefault(_basicAuthParser3); + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _anonymous = require('../decorators/anonymous'); + +const _anonymous2 = _interopRequireDefault(_anonymous); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/users'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('delete'), _dec5 = (0, _route2.default)('/v1/access_tokens/:token'), _dec6 = (0, _anonymous2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/access_tokens'), _dec9 = (0, _anonymous2.default)(), (_class = (function (_Controller) { + (0, _inherits3.default)(UsersController, _Controller); + + function UsersController(userRepository) { + (0, _classCallCheck3.default)(this, UsersController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (UsersController.__proto__ || (0, _getPrototypeOf2.default)(UsersController)).call(this)); + + _this._userRepository = userRepository; + return _this; + } + + (0, _createClass3.default)(UsersController, [{ + key: 'createUser', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + let isUserNameInUse; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.prev = 0; + _context.next = 3; + return this._userRepository.isUserNameInUse(userCredentials.username); + + case 3: + isUserNameInUse = _context.sent; + + if (!isUserNameInUse) { + _context.next = 6; + break; + } + + throw new _HttpError2.default('user with the username already exists'); + + case 6: + _context.next = 8; + return this._userRepository.createWithCredentials(userCredentials); + + case 8: + return _context.abrupt('return', this.ok({ ok: true })); + + case 11: + _context.prev = 11; + _context.t0 = _context.catch(0); + return _context.abrupt('return', this.bad(_context.t0.message)); + + case 14: + case 'end': + return _context.stop(); + } + } + }, _callee, this, [[0, 11]]); + })); + + function createUser(_x) { + return _ref.apply(this, arguments); + } + + return createUser; + }()), + }, { + key: 'deleteAccessToken', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(token) { + let _basicAuthParser, + username, + password, + user; + + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _basicAuthParser = (0, _basicAuthParser4.default)(this.request.get('authorization')), username = _basicAuthParser.username, password = _basicAuthParser.password; + _context2.next = 3; + return this._userRepository.validateLogin(username, password); + + case 3: + user = _context2.sent; + + + this._userRepository.deleteAccessToken(user.id, token); + + return _context2.abrupt('return', this.ok({ ok: true })); + + case 6: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function deleteAccessToken(_x2) { + return _ref2.apply(this, arguments); + } + + return deleteAccessToken; + }()), + }, { + key: 'getAccessTokens', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + let _basicAuthParser2, + username, + password, + user; + + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _basicAuthParser2 = (0, _basicAuthParser4.default)(this.request.get('authorization')), username = _basicAuthParser2.username, password = _basicAuthParser2.password; + _context3.next = 3; + return this._userRepository.validateLogin(username, password); + + case 3: + user = _context3.sent; + return _context3.abrupt('return', this.ok(user.accessTokens)); + + case 5: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function getAccessTokens() { + return _ref3.apply(this, arguments); + } + + return getAccessTokens; + }()), + }]); + return UsersController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createUser', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createUser'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteAccessToken', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteAccessToken'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAccessTokens', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAccessTokens'), _class.prototype)), _class)); +exports.default = UsersController; diff --git a/dist/controllers/WebhooksController.js b/dist/controllers/WebhooksController.js new file mode 100644 index 00000000..9783ca59 --- /dev/null +++ b/dist/controllers/WebhooksController.js @@ -0,0 +1,271 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; + +const validateWebhookMutator = function validateWebhookMutator(webhookMutator) { + if (!webhookMutator.event) { + return new _HttpError2.default('no event name provided'); + } + if (!webhookMutator.url) { + return new _HttpError2.default('no url provided'); + } + if (!webhookMutator.requestType) { + return new _HttpError2.default('no requestType provided'); + } + if (!REQUEST_TYPES.includes(webhookMutator.requestType)) { + return new _HttpError2.default('wrong requestType'); + } + + return null; +}; + +const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookId'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookId'), (_class = (function (_Controller) { + (0, _inherits3.default)(WebhooksController, _Controller); + + function WebhooksController(webhookManager) { + (0, _classCallCheck3.default)(this, WebhooksController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (WebhooksController.__proto__ || (0, _getPrototypeOf2.default)(WebhooksController)).call(this)); + + _this._webhookManager = webhookManager; + return _this; + } + + (0, _createClass3.default)(WebhooksController, [{ + key: 'getAll', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.t0 = this; + _context.next = 3; + return this._webhookManager.getAll(this.user.id); + + case 3: + _context.t1 = _context.sent; + return _context.abrupt('return', _context.t0.ok.call(_context.t0, _context.t1)); + + case 5: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function getAll() { + return _ref.apply(this, arguments); + } + + return getAll; + }()), + }, { + key: 'getById', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookId) { + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.t0 = this; + _context2.next = 3; + return this._webhookManager.getByID(webhookId, this.user.id); + + case 3: + _context2.t1 = _context2.sent; + return _context2.abrupt('return', _context2.t0.ok.call(_context2.t0, _context2.t1)); + + case 5: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function getById(_x) { + return _ref2.apply(this, arguments); + } + + return getById; + }()), + }, { + key: 'create', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + let validateError, + newWebhook; + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + validateError = validateWebhookMutator(model); + + if (!validateError) { + _context3.next = 3; + break; + } + + throw validateError; + + case 3: + _context3.next = 5; + return this._webhookManager.create((0, _extends3.default)({}, model, { + ownerID: this.user.id, + })); + + case 5: + newWebhook = _context3.sent; + return _context3.abrupt('return', this.ok({ + created_at: newWebhook.created_at, + event: newWebhook.event, + id: newWebhook.id, + ok: true, + url: newWebhook.url, + })); + + case 7: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function create(_x2) { + return _ref3.apply(this, arguments); + } + + return create; + }()), + }, { + key: 'deleteById', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookId) { + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return this._webhookManager.deleteByID(webhookId, this.user.id); + + case 2: + return _context4.abrupt('return', this.ok({ ok: true })); + + case 3: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function deleteById(_x3) { + return _ref4.apply(this, arguments); + } + + return deleteById; + }()), + }]); + return WebhooksController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype)), _class)); +exports.default = WebhooksController; diff --git a/dist/controllers/types.js b/dist/controllers/types.js new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dist/controllers/types.js @@ -0,0 +1 @@ + diff --git a/dist/decorators/allowUpload.js b/dist/decorators/allowUpload.js new file mode 100644 index 00000000..53e1d236 --- /dev/null +++ b/dist/decorators/allowUpload.js @@ -0,0 +1,23 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function () { + const fileName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; + const maxCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + return function (target, name, descriptor) { + const allowedUploads = target[name].allowedUploads || []; + if (fileName) { + allowedUploads.push({ + maxCount, + name: fileName, + }); + } + + target[name].allowedUploads = allowedUploads; + return descriptor; + }; +}; diff --git a/dist/decorators/anonymous.js b/dist/decorators/anonymous.js new file mode 100644 index 00000000..78a9fa94 --- /dev/null +++ b/dist/decorators/anonymous.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function () { + return function (target, name, descriptor) { + target[name].anonymous = true; + return descriptor; + }; +}; diff --git a/dist/decorators/httpVerb.js b/dist/decorators/httpVerb.js new file mode 100644 index 00000000..a3d2df25 --- /dev/null +++ b/dist/decorators/httpVerb.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function (httpVerb) { + return function (target, name, descriptor) { + target[name].httpVerb = httpVerb; + return descriptor; + }; +}; diff --git a/dist/decorators/route.js b/dist/decorators/route.js new file mode 100644 index 00000000..d58c4772 --- /dev/null +++ b/dist/decorators/route.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function (route) { + return function (target, name, descriptor) { + target[name].route = route; + return descriptor; + }; +}; diff --git a/dist/decorators/serverSentEvents.js b/dist/decorators/serverSentEvents.js new file mode 100644 index 00000000..ddd1ca6a --- /dev/null +++ b/dist/decorators/serverSentEvents.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function () { + return function (target, name, descriptor) { + target[name].serverSentEvents = true; + return descriptor; + }; +}; diff --git a/dist/decorators/types.js b/dist/decorators/types.js new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dist/decorators/types.js @@ -0,0 +1 @@ + diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js new file mode 100644 index 00000000..7e801d93 --- /dev/null +++ b/dist/defaultBindings.js @@ -0,0 +1,102 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _sparkProtocol = require('spark-protocol'); + +const _DeviceClaimsController = require('./controllers/DeviceClaimsController'); + +const _DeviceClaimsController2 = _interopRequireDefault(_DeviceClaimsController); + +const _DevicesController = require('./controllers/DevicesController'); + +const _DevicesController2 = _interopRequireDefault(_DevicesController); + +const _EventsController = require('./controllers/EventsController'); + +const _EventsController2 = _interopRequireDefault(_EventsController); + +const _OauthClientsController = require('./controllers/OauthClientsController'); + +const _OauthClientsController2 = _interopRequireDefault(_OauthClientsController); + +const _ProductsController = require('./controllers/ProductsController'); + +const _ProductsController2 = _interopRequireDefault(_ProductsController); + +const _ProvisioningController = require('./controllers/ProvisioningController'); + +const _ProvisioningController2 = _interopRequireDefault(_ProvisioningController); + +const _UsersController = require('./controllers/UsersController'); + +const _UsersController2 = _interopRequireDefault(_UsersController); + +const _WebhooksController = require('./controllers/WebhooksController'); + +const _WebhooksController2 = _interopRequireDefault(_WebhooksController); + +const _WebhookManager = require('./managers/WebhookManager'); + +const _WebhookManager2 = _interopRequireDefault(_WebhookManager); + +const _EventManager = require('./managers/EventManager'); + +const _EventManager2 = _interopRequireDefault(_EventManager); + +const _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepository'); + +const _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository); + +const _DeviceManager = require('./managers/DeviceManager'); + +const _DeviceManager2 = _interopRequireDefault(_DeviceManager); + +const _UserFileRepository = require('./repository/UserFileRepository'); + +const _UserFileRepository2 = _interopRequireDefault(_UserFileRepository); + +const _WebhookFileRepository = require('./repository/WebhookFileRepository'); + +const _WebhookFileRepository2 = _interopRequireDefault(_WebhookFileRepository); + +const _settings = require('./settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (container) { + // spark protocol container bindings + (0, _sparkProtocol.defaultBindings)(container); + + // settings + container.bindValue('DEVICE_DIRECTORY', _settings2.default.DEVICE_DIRECTORY); + container.bindValue('FIRMWARE_DIRECTORY', _settings2.default.FIRMWARE_DIRECTORY); + container.bindValue('SERVER_KEY_FILENAME', _settings2.default.SERVER_KEY_FILENAME); + container.bindValue('SERVER_KEYS_DIRECTORY', _settings2.default.SERVER_KEYS_DIRECTORY); + container.bindValue('USERS_DIRECTORY', _settings2.default.USERS_DIRECTORY); + container.bindValue('WEBHOOKS_DIRECTORY', _settings2.default.WEBHOOKS_DIRECTORY); + + // controllers + container.bindClass('DeviceClaimsController', _DeviceClaimsController2.default, ['DeviceManager', 'ClaimCodeManager']); + container.bindClass('DevicesController', _DevicesController2.default, ['DeviceManager']); + container.bindClass('EventsController', _EventsController2.default, ['EventManager']); + container.bindClass('OauthClientsController', _OauthClientsController2.default, []); + container.bindClass('ProductsController', _ProductsController2.default, []); + container.bindClass('ProvisioningController', _ProvisioningController2.default, ['DeviceManager']); + container.bindClass('UsersController', _UsersController2.default, ['UserRepository']); + container.bindClass('WebhooksController', _WebhooksController2.default, ['WebhookManager']); + + // managers + container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'DeviceServer']); + container.bindClass('EventManager', _EventManager2.default, ['EventPublisher']); + container.bindClass('WebhookManager', _WebhookManager2.default, ['WebhookRepository', 'EventPublisher']); + + // Repositories + container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']); + container.bindClass('UserRepository', _UserFileRepository2.default, ['USERS_DIRECTORY']); + container.bindClass('WebhookRepository', _WebhookFileRepository2.default, ['WEBHOOKS_DIRECTORY']); +}; diff --git a/dist/exports.js b/dist/exports.js new file mode 100644 index 00000000..3bcb0cf9 --- /dev/null +++ b/dist/exports.js @@ -0,0 +1,29 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +exports.settings = exports.logger = exports.defaultBindings = exports.createApp = undefined; + +const _logger = require('./lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _app = require('./app'); + +const _app2 = _interopRequireDefault(_app); + +const _defaultBindings = require('./defaultBindings'); + +const _defaultBindings2 = _interopRequireDefault(_defaultBindings); + +const _settings = require('./settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.createApp = _app2.default; +exports.defaultBindings = _defaultBindings2.default; +exports.logger = _logger2.default; +exports.settings = _settings2.default; diff --git a/dist/lib/HttpError.js b/dist/lib/HttpError.js new file mode 100644 index 00000000..949d149a --- /dev/null +++ b/dist/lib/HttpError.js @@ -0,0 +1,45 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const HttpError = (function (_Error) { + (0, _inherits3.default)(HttpError, _Error); + + function HttpError(error) { + const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + (0, _classCallCheck3.default)(this, HttpError); + + const _this = (0, _possibleConstructorReturn3.default)(this, (HttpError.__proto__ || (0, _getPrototypeOf2.default)(HttpError)).call(this, error.message || error)); + + if (typeof error.status === 'number') { + _this.status = error.status; + } else { + _this.status = status; + } + return _this; + } + + return HttpError; +}(Error)); + +exports.default = HttpError; diff --git a/dist/lib/PasswordHasher.js b/dist/lib/PasswordHasher.js new file mode 100644 index 00000000..d2cbd8e9 --- /dev/null +++ b/dist/lib/PasswordHasher.js @@ -0,0 +1,86 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _crypto = require('crypto'); + +const _crypto2 = _interopRequireDefault(_crypto); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const HASH_DIGEST = 'sha1'; /** + * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can download the source here: https://github.com/spark/spark-server + * + * + * + */ + +const HASH_ITERATIONS = 30000; +const KEY_LENGTH = 64; + +const PasswordHasher = (function () { + function PasswordHasher() { + (0, _classCallCheck3.default)(this, PasswordHasher); + } + + (0, _createClass3.default)(PasswordHasher, null, [{ + key: 'generateSalt', + value: function generateSalt() { + const size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 64; + + return new _promise2.default((resolve, reject) => { + _crypto2.default.randomBytes(size, (error, buffer) => { + if (error) { + reject(error); + return; + } + resolve(buffer.toString('base64')); + }); + }); + }, + }, { + key: 'hash', + value: function hash(password, salt) { + return new _promise2.default((resolve, reject) => { + _crypto2.default.pbkdf2(password, salt, HASH_ITERATIONS, KEY_LENGTH, HASH_DIGEST, (error, key) => { + if (error) { + reject(error); + return; + } + resolve(key.toString('base64')); + }); + }); + }, + }]); + return PasswordHasher; +}()); + +exports.default = PasswordHasher; diff --git a/dist/lib/deviceToAPI.js b/dist/lib/deviceToAPI.js new file mode 100644 index 00000000..f9f6c09e --- /dev/null +++ b/dist/lib/deviceToAPI.js @@ -0,0 +1,27 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +const deviceToAPI = function deviceToAPI(device, result) { + return { + cellular: device.isCellular, + connected: device.connected, + current_build_target: device.currentBuildTarget, + functions: device.functions, + id: device.deviceID, + imei: device.imei, + last_app: device.lastFlashedAppName, + last_heard: device.lastHeard, + last_iccid: device.last_iccid, + last_ip_address: device.ip, + name: device.name, + platform_id: device.particleProductId, + product_id: device.particleProductId, + return_value: result, + status: 'normal', + variables: device.variables, + }; +}; + +exports.default = deviceToAPI; diff --git a/dist/lib/eventToApi.js b/dist/lib/eventToApi.js new file mode 100644 index 00000000..80a3b835 --- /dev/null +++ b/dist/lib/eventToApi.js @@ -0,0 +1,15 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +const eventToApi = function eventToApi(event) { + return { + coreid: event.deviceID || null, + data: event.data || null, + published_at: event.publishedAt, + ttl: event.ttl, + }; +}; + +exports.default = eventToApi; diff --git a/dist/lib/logger.js b/dist/lib/logger.js new file mode 100644 index 00000000..8d78a751 --- /dev/null +++ b/dist/lib/logger.js @@ -0,0 +1,63 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +* +* +* +*/ + +const Logger = (function () { + function Logger() { + (0, _classCallCheck3.default)(this, Logger); + } + + (0, _createClass3.default)(Logger, null, [{ + key: 'log', + value: function log() { + let _console; + + // eslint-disable-next-line prefer-rest-params + (_console = console).log.apply(_console, arguments); + }, + }, { + key: 'error', + value: function error() { + let _console2; + + // eslint-disable-next-line prefer-rest-params + (_console2 = console).error.apply(_console2, arguments); + }, + }]); + return Logger; +}()); + +exports.default = Logger; diff --git a/dist/main.js b/dist/main.js new file mode 100644 index 00000000..28a70416 --- /dev/null +++ b/dist/main.js @@ -0,0 +1,76 @@ + + +const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); + +const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); + +const _entries = require('babel-runtime/core-js/object/entries'); + +const _entries2 = _interopRequireDefault(_entries); + +const _constitute = require('constitute'); + +const _os = require('os'); + +const _os2 = _interopRequireDefault(_os); + +const _arrayFlatten = require('array-flatten'); + +const _arrayFlatten2 = _interopRequireDefault(_arrayFlatten); + +const _logger = require('./lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _app = require('./app'); + +const _app2 = _interopRequireDefault(_app); + +const _defaultBindings = require('./defaultBindings'); + +const _defaultBindings2 = _interopRequireDefault(_defaultBindings); + +const _settings = require('./settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const NODE_PORT = process.env.NODE_PORT || 8080; + +process.on('uncaughtException', (exception) => { + _logger2.default.error('uncaughtException', { message: exception.message, stack: exception.stack }); // logging with MetaData + process.exit(1); // exit with failure +}); + +/* This is the container used app-wide for dependency injection. If you want to + * override any of the implementations, create your module with the new + * implementation and use: + * + * container.bindAlias(DefaultImplementation, MyNewImplementation); + * + * You can also set a new value + * container.bindAlias(DefaultValue, 12345); + * + * See https://github.com/justmoon/constitute for more info + */ +const container = new _constitute.Container(); +(0, _defaultBindings2.default)(container); + +const deviceServer = container.constitute('DeviceServer'); +deviceServer.start(); + +const app = (0, _app2.default)(container, _settings2.default); + +app.listen(NODE_PORT, () => console.log(`express server started on port ${NODE_PORT}`)); + +const addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map( +// eslint-disable-next-line no-unused-vars +(_ref) => { + let _ref2 = (0, _slicedToArray3.default)(_ref, 2), + name = _ref2[0], + nic = _ref2[1]; + + return nic.filter(address => address.family === 'IPv4' && address.address !== '127.0.0.1').map(address => address.address); +})); +addresses.forEach(address => console.log(`Your device server IP address is: ${address}`)); diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js new file mode 100644 index 00000000..bc9a550a --- /dev/null +++ b/dist/managers/DeviceManager.js @@ -0,0 +1,652 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); + +const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _ursa = require('ursa'); + +const _ursa2 = _interopRequireDefault(_ursa); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer) { + const _this = this; + + (0, _classCallCheck3.default)(this, DeviceManager); + + this.claimDevice = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) { + let deviceAttributes, + attributesToSave; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._deviceAttributeRepository.getById(deviceID); + + case 2: + deviceAttributes = _context.sent; + + if (deviceAttributes) { + _context.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + if (!(deviceAttributes.ownerID && deviceAttributes.ownerID !== userID)) { + _context.next = 7; + break; + } + + throw new _HttpError2.default('The device belongs to someone else.'); + + case 7: + attributesToSave = (0, _extends3.default)({}, deviceAttributes, { + ownerID: userID, + }); + _context.next = 10; + return _this._deviceAttributeRepository.update(attributesToSave); + + case 10: + return _context.abrupt('return', _context.sent); + + case 11: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x, _x2) { + return _ref.apply(this, arguments); + }; + }()); + + this.unclaimDevice = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID, userID) { + let deviceAttributes, + attributesToSave; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._deviceAttributeRepository.getById(deviceID, userID); + + case 2: + deviceAttributes = _context2.sent; + + if (deviceAttributes) { + _context2.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + attributesToSave = (0, _extends3.default)({}, deviceAttributes, { + ownerID: null, + }); + _context2.next = 8; + return _this._deviceAttributeRepository.update(attributesToSave); + + case 8: + return _context2.abrupt('return', _context2.sent); + + case 9: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x3, _x4) { + return _ref2.apply(this, arguments); + }; + }()); + + this.getByID = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, userID) { + let attributes, + device; + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _this._deviceAttributeRepository.getById(deviceID, userID); + + case 2: + attributes = _context3.sent; + + if (attributes) { + _context3.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + device = _this._deviceServer.getDevice(attributes.deviceID); + return _context3.abrupt('return', (0, _extends3.default)({}, attributes, { + connected: device && device.ping().connected || false, + lastFlashedAppName: null, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, + })); + + case 7: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x5, _x6) { + return _ref3.apply(this, arguments); + }; + }()); + + this.getDetailsByID = (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, userID) { + let device, + _ref5, + _ref6, + attributes, + description; + + return _regenerator2.default.wrap((_context4) => { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + device = _this._deviceServer.getDevice(deviceID); + _context4.next = 3; + return _promise2.default.all([_this._deviceAttributeRepository.getById(deviceID, userID), device && device.getDescription()]); + + case 3: + _ref5 = _context4.sent; + _ref6 = (0, _slicedToArray3.default)(_ref5, 2); + attributes = _ref6[0]; + description = _ref6[1]; + + if (attributes) { + _context4.next = 9; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 9: + return _context4.abrupt('return', (0, _extends3.default)({}, attributes, { + connected: device && device.ping().connected || false, + functions: description ? description.state.f : null, + lastFlashedAppName: null, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, + variables: description ? description.state.v : null, + })); + + case 10: + case 'end': + return _context4.stop(); + } + } + }, _callee4, _this); + })); + + return function (_x7, _x8) { + return _ref4.apply(this, arguments); + }; + }()); + + this.getAll = (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(userID) { + let devicesAttributes, + devicePromises; + return _regenerator2.default.wrap((_context6) => { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + _context6.next = 2; + return _this._deviceAttributeRepository.getAll(userID); + + case 2: + devicesAttributes = _context6.sent; + devicePromises = devicesAttributes.map(function () { + const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) { + let device; + return _regenerator2.default.wrap((_context5) => { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + device = _this._deviceServer.getDevice(attributes.deviceID); + return _context5.abrupt('return', (0, _extends3.default)({}, attributes, { + connected: device && device.ping().connected || false, + lastFlashedAppName: null, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, + })); + + case 2: + case 'end': + return _context5.stop(); + } + } + }, _callee5, _this); + })); + + return function (_x10) { + return _ref8.apply(this, arguments); + }; + }()); + return _context6.abrupt('return', _promise2.default.all(devicePromises)); + + case 5: + case 'end': + return _context6.stop(); + } + } + }, _callee6, _this); + })); + + return function (_x9) { + return _ref7.apply(this, arguments); + }; + }()); + + this.callFunction = (function () { + const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, userID, functionName, functionArguments) { + let doesUserHaveAccess, + device; + return _regenerator2.default.wrap((_context7) => { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + _context7.next = 2; + return _this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + doesUserHaveAccess = _context7.sent; + + if (doesUserHaveAccess) { + _context7.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context7.next = 8; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 8: + _context7.next = 10; + return device.callFunction(functionName, functionArguments); + + case 10: + return _context7.abrupt('return', _context7.sent); + + case 11: + case 'end': + return _context7.stop(); + } + } + }, _callee7, _this); + })); + + return function (_x11, _x12, _x13, _x14) { + return _ref9.apply(this, arguments); + }; + }()); + + this.getVariableValue = (function () { + const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, userID, varName) { + let doesUserHaveAccess, + device; + return _regenerator2.default.wrap((_context8) => { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + _context8.next = 2; + return _this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + doesUserHaveAccess = _context8.sent; + + if (doesUserHaveAccess) { + _context8.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context8.next = 8; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 8: + _context8.next = 10; + return device.getVariableValue(varName); + + case 10: + return _context8.abrupt('return', _context8.sent); + + case 11: + case 'end': + return _context8.stop(); + } + } + }, _callee8, _this); + })); + + return function (_x15, _x16, _x17) { + return _ref10.apply(this, arguments); + }; + }()); + + this.flashBinary = (function () { + const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) { + let device; + return _regenerator2.default.wrap((_context9) => { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context9.next = 3; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 3: + _context9.next = 5; + return device.flash(file.buffer); + + case 5: + return _context9.abrupt('return', _context9.sent); + + case 6: + case 'end': + return _context9.stop(); + } + } + }, _callee9, _this); + })); + + return function (_x18, _x19) { + return _ref11.apply(this, arguments); + }; + }()); + + this.flashKnownApp = (function () { + const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, userID, appName) { + let knownFirmware, + device; + return _regenerator2.default.wrap((_context10) => { + while (1) { + switch (_context10.prev = _context10.next) { + case 0: + _context10.next = 2; + return !_this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + if (!_context10.sent) { + _context10.next = 4; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 4: + knownFirmware = _this._deviceFirmwareRepository.getByName(appName); + + if (knownFirmware) { + _context10.next = 7; + break; + } + + throw new _HttpError2.default(`No firmware ${appName} found`, 404); + + case 7: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context10.next = 10; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 10: + _context10.next = 12; + return device.flash(knownFirmware); + + case 12: + return _context10.abrupt('return', _context10.sent); + + case 13: + case 'end': + return _context10.stop(); + } + } + }, _callee10, _this); + })); + + return function (_x20, _x21, _x22) { + return _ref12.apply(this, arguments); + }; + }()); + + this.provision = (function () { + const _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey) { + let createdKey, + existingAttributes, + attributes; + return _regenerator2.default.wrap((_context11) => { + while (1) { + switch (_context11.prev = _context11.next) { + case 0: + _context11.prev = 0; + createdKey = _ursa2.default.createPublicKey(publicKey); + + if (_ursa2.default.isPublicKey(createdKey)) { + _context11.next = 4; + break; + } + + throw new _HttpError2.default('Not a public key'); + + case 4: + _context11.next = 9; + break; + + case 6: + _context11.prev = 6; + _context11.t0 = _context11.catch(0); + throw new _HttpError2.default(`Key error ${_context11.t0}`); + + case 9: + _context11.next = 11; + return _this._deviceKeyRepository.update(deviceID, publicKey); + + case 11: + _context11.next = 13; + return _this._deviceAttributeRepository.getById(deviceID); + + case 13: + existingAttributes = _context11.sent; + attributes = (0, _extends3.default)({ + deviceID, + }, existingAttributes, { + ownerID: userID, + registrar: userID, + timestamp: new Date(), + }); + _context11.next = 17; + return _this._deviceAttributeRepository.update(attributes); + + case 17: + _context11.next = 19; + return _this.getByID(deviceID, userID); + + case 19: + return _context11.abrupt('return', _context11.sent); + + case 20: + case 'end': + return _context11.stop(); + } + } + }, _callee11, _this, [[0, 6]]); + })); + + return function (_x23, _x24, _x25) { + return _ref13.apply(this, arguments); + }; + }()); + + this.raiseYourHand = (function () { + const _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, userID, shouldShowSignal) { + let device; + return _regenerator2.default.wrap((_context12) => { + while (1) { + switch (_context12.prev = _context12.next) { + case 0: + _context12.next = 2; + return !_this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + if (!_context12.sent) { + _context12.next = 4; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 4: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context12.next = 7; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 7: + _context12.next = 9; + return device.raiseYourHand(shouldShowSignal); + + case 9: + return _context12.abrupt('return', _context12.sent); + + case 10: + case 'end': + return _context12.stop(); + } + } + }, _callee12, _this); + })); + + return function (_x26, _x27, _x28) { + return _ref14.apply(this, arguments); + }; + }()); + + this.renameDevice = (function () { + const _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, userID, name) { + let attributes, + attributesToSave; + return _regenerator2.default.wrap((_context13) => { + while (1) { + switch (_context13.prev = _context13.next) { + case 0: + _context13.next = 2; + return _this._deviceAttributeRepository.getById(deviceID, userID); + + case 2: + attributes = _context13.sent; + + if (attributes) { + _context13.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + attributesToSave = (0, _extends3.default)({}, attributes, { + name, + }); + _context13.next = 8; + return _this._deviceAttributeRepository.update(attributesToSave); + + case 8: + return _context13.abrupt('return', _context13.sent); + + case 9: + case 'end': + return _context13.stop(); + } + } + }, _callee13, _this); + })); + + return function (_x29, _x30, _x31) { + return _ref15.apply(this, arguments); + }; + }()); + + this._deviceAttributeRepository = deviceAttributeRepository; + this._deviceFirmwareRepository = deviceFirmwareRepository; + this._deviceKeyRepository = deviceKeyRepository; + this._deviceServer = deviceServer; +}; + +exports.default = DeviceManager; diff --git a/dist/managers/EventManager.js b/dist/managers/EventManager.js new file mode 100644 index 00000000..44c33acb --- /dev/null +++ b/dist/managers/EventManager.js @@ -0,0 +1,33 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const EventManager = function EventManager(eventPublisher) { + const _this = this; + + (0, _classCallCheck3.default)(this, EventManager); + + this.subscribe = function (eventNamePrefix, eventHandler, filterOptions) { + return _this._eventPublisher.subscribe(eventNamePrefix, eventHandler, filterOptions); + }; + + this.unsubscribe = function (subscriptionID) { + return _this._eventPublisher.unsubscribe(subscriptionID); + }; + + this.publish = function (eventData) { + return _this._eventPublisher.publish(eventData); + }; + + this._eventPublisher = eventPublisher; +}; + +exports.default = EventManager; diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js new file mode 100644 index 00000000..1766ca29 --- /dev/null +++ b/dist/managers/FirmwareCompilationManager.js @@ -0,0 +1,263 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _stringify = require('babel-runtime/core-js/json/stringify'); + +const _stringify2 = _interopRequireDefault(_stringify); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _map = require('babel-runtime/core-js/map'); + +const _map2 = _interopRequireDefault(_map); + +const _crypto = require('crypto'); + +const _crypto2 = _interopRequireDefault(_crypto); + +const _fs = require('fs'); + +const _fs2 = _interopRequireDefault(_fs); + +const _path = require('path'); + +const _path2 = _interopRequireDefault(_path); + +const _mkdirp = require('mkdirp'); + +const _mkdirp2 = _interopRequireDefault(_mkdirp); + +const _rmfr = require('rmfr'); + +const _rmfr2 = _interopRequireDefault(_rmfr); + +const _child_process = require('child_process'); + +const _sparkProtocol = require('spark-protocol'); + +const _settings = require('../settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const IS_COMPILATION_ENABLED = _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); + +const USER_APP_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications'); +const BIN_PATH = _path2.default.join(_settings2.default.BUILD_DIRECTORY, 'bin'); +const MAKE_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'main'); + +const FILE_NAME_BY_KEY = new _map2.default(); + +const getKey = function getKey() { + return _crypto2.default.randomBytes(24).toString('hex').substring(0, 24); +}; + +const getUniqueKey = function getUniqueKey() { + let key = getKey(); + while (FILE_NAME_BY_KEY.has(key)) { + key = getKey(); + } + return key; +}; + +const FirmwareCompilationManager = function FirmwareCompilationManager() { + (0, _classCallCheck3.default)(this, FirmwareCompilationManager); +}; + +FirmwareCompilationManager.firmwareDirectoryExists = function () { + return _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); +}; + +FirmwareCompilationManager.getBinaryForID = function (id) { + if (!FirmwareCompilationManager.firmwareDirectoryExists()) { + return null; + } + + const binaryPath = _path2.default.join(BIN_PATH, id); + if (!_fs2.default.existsSync(binaryPath)) { + return null; + } + + const binFileName = _fs2.default.readdirSync(binaryPath).find(file => file.endsWith('.bin')); + + if (!binFileName) { + return null; + } + + return _fs2.default.readFileSync(_path2.default.join(binaryPath, binFileName)); +}; + +FirmwareCompilationManager.compileSource = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(platformID, files) { + let platformName, + appFolder, + appPath, + id, + binPath, + makeProcess, + errors, + sizeInfo, + date, + config; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (FirmwareCompilationManager.firmwareDirectoryExists()) { + _context.next = 2; + break; + } + + return _context.abrupt('return', null); + + case 2: + platformName = _sparkProtocol.knownPlatforms[platformID]; + + if (platformName) { + _context.next = 5; + break; + } + + return _context.abrupt('return', null); + + case 5: + + platformName = platformName.toLowerCase(); + appFolder = (`${platformName}_firmware_${new Date().getTime()}`).toLowerCase(); + appPath = _path2.default.join(USER_APP_PATH, appFolder); + + _mkdirp2.default.sync(appPath); + + files.forEach((file) => { + const fileName = file.originalname; + const fileExtension = _path2.default.extname(fileName); + let iterator = 0; + let combinedPath = _path2.default.join(appPath, fileName); + + while (_fs2.default.existsSync(combinedPath)) { + combinedPath = _path2.default.join(appPath, `${_path2.default.basename(fileName, fileExtension)}_${iterator++}${fileExtension}`); + } + + _fs2.default.writeFileSync(combinedPath, file.buffer); + }); + + id = getUniqueKey(); + binPath = _path2.default.join(BIN_PATH, id); + makeProcess = (0, _child_process.spawn)('make', [`APP=${appFolder}`, `PLATFORM_ID=${platformID}`, `TARGET_DIR=${_path2.default.relative(MAKE_PATH, binPath).replace(/\\/g, '/')}`], { cwd: MAKE_PATH }); + errors = []; + + makeProcess.stderr.on('data', (data) => { + console.log(`${data}`); + errors.push(`${data}`); + }); + + sizeInfo = 'not implemented'; + + makeProcess.stdout.on('data', (data) => { + const output = `${data}`; + + if (output.includes('text\t')) { + sizeInfo = output; + } + }); + + _context.next = 19; + return new _promise2.default((resolve) => { + makeProcess.on('exit', () => resolve()); + }); + + case 19: + date = new Date(); + + date.setDate(date.getDate() + 1); + config = { + binary_id: id, + errors, + // expire in one day + expires_at: date, + + // TODO: this variable has a bunch of extra crap including file names. + // we should filter out the string to only show the file sizes + sizeInfo, + }; + + + FirmwareCompilationManager.addFirmwareCleanupTask(appPath, config); + + return _context.abrupt('return', config); + + case 24: + case 'end': + return _context.stop(); + } + } + }, _callee, undefined); + })); + + return function (_x, _x2) { + return _ref.apply(this, arguments); + }; +}()); + +FirmwareCompilationManager.addFirmwareCleanupTask = function (appFolderPath, config) { + const configPath = _path2.default.join(appFolderPath, 'config.json'); + if (!_fs2.default.existsSync(configPath)) { + _fs2.default.writeFileSync(configPath, (0, _stringify2.default)(config)); + } + const currentDate = new Date(); + const difference = new Date(config.expires_at).getTime() - currentDate.getTime(); + setTimeout(() => (0, _rmfr2.default)(appFolderPath), difference); +}; + +if (IS_COMPILATION_ENABLED) { + // Delete all expired binaries or queue them up to eventually be deleted. + if (!_fs2.default.existsSync(_settings2.default.BUILD_DIRECTORY)) { + _mkdirp2.default.sync(_settings2.default.BUILD_DIRECTORY); + } + if (!_fs2.default.existsSync(BIN_PATH)) { + _mkdirp2.default.sync(BIN_PATH); + } + + _fs2.default.readdirSync(USER_APP_PATH).forEach((file) => { + const appFolder = _path2.default.join(USER_APP_PATH, file); + const configPath = _path2.default.join(appFolder, 'config.json'); + if (!_fs2.default.existsSync(configPath)) { + return; + } + + const configString = _fs2.default.readFileSync(configPath, 'utf8'); + if (!configString) { + return; + } + const config = JSON.parse(configString); + if (config.expires_at < new Date()) { + // TODO - clean up artifacts in the firmware folder. Every binary will have + // files in firmare/build/target/user & firmware/build/target/user-part + // It might make the most sense to just create a custom MAKE file to do this + (0, _rmfr2.default)(configPath); + (0, _rmfr2.default)(_path2.default.join(BIN_PATH, config.binary_id)); + } else { + FirmwareCompilationManager.addFirmwareCleanupTask(appFolder, config); + } + }); +} + +exports.default = FirmwareCompilationManager; diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js new file mode 100644 index 00000000..b93dbcf0 --- /dev/null +++ b/dist/managers/WebhookManager.js @@ -0,0 +1,533 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _typeof2 = require('babel-runtime/helpers/typeof'); + +const _typeof3 = _interopRequireDefault(_typeof2); + +const _stringify = require('babel-runtime/core-js/json/stringify'); + +const _stringify2 = _interopRequireDefault(_stringify); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _map = require('babel-runtime/core-js/map'); + +const _map2 = _interopRequireDefault(_map); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _hogan = require('hogan.js'); + +const _hogan2 = _interopRequireDefault(_hogan); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _logger = require('../lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _nullthrows = require('nullthrows'); + +const _nullthrows2 = _interopRequireDefault(_nullthrows); + +const _request = require('request'); + +const _request2 = _interopRequireDefault(_request); + +const _throttle = require('lodash/throttle'); + +const _throttle2 = _interopRequireDefault(_throttle); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const parseEventData = function parseEventData(event) { + try { + if (event.data) { + return JSON.parse(event.data); + } + return {}; + } catch (error) { + return {}; + } +}; + +const splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { + const chunks = []; + let ii = 0; + while (ii < buffer.length) { + chunks.push(buffer.slice(ii, ii += chunkSize)); + } + + return chunks; +}; + +const MAX_WEBHOOK_ERRORS_COUNT = 10; +const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; +const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; +const MAX_RESPONSE_MESSAGE_SIZE = 100000; + +const WebhookManager = function WebhookManager(webhookRepository, eventPublisher) { + const _this = this; + + (0, _classCallCheck3.default)(this, WebhookManager); + this._subscriptionIDsByWebhookID = new _map2.default(); + this._errorsCountByWebhookID = new _map2.default(); + + this.create = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) { + let webhook; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._webhookRepository.create(model); + + case 2: + webhook = _context.sent; + + _this._subscribeWebhook(webhook); + return _context.abrupt('return', webhook); + + case 5: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x) { + return _ref.apply(this, arguments); + }; + }()); + + this.deleteByID = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID, userID) { + let webhook; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._webhookRepository.getById(webhookID, userID); + + case 2: + webhook = _context2.sent; + + if (webhook) { + _context2.next = 5; + break; + } + + throw new _HttpError2.default('no webhook found', 404); + + case 5: + _context2.next = 7; + return _this._webhookRepository.deleteById(webhookID); + + case 7: + _this._unsubscribeWebhookByID(webhookID); + + case 8: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x2, _x3) { + return _ref2.apply(this, arguments); + }; + }()); + + this.getAll = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID) { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _this._webhookRepository.getAll(userID); + + case 2: + return _context3.abrupt('return', _context3.sent); + + case 3: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x4) { + return _ref3.apply(this, arguments); + }; + }()); + + this.getByID = (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID, userID) { + let webhook; + return _regenerator2.default.wrap((_context4) => { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return _this._webhookRepository.getById(webhookID, userID); + + case 2: + webhook = _context4.sent; + + if (webhook) { + _context4.next = 5; + break; + } + + throw new _HttpError2.default('no webhook found', 404); + + case 5: + return _context4.abrupt('return', webhook); + + case 6: + case 'end': + return _context4.stop(); + } + } + }, _callee4, _this); + })); + + return function (_x5, _x6) { + return _ref4.apply(this, arguments); + }; + }()); + + this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { + let allWebhooks; + return _regenerator2.default.wrap((_context5) => { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + _context5.next = 2; + return _this._webhookRepository.getAll(); + + case 2: + allWebhooks = _context5.sent; + + allWebhooks.forEach(webhook => _this._subscribeWebhook(webhook)); + + case 4: + case 'end': + return _context5.stop(); + } + } + }, _callee5, _this); + })); + + this._subscribeWebhook = function (webhook) { + const subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), { + deviceID: webhook.deviceID, + mydevices: webhook.mydevices, + userID: webhook.ownerID, + }); + _this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID); + }; + + this._unsubscribeWebhookByID = function (webhookID) { + const subscriptionID = _this._subscriptionIDsByWebhookID.get(webhookID); + if (!subscriptionID) { + return; + } + + _this._eventPublisher.unsubscribe(subscriptionID); + _this._subscriptionIDsByWebhookID.delete(webhookID); + }; + + this._onNewWebhookEvent = function (webhook) { + return function (event) { + try { + const webhookErrorCount = _this._errorsCountByWebhookID.get(webhook.id) || 0; + + if (webhookErrorCount < MAX_WEBHOOK_ERRORS_COUNT) { + _this.runWebhook(webhook, event); + return; + } + + _this._eventPublisher.publish({ + data: 'Too many errors, webhook disabled', + isPublic: false, + name: _this._compileErrorResponseTopic(webhook, event), + userID: event.userID, + }); + + _this.runWebhookThrottled(webhook, event); + } catch (error) { + _logger2.default.error(`webhookError: ${error}`); + } + }; + }; + + this.runWebhook = (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(webhook, event) { + let _ret; + + return _regenerator2.default.wrap((_context7) => { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + _context7.prev = 0; + return _context7.delegateYield(_regenerator2.default.mark(function _callee6() { + let webhookVariablesObject, + requestJson, + requestFormData, + requestUrl, + requestQuery, + responseTopic, + isJsonRequest, + requestOptions, + responseBody, + isResponseBodyAnObject, + responseTemplate, + responseEventData, + chunks; + return _regenerator2.default.wrap((_context6) => { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + webhookVariablesObject = _this._getEventVariables(event); + requestJson = _this._compileJsonTemplate(webhook.json, webhookVariablesObject); + requestFormData = _this._compileJsonTemplate(webhook.form, webhookVariablesObject); + requestUrl = _this._compileTemplate(webhook.url, webhookVariablesObject); + requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject); + responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject); + isJsonRequest = !!requestJson; + requestOptions = { + auth: webhook.auth, + body: isJsonRequest ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined, + form: !isJsonRequest ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, + headers: webhook.headers, + json: true, + method: webhook.requestType, + qs: requestQuery, + strictSSL: webhook.rejectUnauthorized, + url: (0, _nullthrows2.default)(requestUrl), + }; + _context6.next = 10; + return _this._callWebhook(webhook, event, requestOptions); + + case 10: + responseBody = _context6.sent; + + if (responseBody) { + _context6.next = 13; + break; + } + + return _context6.abrupt('return', { + v: void 0, + }); + + case 13: + isResponseBodyAnObject = responseBody === Object(responseBody); + responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(responseBody); + responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(responseBody) : responseBody); + chunks = splitBufferIntoChunks(Buffer.from(responseEventData).slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE); + + + chunks.forEach((chunk, index) => { + const responseEventName = responseTopic && `${responseTopic}/${index}` || `hook-response/${event.name}/${index}`; + + _this._eventPublisher.publish({ + data: chunk, + isPublic: false, + name: responseEventName, + userID: event.userID, + }); + }); + + case 18: + case 'end': + return _context6.stop(); + } + } + }, _callee6, _this); + })(), 't0', 2); + + case 2: + _ret = _context7.t0; + + if (!((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === 'object')) { + _context7.next = 5; + break; + } + + return _context7.abrupt('return', _ret.v); + + case 5: + _context7.next = 10; + break; + + case 7: + _context7.prev = 7; + _context7.t1 = _context7.catch(0); + + _logger2.default.error(`webhookError: ${_context7.t1}`); + + case 10: + case 'end': + return _context7.stop(); + } + } + }, _callee7, _this, [[0, 7]]); + })); + + return function (_x7, _x8) { + return _ref6.apply(this, arguments); + }; + }()); + + this.runWebhookThrottled = (0, _throttle2.default)(this.runWebhook, WEBHOOK_THROTTLE_TIME, { leading: false, trailing: true }); + + this._callWebhook = function (webhook, event, requestOptions) { + return new _promise2.default((resolve, reject) => (0, _request2.default)(requestOptions, (error, response, responseBody) => { + const onResponseError = function onResponseError(errorMessage) { + _this._incrementWebhookErrorCounter(webhook.id); + + _this._eventPublisher.publish({ + data: errorMessage, + isPublic: false, + name: _this._compileErrorResponseTopic(webhook, event), + userID: event.userID, + }); + + reject(new Error(errorMessage)); + }; + + if (error) { + onResponseError(error.message); + return; + } + if (response.statusCode >= 400) { + onResponseError(response.statusMessage); + return; + } + + _this._resetWebhookErrorCounter(webhook.id); + + _this._eventPublisher.publish({ + isPublic: false, + name: `hook-sent/${event.name}`, + userID: event.userID, + }); + + resolve(responseBody); + })); + }; + + this._getEventVariables = function (event) { + const defaultWebhookVariables = { + PARTICLE_DEVICE_ID: event.deviceID, + PARTICLE_EVENT_NAME: event.name, + PARTICLE_EVENT_VALUE: event.data, + PARTICLE_PUBLISHED_AT: event.publishedAt, + // old event names, added for compatibility + SPARK_CORE_ID: event.deviceID, + SPARK_EVENT_NAME: event.name, + SPARK_EVENT_VALUE: event.data, + SPARK_PUBLISHED_AT: event.publishedAt, + }; + + const eventDataVariables = parseEventData(event); + + return (0, _extends3.default)({}, defaultWebhookVariables, eventDataVariables); + }; + + this._getRequestData = function (customData, event, noDefaults) { + const defaultEventData = { + coreid: event.deviceID, + data: event.data, + event: event.name, + published_at: event.publishedAt, + }; + + return noDefaults ? customData : (0, _extends3.default)({}, defaultEventData, customData || {}); + }; + + this._compileTemplate = function (template, variables) { + return template && _hogan2.default.compile(template).render(variables); + }; + + this._compileJsonTemplate = function (template, variables) { + if (!template) { + return undefined; + } + + const compiledTemplate = _this._compileTemplate((0, _stringify2.default)(template), variables); + if (!compiledTemplate) { + return undefined; + } + + return JSON.parse(compiledTemplate); + }; + + this._compileErrorResponseTopic = function (webhook, event) { + const variables = _this._getEventVariables(event); + return _this._compileTemplate(webhook.errorResponseTopic, variables) || `hook-error/${event.name}`; + }; + + this._incrementWebhookErrorCounter = function (webhookID) { + const errorsCount = _this._errorsCountByWebhookID.get(webhookID) || 0; + _this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); + }; + + this._resetWebhookErrorCounter = function (webhookID) { + _this._errorsCountByWebhookID.set(webhookID, 0); + }; + + this._webhookRepository = webhookRepository; + this._eventPublisher = eventPublisher; + + (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { + return _regenerator2.default.wrap((_context8) => { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + _context8.next = 2; + return _this._init(); + + case 2: + return _context8.abrupt('return', _context8.sent); + + case 3: + case 'end': + return _context8.stop(); + } + } + }, _callee8, _this); + }))(); +}; + +exports.default = WebhookManager; diff --git a/dist/repository/DeviceFirmwareFileRepository.js b/dist/repository/DeviceFirmwareFileRepository.js new file mode 100644 index 00000000..7255f1b7 --- /dev/null +++ b/dist/repository/DeviceFirmwareFileRepository.js @@ -0,0 +1,70 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +let _dec, + _desc, + _value, + _class; + +const _sparkProtocol = require('spark-protocol'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], { promise: false }), (_class = (function () { + function DeviceFirmwareFileRepository(path) { + (0, _classCallCheck3.default)(this, DeviceFirmwareFileRepository); + + this._fileManager = new _sparkProtocol.FileManager(path, false); + } + + (0, _createClass3.default)(DeviceFirmwareFileRepository, [{ + key: 'getByName', + value: function getByName(appName) { + return this._fileManager.getFileBuffer(`${appName}.bin`); + }, + }]); + return DeviceFirmwareFileRepository; +}()), (_applyDecoratedDescriptor(_class.prototype, 'getByName', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByName'), _class.prototype)), _class)); +exports.default = DeviceFirmwareFileRepository; diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js new file mode 100644 index 00000000..33353a83 --- /dev/null +++ b/dist/repository/UserFileRepository.js @@ -0,0 +1,535 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); + +const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _desc, + _value, + _class; + +const _uuid = require('uuid'); + +const _uuid2 = _interopRequireDefault(_uuid); + +const _sparkProtocol = require('spark-protocol'); + +const _PasswordHasher = require('../lib/PasswordHasher'); + +const _PasswordHasher2 = _interopRequireDefault(_PasswordHasher); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(['id']), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = (function () { + function UserFileRepository(path) { + const _this = this; + + (0, _classCallCheck3.default)(this, UserFileRepository); + + this.createWithCredentials = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + let username, + password, + salt, + passwordHash, + modelToSave; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + username = userCredentials.username, password = userCredentials.password; + _context.next = 3; + return _PasswordHasher2.default.generateSalt(); + + case 3: + salt = _context.sent; + _context.next = 6; + return _PasswordHasher2.default.hash(password, salt); + + case 6: + passwordHash = _context.sent; + modelToSave = { + accessTokens: [], + passwordHash, + salt, + username, + }; + _context.next = 10; + return _this.create(modelToSave); + + case 10: + return _context.abrupt('return', _context.sent); + + case 11: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x) { + return _ref.apply(this, arguments); + }; + }()); + + this.validateLogin = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + let user, + hash; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.prev = 0; + _context2.next = 3; + return _this.getByUsername(username); + + case 3: + user = _context2.sent; + + if (user) { + _context2.next = 6; + break; + } + + throw new Error('User doesn\'t exist'); + + case 6: + _context2.next = 8; + return _PasswordHasher2.default.hash(password, user.salt); + + case 8: + hash = _context2.sent; + + if (!(hash !== user.passwordHash)) { + _context2.next = 11; + break; + } + + throw new Error('Wrong password'); + + case 11: + return _context2.abrupt('return', user); + + case 14: + _context2.prev = 14; + _context2.t0 = _context2.catch(0); + throw _context2.t0; + + case 17: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this, [[0, 14]]); + })); + + return function (_x2, _x3) { + return _ref2.apply(this, arguments); + }; + }()); + + this.getByAccessToken = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(accessToken) { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _this.getAll(); + + case 2: + _context3.t0 = function (user) { + return user.accessTokens.some(tokenObject => tokenObject.accessToken === accessToken); + }; + + return _context3.abrupt('return', _context3.sent.find(_context3.t0)); + + case 4: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x4) { + return _ref3.apply(this, arguments); + }; + }()); + + this.deleteAccessToken = (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, token) { + let user, + userToSave; + return _regenerator2.default.wrap((_context4) => { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return _this.getById(userID); + + case 2: + user = _context4.sent; + + if (user) { + _context4.next = 5; + break; + } + + throw new Error('User doesn\'t exist'); + + case 5: + userToSave = (0, _extends3.default)({}, user, { + accessTokens: user.accessTokens.filter(tokenObject => tokenObject.accessToken !== token), + }); + _context4.next = 8; + return _this.update(userToSave); + + case 8: + case 'end': + return _context4.stop(); + } + } + }, _callee4, _this); + })); + + return function (_x5, _x6) { + return _ref4.apply(this, arguments); + }; + }()); + + this.saveAccessToken = (function () { + const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) { + let user, + userToSave; + return _regenerator2.default.wrap((_context5) => { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + _context5.next = 2; + return _this.getById(userID); + + case 2: + user = _context5.sent; + + if (user) { + _context5.next = 5; + break; + } + + throw new _HttpError2.default('Could not find user for user ID'); + + case 5: + userToSave = (0, _extends3.default)({}, user, { + accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]), + }); + _context5.next = 8; + return _this.update(userToSave); + + case 8: + return _context5.abrupt('return', _context5.sent); + + case 9: + case 'end': + return _context5.stop(); + } + } + }, _callee5, _this); + })); + + return function (_x7, _x8) { + return _ref5.apply(this, arguments); + }; + }()); + + this._fileManager = new _sparkProtocol.JSONFileManager(path); + } + + (0, _createClass3.default)(UserFileRepository, [{ + key: 'create', + value: (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(user) { + let id, + modelToSave; + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + id = (0, _uuid2.default)(); + + case 1: + _context6.next = 3; + return this._fileManager.hasFile(`${id}.json`); + + case 3: + if (!_context6.sent) { + _context6.next = 7; + break; + } + + id = (0, _uuid2.default)(); + _context6.next = 1; + break; + + case 7: + modelToSave = (0, _extends3.default)({}, user, { + created_at: new Date(), + created_by: null, + id, + }); + + + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + return _context6.abrupt('return', modelToSave); + + case 10: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function create(_x9) { + return _ref6.apply(this, arguments); + } + + return create; + }()), + }, { + key: 'update', + value: (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(model) { + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + this._fileManager.writeFile(`${model.id}.json`, model); + return _context7.abrupt('return', model); + + case 2: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this); + })); + + function update(_x10) { + return _ref7.apply(this, arguments); + } + + return update; + }()), + }, { + key: 'getAll', + value: (function () { + const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { + return _regenerator2.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + return _context8.abrupt('return', this._fileManager.getAllData()); + + case 1: + case 'end': + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function getAll() { + return _ref8.apply(this, arguments); + } + + return getAll; + }()), + }, { + key: 'getById', + value: (function () { + const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) { + return _regenerator2.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + return _context9.abrupt('return', this._fileManager.getFile(`${id}.json`)); + + case 1: + case 'end': + return _context9.stop(); + } + } + }, _callee9, this); + })); + + function getById(_x11) { + return _ref9.apply(this, arguments); + } + + return getById; + }()), + }, { + key: 'getByUsername', + value: (function () { + const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(username) { + return _regenerator2.default.wrap(function _callee10$(_context10) { + while (1) { + switch (_context10.prev = _context10.next) { + case 0: + _context10.next = 2; + return this.getAll(); + + case 2: + _context10.t0 = function (user) { + return user.username === username; + }; + + return _context10.abrupt('return', _context10.sent.find(_context10.t0)); + + case 4: + case 'end': + return _context10.stop(); + } + } + }, _callee10, this); + })); + + function getByUsername(_x12) { + return _ref10.apply(this, arguments); + } + + return getByUsername; + }()), + + // This isn't a good one to memoize as we can't key off user ID and there + // isn't a good way to clear the cache. + + }, { + key: 'deleteById', + value: (function () { + const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) { + return _regenerator2.default.wrap(function _callee11$(_context11) { + while (1) { + switch (_context11.prev = _context11.next) { + case 0: + this._fileManager.deleteFile(`${id}.json`); + + case 1: + case 'end': + return _context11.stop(); + } + } + }, _callee11, this); + })); + + function deleteById(_x13) { + return _ref11.apply(this, arguments); + } + + return deleteById; + }()), + }, { + key: 'isUserNameInUse', + value: (function () { + const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) { + return _regenerator2.default.wrap(function _callee12$(_context12) { + while (1) { + switch (_context12.prev = _context12.next) { + case 0: + _context12.next = 2; + return this.getAll(); + + case 2: + _context12.t0 = function (user) { + return user.username === username; + }; + + return _context12.abrupt('return', _context12.sent.some(_context12.t0)); + + case 4: + case 'end': + return _context12.stop(); + } + } + }, _callee12, this); + })); + + function isUserNameInUse(_x14) { + return _ref12.apply(this, arguments); + } + + return isUserNameInUse; + }()), + }]); + return UserFileRepository; +}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class)); +exports.default = UserFileRepository; diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js new file mode 100644 index 00000000..ed4263e6 --- /dev/null +++ b/dist/repository/WebhookFileRepository.js @@ -0,0 +1,310 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +let _dec, + _dec2, + _dec3, + _dec4, + _desc, + _value, + _class; + +const _uuid = require('uuid'); + +const _uuid2 = _interopRequireDefault(_uuid); + +const _sparkProtocol = require('spark-protocol'); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), (_class = (function () { + function WebhookFileRepository(path) { + const _this = this; + + (0, _classCallCheck3.default)(this, WebhookFileRepository); + + this.getAll = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + const userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let allData; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._getAll(); + + case 2: + allData = _context.sent; + + if (!userID) { + _context.next = 5; + break; + } + + return _context.abrupt('return', allData.filter(webhook => webhook.ownerID === userID)); + + case 5: + return _context.abrupt('return', allData); + + case 6: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function () { + return _ref.apply(this, arguments); + }; + }()); + + this.getById = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) { + const userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + let webhook; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._getByID(id); + + case 2: + webhook = _context2.sent; + + if (!(!webhook || webhook.ownerID !== userID)) { + _context2.next = 5; + break; + } + + return _context2.abrupt('return', null); + + case 5: + return _context2.abrupt('return', webhook); + + case 6: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x2) { + return _ref2.apply(this, arguments); + }; + }()); + + this.update = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x4) { + return _ref3.apply(this, arguments); + }; + }()); + + this._fileManager = new _sparkProtocol.JSONFileManager(path); + } + + (0, _createClass3.default)(WebhookFileRepository, [{ + key: 'create', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) { + let id, + modelToSave; + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + id = (0, _uuid2.default)(); + + case 1: + _context4.next = 3; + return this._fileManager.hasFile(`${id}.json`); + + case 3: + if (!_context4.sent) { + _context4.next = 7; + break; + } + + id = (0, _uuid2.default)(); + _context4.next = 1; + break; + + case 7: + modelToSave = (0, _extends3.default)({}, model, { + created_at: new Date(), + id, + }); + + + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + return _context4.abrupt('return', modelToSave); + + case 10: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function create(_x5) { + return _ref4.apply(this, arguments); + } + + return create; + }()), + }, { + key: 'deleteById', + value: (function () { + const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) { + return _regenerator2.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + this._fileManager.deleteFile(`${id}.json`); + + case 1: + case 'end': + return _context5.stop(); + } + } + }, _callee5, this); + })); + + function deleteById(_x6) { + return _ref5.apply(this, arguments); + } + + return deleteById; + }()), + + // eslint-disable-next-line no-unused-vars + + }, { + key: '_getAll', + value: (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() { + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + return _context6.abrupt('return', this._fileManager.getAllData()); + + case 1: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function _getAll() { + return _ref6.apply(this, arguments); + } + + return _getAll; + }()), + }, { + key: '_getByID', + value: (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) { + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + return _context7.abrupt('return', this._fileManager.getFile(`${id}.json`)); + + case 1: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this); + })); + + function _getByID(_x7) { + return _ref7.apply(this, arguments); + } + + return _getByID; + }()), + }]); + return WebhookFileRepository; +}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class)); +exports.default = WebhookFileRepository; diff --git a/dist/settings.js b/dist/settings.js new file mode 100644 index 00000000..9b53767c --- /dev/null +++ b/dist/settings.js @@ -0,0 +1,51 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _path = require('path'); + +const _path2 = _interopRequireDefault(_path); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* eslint-disable sorting/sort-object-props */ +exports.default = { + BUILD_DIRECTORY: _path2.default.join(__dirname, '../data/build'), + DEVICE_DIRECTORY: _path2.default.join(__dirname, '../data/deviceKeys'), + FIRMWARE_DIRECTORY: _path2.default.join(__dirname, '../data/knownApps'), + FIRMWARE_REPOSITORY_DIRECTORY: _path2.default.join(__dirname, '../../spark-firmware'), + SERVER_KEY_FILENAME: 'default_key.pem', + SERVER_KEYS_DIRECTORY: _path2.default.join(__dirname, '../data'), + USERS_DIRECTORY: _path2.default.join(__dirname, '../data/users'), + WEBHOOKS_DIRECTORY: _path2.default.join(__dirname, '../data/webhooks'), + + ACCESS_TOKEN_LIFETIME: 7776000, // 90 days, + API_TIMEOUT: 30000, // Timeout for API requests. + CRYPTO_SALT: 'aes-128-cbc', + LOG_REQUESTS: true, + LOGIN_ROUTE: '/oauth/token', + + PORT: 5683, + HOST: 'localhost', +}; /** + * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can download the source here: https://github.com/spark/spark-server + * + * + * + */ diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dist/types.js @@ -0,0 +1 @@ + diff --git a/package.json b/package.json index b6c52ac7..4621da23 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "url": "https://github.com/emilyrose" } ], - "main": "./src/exports.js", + "main": "./dist/exports.js", "scripts": { - "build": "babel ./src --out-dir ./build", + "build": "babel ./src --out-dir ./dist", "build:watch": "babel ./src --out-dir ./dist --watch", "build:clean": "rimraf ./build", "lint": "eslint --fix --max-warnings 0 -- .", From c12f1aa2a444be0771f93d14ec389f6f5ca37911 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:43:36 -0800 Subject: [PATCH 325/504] Fixing build... some comments broke node. --- dist/OAuthModel.js | 61 ++-- dist/RouteConfig.js | 145 +++++----- dist/app.js | 32 +-- dist/controllers/Controller.js | 22 +- dist/controllers/DeviceClaimsController.js | 86 +++--- dist/controllers/DevicesController.js | 202 ++++++-------- dist/controllers/EventsController.js | 157 +++++------ dist/controllers/OauthClientsController.js | 102 ++++--- dist/controllers/ProductsController.js | 68 ++--- dist/controllers/ProvisioningController.js | 88 +++--- dist/controllers/UsersController.js | 123 ++++----- dist/controllers/WebhooksController.js | 121 ++++---- dist/controllers/types.js | 2 +- dist/decorators/allowUpload.js | 18 +- dist/decorators/anonymous.js | 8 +- dist/decorators/httpVerb.js | 8 +- dist/decorators/route.js | 8 +- dist/decorators/serverSentEvents.js | 8 +- dist/decorators/types.js | 2 +- dist/defaultBindings.js | 70 ++--- dist/exports.js | 24 +- dist/lib/HttpError.js | 32 +-- dist/lib/PasswordHasher.js | 50 ++-- dist/lib/deviceToAPI.js | 12 +- dist/lib/eventToApi.js | 12 +- dist/lib/logger.js | 34 +-- dist/main.js | 72 ++--- dist/managers/DeviceManager.js | 208 +++++++------- dist/managers/EventManager.js | 16 +- dist/managers/FirmwareCompilationManager.js | 159 ++++++----- dist/managers/WebhookManager.js | 260 +++++++++--------- .../DeviceFirmwareFileRepository.js | 43 ++- dist/repository/UserFileRepository.js | 205 +++++++------- dist/repository/WebhookFileRepository.js | 137 +++++---- dist/settings.js | 16 +- dist/types.js | 2 +- src/RouteConfig.js | 3 +- 37 files changed, 1246 insertions(+), 1370 deletions(-) diff --git a/dist/OAuthModel.js b/dist/OAuthModel.js index 592e0e98..6c7ca2dd 100644 --- a/dist/OAuthModel.js +++ b/dist/OAuthModel.js @@ -1,39 +1,38 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const OAUTH_CLIENTS = [{ +var OAUTH_CLIENTS = [{ clientId: 'CLI2', clientSecret: 'client_secret_here', - grants: ['password'], + grants: ['password'] }]; -const OauthModel = function OauthModel(userRepository) { - const _this = this; +var OauthModel = function OauthModel(userRepository) { + var _this = this; (0, _classCallCheck3.default)(this, OauthModel); - this.getAccessToken = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(bearerToken) { - let user, - userTokenObject; - return _regenerator2.default.wrap((_context) => { + this.getAccessToken = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(bearerToken) { + var user, userTokenObject; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -51,7 +50,9 @@ const OauthModel = function OauthModel(userRepository) { return _context.abrupt('return', null); case 5: - userTokenObject = user.accessTokens.find(tokenObject => tokenObject.accessToken === bearerToken); + userTokenObject = user.accessTokens.find(function (tokenObject) { + return tokenObject.accessToken === bearerToken; + }); if (userTokenObject) { _context.next = 8; @@ -63,7 +64,7 @@ const OauthModel = function OauthModel(userRepository) { case 8: return _context.abrupt('return', { accessToken: userTokenObject.accessToken, - user, + user: user }); case 9: @@ -77,15 +78,17 @@ const OauthModel = function OauthModel(userRepository) { return function (_x) { return _ref.apply(this, arguments); }; - }()); + }(); this.getClient = function (clientId, clientSecret) { - return OAUTH_CLIENTS.find(client => client.clientId === clientId && client.clientSecret === clientSecret); + return OAUTH_CLIENTS.find(function (client) { + return client.clientId === clientId && client.clientSecret === clientSecret; + }); }; - this.getUser = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { - return _regenerator2.default.wrap((_context2) => { + this.getUser = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -106,14 +109,14 @@ const OauthModel = function OauthModel(userRepository) { return function (_x2, _x3) { return _ref2.apply(this, arguments); }; - }()); + }(); this.saveToken = function (tokenObject, client, user) { _this._userRepository.saveAccessToken(user.id, tokenObject); return { accessToken: tokenObject.accessToken, - client, - user, + client: client, + user: user }; }; @@ -127,4 +130,4 @@ const OauthModel = function OauthModel(userRepository) { // eslint-disable-next-line no-unused-vars ; -exports.default = OauthModel; +exports.default = OauthModel; \ No newline at end of file diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js index 2efc9fc1..166e71eb 100644 --- a/dist/RouteConfig.js +++ b/dist/RouteConfig.js @@ -1,64 +1,64 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); +var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); -const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); +var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); -const _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); +var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); -const _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); +var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); -const _create = require('babel-runtime/core-js/object/create'); +var _create = require('babel-runtime/core-js/object/create'); -const _create2 = _interopRequireDefault(_create); +var _create2 = _interopRequireDefault(_create); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names'); +var _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names'); -const _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames); +var _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames); -const _expressOauthServer = require('express-oauth-server'); +var _expressOauthServer = require('express-oauth-server'); -const _expressOauthServer2 = _interopRequireDefault(_expressOauthServer); +var _expressOauthServer2 = _interopRequireDefault(_expressOauthServer); -const _nullthrows = require('nullthrows'); +var _nullthrows = require('nullthrows'); -const _nullthrows2 = _interopRequireDefault(_nullthrows); +var _nullthrows2 = _interopRequireDefault(_nullthrows); -const _multer = require('multer'); +var _multer = require('multer'); -const _multer2 = _interopRequireDefault(_multer); +var _multer2 = _interopRequireDefault(_multer); -const _OAuthModel = require('./OAuthModel'); +var _OAuthModel = require('./OAuthModel'); -const _OAuthModel2 = _interopRequireDefault(_OAuthModel); +var _OAuthModel2 = _interopRequireDefault(_OAuthModel); -const _HttpError = require('./lib/HttpError'); +var _HttpError = require('./lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const maybe = function maybe(middleware, condition) { +var maybe = function maybe(middleware, condition) { return function (request, response, next) { if (condition) { middleware(request, response, next); @@ -68,11 +68,11 @@ const maybe = function maybe(middleware, condition) { }; }; -const injectUserMiddleware = function injectUserMiddleware() { +var injectUserMiddleware = function injectUserMiddleware() { return function (request, response, next) { - const oauthInfo = response.locals.oauth; + var oauthInfo = response.locals.oauth; if (oauthInfo) { - const token = oauthInfo.token; + var token = oauthInfo.token; // eslint-disable-next-line no-param-reassign request.user = token && token.user; } @@ -84,13 +84,13 @@ const injectUserMiddleware = function injectUserMiddleware() { // prevents of closing server-sent-events stream if there aren't events for // a long time, but according to the docs sse keep connection alive automatically. // if there will be related issues in the future, we can return _keepAlive() back. -const serverSentEventsMiddleware = function serverSentEventsMiddleware() { +var serverSentEventsMiddleware = function serverSentEventsMiddleware() { return function (request, response, next) { request.socket.setNoDelay(); response.writeHead(200, { 'Cache-Control': 'no-cache', Connection: 'keep-alive', - 'Content-Type': 'text/event-stream', + 'Content-Type': 'text/event-stream' }); next(); @@ -98,51 +98,47 @@ const serverSentEventsMiddleware = function serverSentEventsMiddleware() { }; exports.default = function (app, container, controllers, settings) { - const oauth = new _expressOauthServer2.default({ + var oauth = new _expressOauthServer2.default({ ACCESS_TOKEN_LIFETIME: settings.ACCESS_TOKEN_LIFETIME, allowBearerTokensInQueryString: true, - model: new _OAuthModel2.default(container.constitute('UserRepository')), + model: new _OAuthModel2.default(container.constitute('UserRepository')) }); - const filesMiddleware = function filesMiddleware() { - const allowedUploads = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var filesMiddleware = function filesMiddleware() { + var allowedUploads = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; return (0, _nullthrows2.default)(allowedUploads).length ? (0, _multer2.default)().fields(allowedUploads) : (0, _multer2.default)().any(); }; app.post(settings.LOGIN_ROUTE, oauth.token()); - controllers.forEach((controllerName) => { - const controller = container.constitute(controllerName); - (0, _getOwnPropertyNames2.default)((0, _getPrototypeOf2.default)(controller)).forEach((functionName) => { - const mappedFunction = controller[functionName]; - let allowedUploads = mappedFunction.allowedUploads, - anonymous = mappedFunction.anonymous, - httpVerb = mappedFunction.httpVerb, - route = mappedFunction.route, - serverSentEvents = mappedFunction.serverSentEvents; + controllers.forEach(function (controllerName) { + var controller = container.constitute(controllerName); + (0, _getOwnPropertyNames2.default)((0, _getPrototypeOf2.default)(controller)).forEach(function (functionName) { + var mappedFunction = controller[functionName]; + var allowedUploads = mappedFunction.allowedUploads, + anonymous = mappedFunction.anonymous, + httpVerb = mappedFunction.httpVerb, + route = mappedFunction.route, + serverSentEvents = mappedFunction.serverSentEvents; if (!httpVerb) { return; } - app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), maybe(filesMiddleware(allowedUploads), allowedUploads), (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) { - let argumentNames, - values, - controllerInstance, - _request$body, - access_token, - body, - functionResult, - result, - httpError; - - return _regenerator2.default.wrap((_context) => { + app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), maybe(filesMiddleware(allowedUploads), allowedUploads), function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) { + var argumentNames, values, controllerInstance, _request$body, access_token, body, functionResult, result, httpError; + + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: - argumentNames = (route.match(/:[\w]*/g) || []).map(argumentName => argumentName.replace(':', '')); - values = argumentNames.map(argument => request.params[argument]); + argumentNames = (route.match(/:[\w]*/g) || []).map(function (argumentName) { + return argumentName.replace(':', ''); + }); + values = argumentNames.map(function (argument) { + return request.params[argument]; + }); controllerInstance = container.constitute(controllerName); // In order parallel requests on the controller, the state @@ -162,7 +158,7 @@ exports.default = function (app, container, controllers, settings) { // Take access token out if it's posted. _request$body = request.body, access_token = _request$body.access_token, body = (0, _objectWithoutProperties3.default)(_request$body, ['access_token']); _context.prev = 8; - functionResult = mappedFunction.call(...[controllerInstance].concat((0, _toConsumableArray3.default)(values), [body])); + functionResult = mappedFunction.call.apply(mappedFunction, [controllerInstance].concat((0, _toConsumableArray3.default)(values), [body])); if (!functionResult.then) { _context.next = 17; @@ -170,7 +166,11 @@ exports.default = function (app, container, controllers, settings) { } _context.next = 13; - return _promise2.default.race([functionResult, !serverSentEvents ? new _promise2.default((resolve, reject) => setTimeout(() => reject(new Error('timeout')), settings.API_TIMEOUT)) : null]); + return _promise2.default.race([functionResult, !serverSentEvents ? new _promise2.default(function (resolve, reject) { + return setTimeout(function () { + return reject(new Error('timeout')); + }, settings.API_TIMEOUT); + }) : null]); case 13: result = _context.sent; @@ -188,12 +188,12 @@ exports.default = function (app, container, controllers, settings) { case 20: _context.prev = 20; - _context.t0 = _context.catch(8); + _context.t0 = _context['catch'](8); httpError = new _HttpError2.default(_context.t0); response.status(httpError.status).json({ error: httpError.message, - ok: false, + ok: false }); case 24: @@ -207,19 +207,18 @@ exports.default = function (app, container, controllers, settings) { return function (_x2, _x3) { return _ref.apply(this, arguments); }; - }())); + }()); }); }); - app.all('*', (request, response) => { + app.all('*', function (request, response) { response.sendStatus(404); }); - app.use((error, request, response, next, // eslint-disable-line no-unused-vars - ) => { + app.use(function (error, request, response, next) { response.status(400).json({ error: error.code ? error.code : error, - ok: false, + ok: false }); }); -}; +}; \ No newline at end of file diff --git a/dist/app.js b/dist/app.js index 6c1dfc67..3364ac19 100644 --- a/dist/app.js +++ b/dist/app.js @@ -1,37 +1,37 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _bodyParser = require('body-parser'); +var _bodyParser = require('body-parser'); -const _bodyParser2 = _interopRequireDefault(_bodyParser); +var _bodyParser2 = _interopRequireDefault(_bodyParser); -const _express = require('express'); +var _express = require('express'); -const _express2 = _interopRequireDefault(_express); +var _express2 = _interopRequireDefault(_express); -const _morgan = require('morgan'); +var _morgan = require('morgan'); -const _morgan2 = _interopRequireDefault(_morgan); +var _morgan2 = _interopRequireDefault(_morgan); -const _RouteConfig = require('./RouteConfig'); +var _RouteConfig = require('./RouteConfig'); -const _RouteConfig2 = _interopRequireDefault(_RouteConfig); +var _RouteConfig2 = _interopRequireDefault(_RouteConfig); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = function (container, settings) { - const app = (0, _express2.default)(); + var app = (0, _express2.default)(); - const setCORSHeaders = function setCORSHeaders(request, response, next) { + var setCORSHeaders = function setCORSHeaders(request, response, next) { if (request.method === 'OPTIONS') { response.set({ 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Origin': '*', - 'Access-Control-Max-Age': '300', + 'Access-Control-Max-Age': '300' }); return response.sendStatus(204); } @@ -50,7 +50,7 @@ exports.default = function (container, settings) { (0, _RouteConfig2.default)(app, container, ['DeviceClaimsController', // to avoid routes collisions EventsController should be placed // before DevicesController - 'EventsController', 'DevicesController', 'OauthClientsController', 'ProductsController', 'ProvisioningController', 'UsersController', 'WebhooksController'], settings); + 'EventsController', 'DevicesController', 'OauthClientsController', 'ProductsController', 'ProvisioningController', 'UsersController', 'WebhooksController'], settings); return app; -}; +}; \ No newline at end of file diff --git a/dist/controllers/Controller.js b/dist/controllers/Controller.js index ecb1cbec..b11205a2 100644 --- a/dist/controllers/Controller.js +++ b/dist/controllers/Controller.js @@ -1,36 +1,36 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); exports.default = undefined; -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const Controller = function Controller() { +var Controller = function Controller() { (0, _classCallCheck3.default)(this, Controller); this.bad = function (message) { - const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + var status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; return { data: { error: message, - ok: false, + ok: false }, - status, + status: status }; }; this.ok = function (output) { return { data: output, - status: 200, + status: 200 }; }; }; -exports.default = Controller; +exports.default = Controller; \ No newline at end of file diff --git a/dist/controllers/DeviceClaimsController.js b/dist/controllers/DeviceClaimsController.js index 6e465768..1789c62b 100644 --- a/dist/controllers/DeviceClaimsController.js +++ b/dist/controllers/DeviceClaimsController.js @@ -1,64 +1,60 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _desc, - _value, - _class; +var _dec, _dec2, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -68,7 +64,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -83,13 +81,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/device_claims'), (_class = (function (_Controller) { +var DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/device_claims'), (_class = function (_Controller) { (0, _inherits3.default)(DeviceClaimsController, _Controller); function DeviceClaimsController(deviceManager, claimCodeManager) { (0, _classCallCheck3.default)(this, DeviceClaimsController); - const _this = (0, _possibleConstructorReturn3.default)(this, (DeviceClaimsController.__proto__ || (0, _getPrototypeOf2.default)(DeviceClaimsController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (DeviceClaimsController.__proto__ || (0, _getPrototypeOf2.default)(DeviceClaimsController)).call(this)); _this._deviceManager = deviceManager; _this._claimCodeManager = claimCodeManager; @@ -98,11 +96,9 @@ const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _createClass3.default)(DeviceClaimsController, [{ key: 'createClaimCode', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { - let claimCode, - devices, - deviceIDs; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + var claimCode, devices, deviceIDs; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -113,7 +109,9 @@ const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = case 3: devices = _context.sent; - deviceIDs = devices.map(device => device.deviceID); + deviceIDs = devices.map(function (device) { + return device.deviceID; + }); return _context.abrupt('return', this.ok({ claim_code: claimCode, device_ids: deviceIDs })); case 6: @@ -129,8 +127,8 @@ const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return createClaimCode; - }()), + }() }]); return DeviceClaimsController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClaimCode', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClaimCode'), _class.prototype)), _class)); -exports.default = DeviceClaimsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createClaimCode', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClaimCode'), _class.prototype)), _class)); +exports.default = DeviceClaimsController; \ No newline at end of file diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js index a6cc6beb..43223ede 100644 --- a/dist/controllers/DevicesController.js +++ b/dist/controllers/DevicesController.js @@ -1,106 +1,84 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _dec10, - _dec11, - _dec12, - _dec13, - _dec14, - _dec15, - _dec16, - _dec17, - _dec18, - _dec19, - _dec20, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _desc, _value, _class; -const _nullthrows = require('nullthrows'); +var _nullthrows = require('nullthrows'); -const _nullthrows2 = _interopRequireDefault(_nullthrows); +var _nullthrows2 = _interopRequireDefault(_nullthrows); -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _FirmwareCompilationManager = require('../managers/FirmwareCompilationManager'); +var _FirmwareCompilationManager = require('../managers/FirmwareCompilationManager'); -const _FirmwareCompilationManager2 = _interopRequireDefault(_FirmwareCompilationManager); +var _FirmwareCompilationManager2 = _interopRequireDefault(_FirmwareCompilationManager); -const _allowUpload = require('../decorators/allowUpload'); +var _allowUpload = require('../decorators/allowUpload'); -const _allowUpload2 = _interopRequireDefault(_allowUpload); +var _allowUpload2 = _interopRequireDefault(_allowUpload); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); -const _deviceToAPI = require('../lib/deviceToAPI'); +var _deviceToAPI = require('../lib/deviceToAPI'); -const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); +var _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -110,7 +88,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -125,13 +105,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = (function (_Controller) { +var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = function (_Controller) { (0, _inherits3.default)(DevicesController, _Controller); function DevicesController(deviceManager) { (0, _classCallCheck3.default)(this, DevicesController); - const _this = (0, _possibleConstructorReturn3.default)(this, (DevicesController.__proto__ || (0, _getPrototypeOf2.default)(DevicesController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (DevicesController.__proto__ || (0, _getPrototypeOf2.default)(DevicesController)).call(this)); _this._deviceManager = deviceManager; return _this; @@ -139,9 +119,9 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ (0, _createClass3.default)(DevicesController, [{ key: 'claimDevice', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(postBody) { - let deviceID; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(postBody) { + var deviceID; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -166,11 +146,11 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return claimDevice; - }()), + }() }, { key: 'getAppFirmware', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(binaryID) { + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(binaryID) { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { @@ -190,12 +170,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getAppFirmware; - }()), + }() }, { key: 'compileSources', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(postBody) { - let response; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(postBody) { + var response; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { @@ -215,8 +195,8 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 5: return _context3.abrupt('return', this.ok((0, _extends3.default)({}, response, { - binary_url: `/v1/binaries/${response.binary_id}`, - ok: true, + binary_url: '/v1/binaries/' + response.binary_id, + ok: true }))); case 6: @@ -232,11 +212,11 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return compileSources; - }()), + }() }, { key: 'unclaimDevice', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) { + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -260,12 +240,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return unclaimDevice; - }()), + }() }, { key: 'getDevices', - value: (function () { - const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { - let devices; + value: function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { + var devices; return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { @@ -276,11 +256,13 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 3: devices = _context5.sent; - return _context5.abrupt('return', this.ok(devices.map(device => (0, _deviceToAPI2.default)(device)))); + return _context5.abrupt('return', this.ok(devices.map(function (device) { + return (0, _deviceToAPI2.default)(device); + }))); case 7: _context5.prev = 7; - _context5.t0 = _context5.catch(0); + _context5.t0 = _context5['catch'](0); return _context5.abrupt('return', this.ok([])); case 10: @@ -296,12 +278,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getDevices; - }()), + }() }, { key: 'getDevice', - value: (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) { - let device; + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) { + var device; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { @@ -326,13 +308,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getDevice; - }()), + }() }, { key: 'getVariableValue', - value: (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, varName) { - let varValue, - errorMessage; + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, varName) { + var varValue, errorMessage; return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { @@ -347,7 +328,7 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 7: _context7.prev = 7; - _context7.t0 = _context7.catch(0); + _context7.t0 = _context7['catch'](0); errorMessage = _context7.t0.message; if (!errorMessage.match('Variable not found')) { @@ -373,15 +354,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getVariableValue; - }()), + }() }, { key: 'updateDevice', - value: (function () { - const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) { - let updatedAttributes, - flashStatus, - file, - _flashStatus; + value: function () { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) { + var updatedAttributes, flashStatus, file, _flashStatus; return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { @@ -471,14 +449,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return updateDevice; - }()), + }() }, { key: 'callDeviceFunction', - value: (function () { - const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) { - let result, - device, - errorMessage; + value: function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) { + var result, device, errorMessage; return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { @@ -498,7 +474,7 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 10: _context9.prev = 10; - _context9.t0 = _context9.catch(0); + _context9.t0 = _context9['catch'](0); errorMessage = _context9.t0.message; if (!(errorMessage.indexOf('Unknown Function') >= 0)) { @@ -524,8 +500,8 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return callDeviceFunction; - }()), + }() }]); return DevicesController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype)), _class)); -exports.default = DevicesController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype)), _class)); +exports.default = DevicesController; \ No newline at end of file diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js index 5208feb2..4860098e 100644 --- a/dist/controllers/EventsController.js +++ b/dist/controllers/EventsController.js @@ -1,93 +1,80 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _stringify = require('babel-runtime/core-js/json/stringify'); +var _stringify = require('babel-runtime/core-js/json/stringify'); -const _stringify2 = _interopRequireDefault(_stringify); +var _stringify2 = _interopRequireDefault(_stringify); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _dec10, - _dec11, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _serverSentEvents = require('../decorators/serverSentEvents'); +var _serverSentEvents = require('../decorators/serverSentEvents'); -const _serverSentEvents2 = _interopRequireDefault(_serverSentEvents); +var _serverSentEvents2 = _interopRequireDefault(_serverSentEvents); -const _logger = require('../lib/logger'); +var _logger = require('../lib/logger'); -const _logger2 = _interopRequireDefault(_logger); +var _logger2 = _interopRequireDefault(_logger); -const _eventToApi = require('../lib/eventToApi'); +var _eventToApi = require('../lib/eventToApi'); -const _eventToApi2 = _interopRequireDefault(_eventToApi); +var _eventToApi2 = _interopRequireDefault(_eventToApi); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -97,7 +84,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -112,13 +101,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec3 = (0, _serverSentEvents2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('post'), _dec11 = (0, _route2.default)('/v1/devices/events'), (_class = (function (_Controller) { +var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec3 = (0, _serverSentEvents2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('post'), _dec11 = (0, _route2.default)('/v1/devices/events'), (_class = function (_Controller) { (0, _inherits3.default)(EventsController, _Controller); function EventsController(eventManager) { (0, _classCallCheck3.default)(this, EventsController); - const _this = (0, _possibleConstructorReturn3.default)(this, (EventsController.__proto__ || (0, _getPrototypeOf2.default)(EventsController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (EventsController.__proto__ || (0, _getPrototypeOf2.default)(EventsController)).call(this)); _this._eventManager = eventManager; return _this; @@ -127,10 +116,10 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro (0, _createClass3.default)(EventsController, [{ key: '_closeStream', value: function _closeStream(subscriptionID) { - const _this2 = this; + var _this2 = this; - return new _promise2.default((resolve) => { - const closeStreamHandler = function closeStreamHandler() { + return new _promise2.default(function (resolve) { + var closeStreamHandler = function closeStreamHandler() { _this2._eventManager.unsubscribe(subscriptionID); resolve(); }; @@ -140,23 +129,23 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro _this2.response.on('finish', closeStreamHandler); _this2.response.on('end', closeStreamHandler); }); - }, + } }, { key: '_pipeEvent', value: function _pipeEvent(event) { try { - this.response.write(`event: ${event.name}\n`); - this.response.write(`data: ${(0, _stringify2.default)((0, _eventToApi2.default)(event))}\n\n`); + this.response.write('event: ' + event.name + '\n'); + this.response.write('data: ' + (0, _stringify2.default)((0, _eventToApi2.default)(event)) + '\n\n'); } catch (error) { - _logger2.default.error(`pipeEvents - write error: ${error}`); + _logger2.default.error('pipeEvents - write error: ' + error); throw error; } - }, + } }, { key: 'getEvents', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(eventNamePrefix) { - let subscriptionID; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(eventNamePrefix) { + var subscriptionID; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -181,19 +170,19 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return getEvents; - }()), + }() }, { key: 'getMyEvents', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) { - let subscriptionID; + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) { + var subscriptionID; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { mydevices: true, - userID: this.user.id, + userID: this.user.id }); _context2.next = 3; return this._closeStream(subscriptionID); @@ -214,19 +203,19 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return getMyEvents; - }()), + }() }, { key: 'getDeviceEvents', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, eventNamePrefix) { - let subscriptionID; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, eventNamePrefix) { + var subscriptionID; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { - deviceID, - userID: this.user.id, + deviceID: deviceID, + userID: this.user.id }); _context3.next = 3; return this._closeStream(subscriptionID); @@ -247,12 +236,12 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return getDeviceEvents; - }()), + }() }, { key: 'publish', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(postBody) { - let eventData; + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(postBody) { + var eventData; return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -262,7 +251,7 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro isPublic: !postBody.private, name: postBody.name, ttl: postBody.ttl, - userID: this.user.id, + userID: this.user.id }; @@ -282,8 +271,8 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return publish; - }()), + }() }]); return EventsController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class)); -exports.default = EventsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class)); +exports.default = EventsController; \ No newline at end of file diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js index e9ff1c86..69a818aa 100644 --- a/dist/controllers/OauthClientsController.js +++ b/dist/controllers/OauthClientsController.js @@ -1,72 +1,64 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -76,7 +68,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -91,7 +85,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = (function (_Controller) { +var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = function (_Controller) { (0, _inherits3.default)(OauthClientsController, _Controller); function OauthClientsController() { @@ -103,9 +97,9 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = key: 'createClient', // eslint-disable-next-line class-methods-use-this - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { - return _regenerator2.default.wrap((_context) => { + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -124,14 +118,14 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return createClient; - }()), + }() }, { key: 'editClient', // eslint-disable-next-line class-methods-use-this - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { - return _regenerator2.default.wrap((_context2) => { + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -150,14 +144,14 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return editClient; - }()), + }() }, { key: 'deleteClient', // eslint-disable-next-line class-methods-use-this - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { - return _regenerator2.default.wrap((_context3) => { + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -176,8 +170,8 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return deleteClient; - }()), + }() }]); return OauthClientsController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class)); -exports.default = OauthClientsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class)); +exports.default = OauthClientsController; \ No newline at end of file diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js index 0ec9cf30..e9382aa3 100644 --- a/dist/controllers/ProductsController.js +++ b/dist/controllers/ProductsController.js @@ -1,68 +1,42 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _dec10, - _dec11, - _dec12, - _dec13, - _dec14, - _dec15, - _dec16, - _dec17, - _dec18, - _dec19, - _dec20, - _dec21, - _dec22, - _dec23, - _dec24, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _desc, _value, _class; /* eslint-disable */ var _Controller2 = require('./Controller'); @@ -429,4 +403,4 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro return ProductsController; }(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'generateClaimCode', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'generateClaimCode'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec23, _dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class)); exports.default = ProductsController; -/* eslint-enable */ +/* eslint-enable */ \ No newline at end of file diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js index a65f7fdd..88ffdb80 100644 --- a/dist/controllers/ProvisioningController.js +++ b/dist/controllers/ProvisioningController.js @@ -1,72 +1,68 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _desc, - _value, - _class; +var _dec, _dec2, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); -const _deviceToAPI = require('../lib/deviceToAPI'); +var _deviceToAPI = require('../lib/deviceToAPI'); -const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); +var _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -76,7 +72,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -91,13 +89,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/provisioning/:coreID'), (_class = (function (_Controller) { +var ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/provisioning/:coreID'), (_class = function (_Controller) { (0, _inherits3.default)(ProvisioningController, _Controller); function ProvisioningController(deviceManager) { (0, _classCallCheck3.default)(this, ProvisioningController); - const _this = (0, _possibleConstructorReturn3.default)(this, (ProvisioningController.__proto__ || (0, _getPrototypeOf2.default)(ProvisioningController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (ProvisioningController.__proto__ || (0, _getPrototypeOf2.default)(ProvisioningController)).call(this)); _this._deviceManager = deviceManager; return _this; @@ -105,9 +103,9 @@ const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _createClass3.default)(ProvisioningController, [{ key: 'provision', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(coreID, postBody) { - let device; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(coreID, postBody) { + var device; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -140,8 +138,8 @@ const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return provision; - }()), + }() }]); return ProvisioningController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'provision', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'provision'), _class.prototype)), _class)); -exports.default = ProvisioningController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'provision', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'provision'), _class.prototype)), _class)); +exports.default = ProvisioningController; \ No newline at end of file diff --git a/dist/controllers/UsersController.js b/dist/controllers/UsersController.js index 86db684a..6b9179d4 100644 --- a/dist/controllers/UsersController.js +++ b/dist/controllers/UsersController.js @@ -1,83 +1,72 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _desc, _value, _class; -const _basicAuthParser3 = require('basic-auth-parser'); +var _basicAuthParser3 = require('basic-auth-parser'); -const _basicAuthParser4 = _interopRequireDefault(_basicAuthParser3); +var _basicAuthParser4 = _interopRequireDefault(_basicAuthParser3); -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _anonymous = require('../decorators/anonymous'); +var _anonymous = require('../decorators/anonymous'); -const _anonymous2 = _interopRequireDefault(_anonymous); +var _anonymous2 = _interopRequireDefault(_anonymous); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -87,7 +76,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -102,13 +93,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/users'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('delete'), _dec5 = (0, _route2.default)('/v1/access_tokens/:token'), _dec6 = (0, _anonymous2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/access_tokens'), _dec9 = (0, _anonymous2.default)(), (_class = (function (_Controller) { +var UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/users'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('delete'), _dec5 = (0, _route2.default)('/v1/access_tokens/:token'), _dec6 = (0, _anonymous2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/access_tokens'), _dec9 = (0, _anonymous2.default)(), (_class = function (_Controller) { (0, _inherits3.default)(UsersController, _Controller); function UsersController(userRepository) { (0, _classCallCheck3.default)(this, UsersController); - const _this = (0, _possibleConstructorReturn3.default)(this, (UsersController.__proto__ || (0, _getPrototypeOf2.default)(UsersController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (UsersController.__proto__ || (0, _getPrototypeOf2.default)(UsersController)).call(this)); _this._userRepository = userRepository; return _this; @@ -116,9 +107,9 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro (0, _createClass3.default)(UsersController, [{ key: 'createUser', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { - let isUserNameInUse; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + var isUserNameInUse; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -146,7 +137,7 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro case 11: _context.prev = 11; - _context.t0 = _context.catch(0); + _context.t0 = _context['catch'](0); return _context.abrupt('return', this.bad(_context.t0.message)); case 14: @@ -162,15 +153,12 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro } return createUser; - }()), + }() }, { key: 'deleteAccessToken', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(token) { - let _basicAuthParser, - username, - password, - user; + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(token) { + var _basicAuthParser, username, password, user; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { @@ -201,15 +189,12 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro } return deleteAccessToken; - }()), + }() }, { key: 'getAccessTokens', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { - let _basicAuthParser2, - username, - password, - user; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + var _basicAuthParser2, username, password, user; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { @@ -236,8 +221,8 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro } return getAccessTokens; - }()), + }() }]); return UsersController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createUser', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createUser'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteAccessToken', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteAccessToken'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAccessTokens', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAccessTokens'), _class.prototype)), _class)); -exports.default = UsersController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createUser', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createUser'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteAccessToken', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteAccessToken'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAccessTokens', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAccessTokens'), _class.prototype)), _class)); +exports.default = UsersController; \ No newline at end of file diff --git a/dist/controllers/WebhooksController.js b/dist/controllers/WebhooksController.js index 9783ca59..d9fe1768 100644 --- a/dist/controllers/WebhooksController.js +++ b/dist/controllers/WebhooksController.js @@ -1,78 +1,68 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -82,7 +72,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -97,9 +89,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; +var REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; -const validateWebhookMutator = function validateWebhookMutator(webhookMutator) { +var validateWebhookMutator = function validateWebhookMutator(webhookMutator) { if (!webhookMutator.event) { return new _HttpError2.default('no event name provided'); } @@ -116,13 +108,13 @@ const validateWebhookMutator = function validateWebhookMutator(webhookMutator) { return null; }; -const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookId'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookId'), (_class = (function (_Controller) { +var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookId'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookId'), (_class = function (_Controller) { (0, _inherits3.default)(WebhooksController, _Controller); function WebhooksController(webhookManager) { (0, _classCallCheck3.default)(this, WebhooksController); - const _this = (0, _possibleConstructorReturn3.default)(this, (WebhooksController.__proto__ || (0, _getPrototypeOf2.default)(WebhooksController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (WebhooksController.__proto__ || (0, _getPrototypeOf2.default)(WebhooksController)).call(this)); _this._webhookManager = webhookManager; return _this; @@ -130,8 +122,8 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ (0, _createClass3.default)(WebhooksController, [{ key: 'getAll', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -157,11 +149,11 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return getAll; - }()), + }() }, { key: 'getById', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookId) { + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookId) { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { @@ -187,13 +179,12 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return getById; - }()), + }() }, { key: 'create', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { - let validateError, - newWebhook; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + var validateError, newWebhook; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { @@ -210,7 +201,7 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ case 3: _context3.next = 5; return this._webhookManager.create((0, _extends3.default)({}, model, { - ownerID: this.user.id, + ownerID: this.user.id })); case 5: @@ -220,7 +211,7 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ event: newWebhook.event, id: newWebhook.id, ok: true, - url: newWebhook.url, + url: newWebhook.url })); case 7: @@ -236,11 +227,11 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return create; - }()), + }() }, { key: 'deleteById', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookId) { + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookId) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -264,8 +255,8 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return deleteById; - }()), + }() }]); return WebhooksController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype)), _class)); -exports.default = WebhooksController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype)), _class)); +exports.default = WebhooksController; \ No newline at end of file diff --git a/dist/controllers/types.js b/dist/controllers/types.js index 8b137891..9a390c31 100644 --- a/dist/controllers/types.js +++ b/dist/controllers/types.js @@ -1 +1 @@ - +"use strict"; \ No newline at end of file diff --git a/dist/decorators/allowUpload.js b/dist/decorators/allowUpload.js index 53e1d236..700de3f9 100644 --- a/dist/decorators/allowUpload.js +++ b/dist/decorators/allowUpload.js @@ -1,23 +1,23 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ exports.default = function () { - const fileName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; - const maxCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + var fileName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; + var maxCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return function (target, name, descriptor) { - const allowedUploads = target[name].allowedUploads || []; + var allowedUploads = target[name].allowedUploads || []; if (fileName) { allowedUploads.push({ - maxCount, - name: fileName, + maxCount: maxCount, + name: fileName }); } target[name].allowedUploads = allowedUploads; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/anonymous.js b/dist/decorators/anonymous.js index 78a9fa94..d139c96e 100644 --- a/dist/decorators/anonymous.js +++ b/dist/decorators/anonymous.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function () { target[name].anonymous = true; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/httpVerb.js b/dist/decorators/httpVerb.js index a3d2df25..09aac162 100644 --- a/dist/decorators/httpVerb.js +++ b/dist/decorators/httpVerb.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function (httpVerb) { target[name].httpVerb = httpVerb; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/route.js b/dist/decorators/route.js index d58c4772..0cbce75e 100644 --- a/dist/decorators/route.js +++ b/dist/decorators/route.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function (route) { target[name].route = route; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/serverSentEvents.js b/dist/decorators/serverSentEvents.js index ddd1ca6a..bdd4ea05 100644 --- a/dist/decorators/serverSentEvents.js +++ b/dist/decorators/serverSentEvents.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function () { target[name].serverSentEvents = true; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/types.js b/dist/decorators/types.js index 8b137891..a726efc4 100644 --- a/dist/decorators/types.js +++ b/dist/decorators/types.js @@ -1 +1 @@ - +'use strict'; \ No newline at end of file diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js index 7e801d93..8522b18a 100644 --- a/dist/defaultBindings.js +++ b/dist/defaultBindings.js @@ -1,70 +1,70 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _DeviceClaimsController = require('./controllers/DeviceClaimsController'); +var _DeviceClaimsController = require('./controllers/DeviceClaimsController'); -const _DeviceClaimsController2 = _interopRequireDefault(_DeviceClaimsController); +var _DeviceClaimsController2 = _interopRequireDefault(_DeviceClaimsController); -const _DevicesController = require('./controllers/DevicesController'); +var _DevicesController = require('./controllers/DevicesController'); -const _DevicesController2 = _interopRequireDefault(_DevicesController); +var _DevicesController2 = _interopRequireDefault(_DevicesController); -const _EventsController = require('./controllers/EventsController'); +var _EventsController = require('./controllers/EventsController'); -const _EventsController2 = _interopRequireDefault(_EventsController); +var _EventsController2 = _interopRequireDefault(_EventsController); -const _OauthClientsController = require('./controllers/OauthClientsController'); +var _OauthClientsController = require('./controllers/OauthClientsController'); -const _OauthClientsController2 = _interopRequireDefault(_OauthClientsController); +var _OauthClientsController2 = _interopRequireDefault(_OauthClientsController); -const _ProductsController = require('./controllers/ProductsController'); +var _ProductsController = require('./controllers/ProductsController'); -const _ProductsController2 = _interopRequireDefault(_ProductsController); +var _ProductsController2 = _interopRequireDefault(_ProductsController); -const _ProvisioningController = require('./controllers/ProvisioningController'); +var _ProvisioningController = require('./controllers/ProvisioningController'); -const _ProvisioningController2 = _interopRequireDefault(_ProvisioningController); +var _ProvisioningController2 = _interopRequireDefault(_ProvisioningController); -const _UsersController = require('./controllers/UsersController'); +var _UsersController = require('./controllers/UsersController'); -const _UsersController2 = _interopRequireDefault(_UsersController); +var _UsersController2 = _interopRequireDefault(_UsersController); -const _WebhooksController = require('./controllers/WebhooksController'); +var _WebhooksController = require('./controllers/WebhooksController'); -const _WebhooksController2 = _interopRequireDefault(_WebhooksController); +var _WebhooksController2 = _interopRequireDefault(_WebhooksController); -const _WebhookManager = require('./managers/WebhookManager'); +var _WebhookManager = require('./managers/WebhookManager'); -const _WebhookManager2 = _interopRequireDefault(_WebhookManager); +var _WebhookManager2 = _interopRequireDefault(_WebhookManager); -const _EventManager = require('./managers/EventManager'); +var _EventManager = require('./managers/EventManager'); -const _EventManager2 = _interopRequireDefault(_EventManager); +var _EventManager2 = _interopRequireDefault(_EventManager); -const _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepository'); +var _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepository'); -const _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository); +var _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository); -const _DeviceManager = require('./managers/DeviceManager'); +var _DeviceManager = require('./managers/DeviceManager'); -const _DeviceManager2 = _interopRequireDefault(_DeviceManager); +var _DeviceManager2 = _interopRequireDefault(_DeviceManager); -const _UserFileRepository = require('./repository/UserFileRepository'); +var _UserFileRepository = require('./repository/UserFileRepository'); -const _UserFileRepository2 = _interopRequireDefault(_UserFileRepository); +var _UserFileRepository2 = _interopRequireDefault(_UserFileRepository); -const _WebhookFileRepository = require('./repository/WebhookFileRepository'); +var _WebhookFileRepository = require('./repository/WebhookFileRepository'); -const _WebhookFileRepository2 = _interopRequireDefault(_WebhookFileRepository); +var _WebhookFileRepository2 = _interopRequireDefault(_WebhookFileRepository); -const _settings = require('./settings'); +var _settings = require('./settings'); -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -99,4 +99,4 @@ exports.default = function (container) { container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']); container.bindClass('UserRepository', _UserFileRepository2.default, ['USERS_DIRECTORY']); container.bindClass('WebhookRepository', _WebhookFileRepository2.default, ['WEBHOOKS_DIRECTORY']); -}; +}; \ No newline at end of file diff --git a/dist/exports.js b/dist/exports.js index 3bcb0cf9..a7506238 100644 --- a/dist/exports.js +++ b/dist/exports.js @@ -1,29 +1,29 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); exports.settings = exports.logger = exports.defaultBindings = exports.createApp = undefined; -const _logger = require('./lib/logger'); +var _logger = require('./lib/logger'); -const _logger2 = _interopRequireDefault(_logger); +var _logger2 = _interopRequireDefault(_logger); -const _app = require('./app'); +var _app = require('./app'); -const _app2 = _interopRequireDefault(_app); +var _app2 = _interopRequireDefault(_app); -const _defaultBindings = require('./defaultBindings'); +var _defaultBindings = require('./defaultBindings'); -const _defaultBindings2 = _interopRequireDefault(_defaultBindings); +var _defaultBindings2 = _interopRequireDefault(_defaultBindings); -const _settings = require('./settings'); +var _settings = require('./settings'); -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.createApp = _app2.default; exports.defaultBindings = _defaultBindings2.default; exports.logger = _logger2.default; -exports.settings = _settings2.default; +exports.settings = _settings2.default; \ No newline at end of file diff --git a/dist/lib/HttpError.js b/dist/lib/HttpError.js index 949d149a..e133f7ec 100644 --- a/dist/lib/HttpError.js +++ b/dist/lib/HttpError.js @@ -1,35 +1,35 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const HttpError = (function (_Error) { +var HttpError = function (_Error) { (0, _inherits3.default)(HttpError, _Error); function HttpError(error) { - const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + var status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; (0, _classCallCheck3.default)(this, HttpError); - const _this = (0, _possibleConstructorReturn3.default)(this, (HttpError.__proto__ || (0, _getPrototypeOf2.default)(HttpError)).call(this, error.message || error)); + var _this = (0, _possibleConstructorReturn3.default)(this, (HttpError.__proto__ || (0, _getPrototypeOf2.default)(HttpError)).call(this, error.message || error)); if (typeof error.status === 'number') { _this.status = error.status; @@ -40,6 +40,6 @@ const HttpError = (function (_Error) { } return HttpError; -}(Error)); +}(Error); -exports.default = HttpError; +exports.default = HttpError; \ No newline at end of file diff --git a/dist/lib/PasswordHasher.js b/dist/lib/PasswordHasher.js index d2cbd8e9..6a694e76 100644 --- a/dist/lib/PasswordHasher.js +++ b/dist/lib/PasswordHasher.js @@ -1,28 +1,28 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _crypto = require('crypto'); +var _crypto = require('crypto'); -const _crypto2 = _interopRequireDefault(_crypto); +var _crypto2 = _interopRequireDefault(_crypto); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const HASH_DIGEST = 'sha1'; /** +var HASH_DIGEST = 'sha1'; /** * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ * * This program is free software: you can redistribute it and/or modify @@ -39,14 +39,14 @@ const HASH_DIGEST = 'sha1'; /** * * You can download the source here: https://github.com/spark/spark-server * - * + * * */ -const HASH_ITERATIONS = 30000; -const KEY_LENGTH = 64; +var HASH_ITERATIONS = 30000; +var KEY_LENGTH = 64; -const PasswordHasher = (function () { +var PasswordHasher = function () { function PasswordHasher() { (0, _classCallCheck3.default)(this, PasswordHasher); } @@ -54,10 +54,10 @@ const PasswordHasher = (function () { (0, _createClass3.default)(PasswordHasher, null, [{ key: 'generateSalt', value: function generateSalt() { - const size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 64; + var size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 64; - return new _promise2.default((resolve, reject) => { - _crypto2.default.randomBytes(size, (error, buffer) => { + return new _promise2.default(function (resolve, reject) { + _crypto2.default.randomBytes(size, function (error, buffer) { if (error) { reject(error); return; @@ -65,12 +65,12 @@ const PasswordHasher = (function () { resolve(buffer.toString('base64')); }); }); - }, + } }, { key: 'hash', value: function hash(password, salt) { - return new _promise2.default((resolve, reject) => { - _crypto2.default.pbkdf2(password, salt, HASH_ITERATIONS, KEY_LENGTH, HASH_DIGEST, (error, key) => { + return new _promise2.default(function (resolve, reject) { + _crypto2.default.pbkdf2(password, salt, HASH_ITERATIONS, KEY_LENGTH, HASH_DIGEST, function (error, key) { if (error) { reject(error); return; @@ -78,9 +78,9 @@ const PasswordHasher = (function () { resolve(key.toString('base64')); }); }); - }, + } }]); return PasswordHasher; -}()); +}(); -exports.default = PasswordHasher; +exports.default = PasswordHasher; \ No newline at end of file diff --git a/dist/lib/deviceToAPI.js b/dist/lib/deviceToAPI.js index f9f6c09e..d332c6ab 100644 --- a/dist/lib/deviceToAPI.js +++ b/dist/lib/deviceToAPI.js @@ -1,9 +1,9 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const deviceToAPI = function deviceToAPI(device, result) { +var deviceToAPI = function deviceToAPI(device, result) { return { cellular: device.isCellular, connected: device.connected, @@ -20,8 +20,8 @@ const deviceToAPI = function deviceToAPI(device, result) { product_id: device.particleProductId, return_value: result, status: 'normal', - variables: device.variables, + variables: device.variables }; }; -exports.default = deviceToAPI; +exports.default = deviceToAPI; \ No newline at end of file diff --git a/dist/lib/eventToApi.js b/dist/lib/eventToApi.js index 80a3b835..10e30672 100644 --- a/dist/lib/eventToApi.js +++ b/dist/lib/eventToApi.js @@ -1,15 +1,15 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const eventToApi = function eventToApi(event) { +var eventToApi = function eventToApi(event) { return { coreid: event.deviceID || null, data: event.data || null, published_at: event.publishedAt, - ttl: event.ttl, + ttl: event.ttl }; }; -exports.default = eventToApi; +exports.default = eventToApi; \ No newline at end of file diff --git a/dist/lib/logger.js b/dist/lib/logger.js index 8d78a751..17958cd0 100644 --- a/dist/lib/logger.js +++ b/dist/lib/logger.js @@ -1,16 +1,16 @@ +"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require("babel-runtime/helpers/createClass"); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -31,33 +31,33 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * * You can download the source here: https://github.com/spark/spark-server * -* +* * */ -const Logger = (function () { +var Logger = function () { function Logger() { (0, _classCallCheck3.default)(this, Logger); } (0, _createClass3.default)(Logger, null, [{ - key: 'log', + key: "log", value: function log() { - let _console; + var _console; // eslint-disable-next-line prefer-rest-params (_console = console).log.apply(_console, arguments); - }, + } }, { - key: 'error', + key: "error", value: function error() { - let _console2; + var _console2; // eslint-disable-next-line prefer-rest-params (_console2 = console).error.apply(_console2, arguments); - }, + } }]); return Logger; -}()); +}(); -exports.default = Logger; +exports.default = Logger; \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index 28a70416..bb0d541d 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,44 +1,44 @@ +'use strict'; +var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); -const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); +var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); -const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); +var _entries = require('babel-runtime/core-js/object/entries'); -const _entries = require('babel-runtime/core-js/object/entries'); +var _entries2 = _interopRequireDefault(_entries); -const _entries2 = _interopRequireDefault(_entries); +var _constitute = require('constitute'); -const _constitute = require('constitute'); +var _os = require('os'); -const _os = require('os'); +var _os2 = _interopRequireDefault(_os); -const _os2 = _interopRequireDefault(_os); +var _arrayFlatten = require('array-flatten'); -const _arrayFlatten = require('array-flatten'); +var _arrayFlatten2 = _interopRequireDefault(_arrayFlatten); -const _arrayFlatten2 = _interopRequireDefault(_arrayFlatten); +var _logger = require('./lib/logger'); -const _logger = require('./lib/logger'); +var _logger2 = _interopRequireDefault(_logger); -const _logger2 = _interopRequireDefault(_logger); +var _app = require('./app'); -const _app = require('./app'); +var _app2 = _interopRequireDefault(_app); -const _app2 = _interopRequireDefault(_app); +var _defaultBindings = require('./defaultBindings'); -const _defaultBindings = require('./defaultBindings'); +var _defaultBindings2 = _interopRequireDefault(_defaultBindings); -const _defaultBindings2 = _interopRequireDefault(_defaultBindings); +var _settings = require('./settings'); -const _settings = require('./settings'); - -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const NODE_PORT = process.env.NODE_PORT || 8080; +var NODE_PORT = process.env.NODE_PORT || 8080; -process.on('uncaughtException', (exception) => { +process.on('uncaughtException', function (exception) { _logger2.default.error('uncaughtException', { message: exception.message, stack: exception.stack }); // logging with MetaData process.exit(1); // exit with failure }); @@ -54,23 +54,31 @@ process.on('uncaughtException', (exception) => { * * See https://github.com/justmoon/constitute for more info */ -const container = new _constitute.Container(); +var container = new _constitute.Container(); (0, _defaultBindings2.default)(container); -const deviceServer = container.constitute('DeviceServer'); +var deviceServer = container.constitute('DeviceServer'); deviceServer.start(); -const app = (0, _app2.default)(container, _settings2.default); +var app = (0, _app2.default)(container, _settings2.default); -app.listen(NODE_PORT, () => console.log(`express server started on port ${NODE_PORT}`)); +app.listen(NODE_PORT, function () { + return console.log('express server started on port ' + NODE_PORT); +}); -const addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map( +var addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map( // eslint-disable-next-line no-unused-vars -(_ref) => { - let _ref2 = (0, _slicedToArray3.default)(_ref, 2), - name = _ref2[0], - nic = _ref2[1]; - - return nic.filter(address => address.family === 'IPv4' && address.address !== '127.0.0.1').map(address => address.address); +function (_ref) { + var _ref2 = (0, _slicedToArray3.default)(_ref, 2), + name = _ref2[0], + nic = _ref2[1]; + + return nic.filter(function (address) { + return address.family === 'IPv4' && address.address !== '127.0.0.1'; + }).map(function (address) { + return address.address; + }); })); -addresses.forEach(address => console.log(`Your device server IP address is: ${address}`)); +addresses.forEach(function (address) { + return console.log('Your device server IP address is: ' + address); +}); \ No newline at end of file diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js index bc9a550a..15a888ea 100644 --- a/dist/managers/DeviceManager.js +++ b/dist/managers/DeviceManager.js @@ -1,53 +1,52 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); +var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); -const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); +var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _ursa = require('ursa'); +var _ursa = require('ursa'); -const _ursa2 = _interopRequireDefault(_ursa); +var _ursa2 = _interopRequireDefault(_ursa); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer) { - const _this = this; +var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer) { + var _this = this; (0, _classCallCheck3.default)(this, DeviceManager); - this.claimDevice = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) { - let deviceAttributes, - attributesToSave; - return _regenerator2.default.wrap((_context) => { + this.claimDevice = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) { + var deviceAttributes, attributesToSave; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -74,7 +73,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 7: attributesToSave = (0, _extends3.default)({}, deviceAttributes, { - ownerID: userID, + ownerID: userID }); _context.next = 10; return _this._deviceAttributeRepository.update(attributesToSave); @@ -93,13 +92,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x, _x2) { return _ref.apply(this, arguments); }; - }()); + }(); - this.unclaimDevice = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID, userID) { - let deviceAttributes, - attributesToSave; - return _regenerator2.default.wrap((_context2) => { + this.unclaimDevice = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID, userID) { + var deviceAttributes, attributesToSave; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -118,7 +116,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 5: attributesToSave = (0, _extends3.default)({}, deviceAttributes, { - ownerID: null, + ownerID: null }); _context2.next = 8; return _this._deviceAttributeRepository.update(attributesToSave); @@ -137,13 +135,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x3, _x4) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.getByID = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, userID) { - let attributes, - device; - return _regenerator2.default.wrap((_context3) => { + this.getByID = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, userID) { + var attributes, device; + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -165,7 +162,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return _context3.abrupt('return', (0, _extends3.default)({}, attributes, { connected: device && device.ping().connected || false, lastFlashedAppName: null, - lastHeard: device && device.ping().lastPing || attributes.lastHeard, + lastHeard: device && device.ping().lastPing || attributes.lastHeard })); case 7: @@ -179,17 +176,13 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x5, _x6) { return _ref3.apply(this, arguments); }; - }()); + }(); - this.getDetailsByID = (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, userID) { - let device, - _ref5, - _ref6, - attributes, - description; + this.getDetailsByID = function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, userID) { + var device, _ref5, _ref6, attributes, description; - return _regenerator2.default.wrap((_context4) => { + return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: @@ -216,7 +209,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi functions: description ? description.state.f : null, lastFlashedAppName: null, lastHeard: device && device.ping().lastPing || attributes.lastHeard, - variables: description ? description.state.v : null, + variables: description ? description.state.v : null })); case 10: @@ -230,13 +223,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x7, _x8) { return _ref4.apply(this, arguments); }; - }()); + }(); - this.getAll = (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(userID) { - let devicesAttributes, - devicePromises; - return _regenerator2.default.wrap((_context6) => { + this.getAll = function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(userID) { + var devicesAttributes, devicePromises; + return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: @@ -246,9 +238,9 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 2: devicesAttributes = _context6.sent; devicePromises = devicesAttributes.map(function () { - const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) { - let device; - return _regenerator2.default.wrap((_context5) => { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) { + var device; + return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: @@ -256,7 +248,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return _context5.abrupt('return', (0, _extends3.default)({}, attributes, { connected: device && device.ping().connected || false, lastFlashedAppName: null, - lastHeard: device && device.ping().lastPing || attributes.lastHeard, + lastHeard: device && device.ping().lastPing || attributes.lastHeard })); case 2: @@ -284,13 +276,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x9) { return _ref7.apply(this, arguments); }; - }()); + }(); - this.callFunction = (function () { - const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, userID, functionName, functionArguments) { - let doesUserHaveAccess, - device; - return _regenerator2.default.wrap((_context7) => { + this.callFunction = function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, userID, functionName, functionArguments) { + var doesUserHaveAccess, device; + return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: @@ -335,13 +326,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x11, _x12, _x13, _x14) { return _ref9.apply(this, arguments); }; - }()); + }(); - this.getVariableValue = (function () { - const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, userID, varName) { - let doesUserHaveAccess, - device; - return _regenerator2.default.wrap((_context8) => { + this.getVariableValue = function () { + var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, userID, varName) { + var doesUserHaveAccess, device; + return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: @@ -386,12 +376,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x15, _x16, _x17) { return _ref10.apply(this, arguments); }; - }()); + }(); - this.flashBinary = (function () { - const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) { - let device; - return _regenerator2.default.wrap((_context9) => { + this.flashBinary = function () { + var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) { + var device; + return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: @@ -422,13 +412,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x18, _x19) { return _ref11.apply(this, arguments); }; - }()); + }(); - this.flashKnownApp = (function () { - const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, userID, appName) { - let knownFirmware, - device; - return _regenerator2.default.wrap((_context10) => { + this.flashKnownApp = function () { + var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, userID, appName) { + var knownFirmware, device; + return _regenerator2.default.wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { case 0: @@ -451,7 +440,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi break; } - throw new _HttpError2.default(`No firmware ${appName} found`, 404); + throw new _HttpError2.default('No firmware ' + appName + ' found', 404); case 7: device = _this._deviceServer.getDevice(deviceID); @@ -481,14 +470,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x20, _x21, _x22) { return _ref12.apply(this, arguments); }; - }()); - - this.provision = (function () { - const _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey) { - let createdKey, - existingAttributes, - attributes; - return _regenerator2.default.wrap((_context11) => { + }(); + + this.provision = function () { + var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey) { + var createdKey, existingAttributes, attributes; + return _regenerator2.default.wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: @@ -508,8 +495,8 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 6: _context11.prev = 6; - _context11.t0 = _context11.catch(0); - throw new _HttpError2.default(`Key error ${_context11.t0}`); + _context11.t0 = _context11['catch'](0); + throw new _HttpError2.default('Key error ' + _context11.t0); case 9: _context11.next = 11; @@ -522,11 +509,11 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 13: existingAttributes = _context11.sent; attributes = (0, _extends3.default)({ - deviceID, + deviceID: deviceID }, existingAttributes, { ownerID: userID, registrar: userID, - timestamp: new Date(), + timestamp: new Date() }); _context11.next = 17; return _this._deviceAttributeRepository.update(attributes); @@ -549,12 +536,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x23, _x24, _x25) { return _ref13.apply(this, arguments); }; - }()); + }(); - this.raiseYourHand = (function () { - const _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, userID, shouldShowSignal) { - let device; - return _regenerator2.default.wrap((_context12) => { + this.raiseYourHand = function () { + var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, userID, shouldShowSignal) { + var device; + return _regenerator2.default.wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { case 0: @@ -597,13 +584,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x26, _x27, _x28) { return _ref14.apply(this, arguments); }; - }()); + }(); - this.renameDevice = (function () { - const _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, userID, name) { - let attributes, - attributesToSave; - return _regenerator2.default.wrap((_context13) => { + this.renameDevice = function () { + var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, userID, name) { + var attributes, attributesToSave; + return _regenerator2.default.wrap(function _callee13$(_context13) { while (1) { switch (_context13.prev = _context13.next) { case 0: @@ -622,7 +608,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 5: attributesToSave = (0, _extends3.default)({}, attributes, { - name, + name: name }); _context13.next = 8; return _this._deviceAttributeRepository.update(attributesToSave); @@ -641,7 +627,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x29, _x30, _x31) { return _ref15.apply(this, arguments); }; - }()); + }(); this._deviceAttributeRepository = deviceAttributeRepository; this._deviceFirmwareRepository = deviceFirmwareRepository; @@ -649,4 +635,4 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi this._deviceServer = deviceServer; }; -exports.default = DeviceManager; +exports.default = DeviceManager; \ No newline at end of file diff --git a/dist/managers/EventManager.js b/dist/managers/EventManager.js index 44c33acb..08436883 100644 --- a/dist/managers/EventManager.js +++ b/dist/managers/EventManager.js @@ -1,17 +1,17 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const EventManager = function EventManager(eventPublisher) { - const _this = this; +var EventManager = function EventManager(eventPublisher) { + var _this = this; (0, _classCallCheck3.default)(this, EventManager); @@ -30,4 +30,4 @@ const EventManager = function EventManager(eventPublisher) { this._eventPublisher = eventPublisher; }; -exports.default = EventManager; +exports.default = EventManager; \ No newline at end of file diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js index 1766ca29..d124a01d 100644 --- a/dist/managers/FirmwareCompilationManager.js +++ b/dist/managers/FirmwareCompilationManager.js @@ -1,84 +1,84 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _stringify = require('babel-runtime/core-js/json/stringify'); +var _stringify = require('babel-runtime/core-js/json/stringify'); -const _stringify2 = _interopRequireDefault(_stringify); +var _stringify2 = _interopRequireDefault(_stringify); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _map = require('babel-runtime/core-js/map'); +var _map = require('babel-runtime/core-js/map'); -const _map2 = _interopRequireDefault(_map); +var _map2 = _interopRequireDefault(_map); -const _crypto = require('crypto'); +var _crypto = require('crypto'); -const _crypto2 = _interopRequireDefault(_crypto); +var _crypto2 = _interopRequireDefault(_crypto); -const _fs = require('fs'); +var _fs = require('fs'); -const _fs2 = _interopRequireDefault(_fs); +var _fs2 = _interopRequireDefault(_fs); -const _path = require('path'); +var _path = require('path'); -const _path2 = _interopRequireDefault(_path); +var _path2 = _interopRequireDefault(_path); -const _mkdirp = require('mkdirp'); +var _mkdirp = require('mkdirp'); -const _mkdirp2 = _interopRequireDefault(_mkdirp); +var _mkdirp2 = _interopRequireDefault(_mkdirp); -const _rmfr = require('rmfr'); +var _rmfr = require('rmfr'); -const _rmfr2 = _interopRequireDefault(_rmfr); +var _rmfr2 = _interopRequireDefault(_rmfr); -const _child_process = require('child_process'); +var _child_process = require('child_process'); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _settings = require('../settings'); +var _settings = require('../settings'); -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const IS_COMPILATION_ENABLED = _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); +var IS_COMPILATION_ENABLED = _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); -const USER_APP_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications'); -const BIN_PATH = _path2.default.join(_settings2.default.BUILD_DIRECTORY, 'bin'); -const MAKE_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'main'); +var USER_APP_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications'); +var BIN_PATH = _path2.default.join(_settings2.default.BUILD_DIRECTORY, 'bin'); +var MAKE_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'main'); -const FILE_NAME_BY_KEY = new _map2.default(); +var FILE_NAME_BY_KEY = new _map2.default(); -const getKey = function getKey() { +var getKey = function getKey() { return _crypto2.default.randomBytes(24).toString('hex').substring(0, 24); }; -const getUniqueKey = function getUniqueKey() { - let key = getKey(); +var getUniqueKey = function getUniqueKey() { + var key = getKey(); while (FILE_NAME_BY_KEY.has(key)) { key = getKey(); } return key; }; -const FirmwareCompilationManager = function FirmwareCompilationManager() { +var FirmwareCompilationManager = function FirmwareCompilationManager() { (0, _classCallCheck3.default)(this, FirmwareCompilationManager); }; @@ -91,12 +91,14 @@ FirmwareCompilationManager.getBinaryForID = function (id) { return null; } - const binaryPath = _path2.default.join(BIN_PATH, id); + var binaryPath = _path2.default.join(BIN_PATH, id); if (!_fs2.default.existsSync(binaryPath)) { return null; } - const binFileName = _fs2.default.readdirSync(binaryPath).find(file => file.endsWith('.bin')); + var binFileName = _fs2.default.readdirSync(binaryPath).find(function (file) { + return file.endsWith('.bin'); + }); if (!binFileName) { return null; @@ -105,19 +107,10 @@ FirmwareCompilationManager.getBinaryForID = function (id) { return _fs2.default.readFileSync(_path2.default.join(binaryPath, binFileName)); }; -FirmwareCompilationManager.compileSource = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(platformID, files) { - let platformName, - appFolder, - appPath, - id, - binPath, - makeProcess, - errors, - sizeInfo, - date, - config; - return _regenerator2.default.wrap((_context) => { +FirmwareCompilationManager.compileSource = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(platformID, files) { + var platformName, appFolder, appPath, id, binPath, makeProcess, errors, sizeInfo, date, config; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -141,19 +134,19 @@ FirmwareCompilationManager.compileSource = (function () { case 5: platformName = platformName.toLowerCase(); - appFolder = (`${platformName}_firmware_${new Date().getTime()}`).toLowerCase(); + appFolder = (platformName + '_firmware_' + new Date().getTime()).toLowerCase(); appPath = _path2.default.join(USER_APP_PATH, appFolder); _mkdirp2.default.sync(appPath); - files.forEach((file) => { - const fileName = file.originalname; - const fileExtension = _path2.default.extname(fileName); - let iterator = 0; - let combinedPath = _path2.default.join(appPath, fileName); + files.forEach(function (file) { + var fileName = file.originalname; + var fileExtension = _path2.default.extname(fileName); + var iterator = 0; + var combinedPath = _path2.default.join(appPath, fileName); while (_fs2.default.existsSync(combinedPath)) { - combinedPath = _path2.default.join(appPath, `${_path2.default.basename(fileName, fileExtension)}_${iterator++}${fileExtension}`); + combinedPath = _path2.default.join(appPath, '' + _path2.default.basename(fileName, fileExtension) + ('_' + iterator++ + fileExtension)); } _fs2.default.writeFileSync(combinedPath, file.buffer); @@ -161,18 +154,18 @@ FirmwareCompilationManager.compileSource = (function () { id = getUniqueKey(); binPath = _path2.default.join(BIN_PATH, id); - makeProcess = (0, _child_process.spawn)('make', [`APP=${appFolder}`, `PLATFORM_ID=${platformID}`, `TARGET_DIR=${_path2.default.relative(MAKE_PATH, binPath).replace(/\\/g, '/')}`], { cwd: MAKE_PATH }); + makeProcess = (0, _child_process.spawn)('make', ['APP=' + appFolder, 'PLATFORM_ID=' + platformID, 'TARGET_DIR=' + _path2.default.relative(MAKE_PATH, binPath).replace(/\\/g, '/')], { cwd: MAKE_PATH }); errors = []; - makeProcess.stderr.on('data', (data) => { - console.log(`${data}`); - errors.push(`${data}`); + makeProcess.stderr.on('data', function (data) { + console.log('' + data); + errors.push('' + data); }); sizeInfo = 'not implemented'; - makeProcess.stdout.on('data', (data) => { - const output = `${data}`; + makeProcess.stdout.on('data', function (data) { + var output = '' + data; if (output.includes('text\t')) { sizeInfo = output; @@ -180,8 +173,10 @@ FirmwareCompilationManager.compileSource = (function () { }); _context.next = 19; - return new _promise2.default((resolve) => { - makeProcess.on('exit', () => resolve()); + return new _promise2.default(function (resolve) { + makeProcess.on('exit', function () { + return resolve(); + }); }); case 19: @@ -190,13 +185,13 @@ FirmwareCompilationManager.compileSource = (function () { date.setDate(date.getDate() + 1); config = { binary_id: id, - errors, + errors: errors, // expire in one day expires_at: date, // TODO: this variable has a bunch of extra crap including file names. // we should filter out the string to only show the file sizes - sizeInfo, + sizeInfo: sizeInfo }; @@ -215,16 +210,18 @@ FirmwareCompilationManager.compileSource = (function () { return function (_x, _x2) { return _ref.apply(this, arguments); }; -}()); +}(); FirmwareCompilationManager.addFirmwareCleanupTask = function (appFolderPath, config) { - const configPath = _path2.default.join(appFolderPath, 'config.json'); + var configPath = _path2.default.join(appFolderPath, 'config.json'); if (!_fs2.default.existsSync(configPath)) { _fs2.default.writeFileSync(configPath, (0, _stringify2.default)(config)); } - const currentDate = new Date(); - const difference = new Date(config.expires_at).getTime() - currentDate.getTime(); - setTimeout(() => (0, _rmfr2.default)(appFolderPath), difference); + var currentDate = new Date(); + var difference = new Date(config.expires_at).getTime() - currentDate.getTime(); + setTimeout(function () { + return (0, _rmfr2.default)(appFolderPath); + }, difference); }; if (IS_COMPILATION_ENABLED) { @@ -236,18 +233,18 @@ if (IS_COMPILATION_ENABLED) { _mkdirp2.default.sync(BIN_PATH); } - _fs2.default.readdirSync(USER_APP_PATH).forEach((file) => { - const appFolder = _path2.default.join(USER_APP_PATH, file); - const configPath = _path2.default.join(appFolder, 'config.json'); + _fs2.default.readdirSync(USER_APP_PATH).forEach(function (file) { + var appFolder = _path2.default.join(USER_APP_PATH, file); + var configPath = _path2.default.join(appFolder, 'config.json'); if (!_fs2.default.existsSync(configPath)) { return; } - const configString = _fs2.default.readFileSync(configPath, 'utf8'); + var configString = _fs2.default.readFileSync(configPath, 'utf8'); if (!configString) { return; } - const config = JSON.parse(configString); + var config = JSON.parse(configString); if (config.expires_at < new Date()) { // TODO - clean up artifacts in the firmware folder. Every binary will have // files in firmare/build/target/user & firmware/build/target/user-part @@ -260,4 +257,4 @@ if (IS_COMPILATION_ENABLED) { }); } -exports.default = FirmwareCompilationManager; +exports.default = FirmwareCompilationManager; \ No newline at end of file diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js index b93dbcf0..77ff0346 100644 --- a/dist/managers/WebhookManager.js +++ b/dist/managers/WebhookManager.js @@ -1,68 +1,68 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _typeof2 = require('babel-runtime/helpers/typeof'); +var _typeof2 = require('babel-runtime/helpers/typeof'); -const _typeof3 = _interopRequireDefault(_typeof2); +var _typeof3 = _interopRequireDefault(_typeof2); -const _stringify = require('babel-runtime/core-js/json/stringify'); +var _stringify = require('babel-runtime/core-js/json/stringify'); -const _stringify2 = _interopRequireDefault(_stringify); +var _stringify2 = _interopRequireDefault(_stringify); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _map = require('babel-runtime/core-js/map'); +var _map = require('babel-runtime/core-js/map'); -const _map2 = _interopRequireDefault(_map); +var _map2 = _interopRequireDefault(_map); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _hogan = require('hogan.js'); +var _hogan = require('hogan.js'); -const _hogan2 = _interopRequireDefault(_hogan); +var _hogan2 = _interopRequireDefault(_hogan); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _logger = require('../lib/logger'); +var _logger = require('../lib/logger'); -const _logger2 = _interopRequireDefault(_logger); +var _logger2 = _interopRequireDefault(_logger); -const _nullthrows = require('nullthrows'); +var _nullthrows = require('nullthrows'); -const _nullthrows2 = _interopRequireDefault(_nullthrows); +var _nullthrows2 = _interopRequireDefault(_nullthrows); -const _request = require('request'); +var _request = require('request'); -const _request2 = _interopRequireDefault(_request); +var _request2 = _interopRequireDefault(_request); -const _throttle = require('lodash/throttle'); +var _throttle = require('lodash/throttle'); -const _throttle2 = _interopRequireDefault(_throttle); +var _throttle2 = _interopRequireDefault(_throttle); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const parseEventData = function parseEventData(event) { +var parseEventData = function parseEventData(event) { try { if (event.data) { return JSON.parse(event.data); @@ -73,9 +73,9 @@ const parseEventData = function parseEventData(event) { } }; -const splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { - const chunks = []; - let ii = 0; +var splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { + var chunks = []; + var ii = 0; while (ii < buffer.length) { chunks.push(buffer.slice(ii, ii += chunkSize)); } @@ -83,22 +83,22 @@ const splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) return chunks; }; -const MAX_WEBHOOK_ERRORS_COUNT = 10; -const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; -const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; -const MAX_RESPONSE_MESSAGE_SIZE = 100000; +var MAX_WEBHOOK_ERRORS_COUNT = 10; +var WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; +var MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; +var MAX_RESPONSE_MESSAGE_SIZE = 100000; -const WebhookManager = function WebhookManager(webhookRepository, eventPublisher) { - const _this = this; +var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) { + var _this = this; (0, _classCallCheck3.default)(this, WebhookManager); this._subscriptionIDsByWebhookID = new _map2.default(); this._errorsCountByWebhookID = new _map2.default(); - this.create = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) { - let webhook; - return _regenerator2.default.wrap((_context) => { + this.create = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) { + var webhook; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -122,12 +122,12 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x) { return _ref.apply(this, arguments); }; - }()); + }(); - this.deleteByID = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID, userID) { - let webhook; - return _regenerator2.default.wrap((_context2) => { + this.deleteByID = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID, userID) { + var webhook; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -162,11 +162,11 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x2, _x3) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.getAll = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID) { - return _regenerator2.default.wrap((_context3) => { + this.getAll = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID) { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -187,12 +187,12 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x4) { return _ref3.apply(this, arguments); }; - }()); + }(); - this.getByID = (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID, userID) { - let webhook; - return _regenerator2.default.wrap((_context4) => { + this.getByID = function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID, userID) { + var webhook; + return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: @@ -223,11 +223,11 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x5, _x6) { return _ref4.apply(this, arguments); }; - }()); + }(); this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { - let allWebhooks; - return _regenerator2.default.wrap((_context5) => { + var allWebhooks; + return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: @@ -237,7 +237,9 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher case 2: allWebhooks = _context5.sent; - allWebhooks.forEach(webhook => _this._subscribeWebhook(webhook)); + allWebhooks.forEach(function (webhook) { + return _this._subscribeWebhook(webhook); + }); case 4: case 'end': @@ -248,16 +250,16 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher })); this._subscribeWebhook = function (webhook) { - const subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), { + var subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), { deviceID: webhook.deviceID, mydevices: webhook.mydevices, - userID: webhook.ownerID, + userID: webhook.ownerID }); _this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID); }; this._unsubscribeWebhookByID = function (webhookID) { - const subscriptionID = _this._subscriptionIDsByWebhookID.get(webhookID); + var subscriptionID = _this._subscriptionIDsByWebhookID.get(webhookID); if (!subscriptionID) { return; } @@ -269,7 +271,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher this._onNewWebhookEvent = function (webhook) { return function (event) { try { - const webhookErrorCount = _this._errorsCountByWebhookID.get(webhook.id) || 0; + var webhookErrorCount = _this._errorsCountByWebhookID.get(webhook.id) || 0; if (webhookErrorCount < MAX_WEBHOOK_ERRORS_COUNT) { _this.runWebhook(webhook, event); @@ -280,40 +282,28 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher data: 'Too many errors, webhook disabled', isPublic: false, name: _this._compileErrorResponseTopic(webhook, event), - userID: event.userID, + userID: event.userID }); _this.runWebhookThrottled(webhook, event); } catch (error) { - _logger2.default.error(`webhookError: ${error}`); + _logger2.default.error('webhookError: ' + error); } }; }; - this.runWebhook = (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(webhook, event) { - let _ret; + this.runWebhook = function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(webhook, event) { + var _ret; - return _regenerator2.default.wrap((_context7) => { + return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: _context7.prev = 0; return _context7.delegateYield(_regenerator2.default.mark(function _callee6() { - let webhookVariablesObject, - requestJson, - requestFormData, - requestUrl, - requestQuery, - responseTopic, - isJsonRequest, - requestOptions, - responseBody, - isResponseBodyAnObject, - responseTemplate, - responseEventData, - chunks; - return _regenerator2.default.wrap((_context6) => { + var webhookVariablesObject, requestJson, requestFormData, requestUrl, requestQuery, responseTopic, isJsonRequest, requestOptions, responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks; + return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: @@ -333,7 +323,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher method: webhook.requestType, qs: requestQuery, strictSSL: webhook.rejectUnauthorized, - url: (0, _nullthrows2.default)(requestUrl), + url: (0, _nullthrows2.default)(requestUrl) }; _context6.next = 10; return _this._callWebhook(webhook, event, requestOptions); @@ -347,7 +337,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher } return _context6.abrupt('return', { - v: void 0, + v: void 0 }); case 13: @@ -357,14 +347,14 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher chunks = splitBufferIntoChunks(Buffer.from(responseEventData).slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE); - chunks.forEach((chunk, index) => { - const responseEventName = responseTopic && `${responseTopic}/${index}` || `hook-response/${event.name}/${index}`; + chunks.forEach(function (chunk, index) { + var responseEventName = responseTopic && responseTopic + '/' + index || 'hook-response/' + event.name + '/' + index; _this._eventPublisher.publish({ data: chunk, isPublic: false, name: responseEventName, - userID: event.userID, + userID: event.userID }); }); @@ -379,7 +369,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher case 2: _ret = _context7.t0; - if (!((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === 'object')) { + if (!((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === "object")) { _context7.next = 5; break; } @@ -392,9 +382,9 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher case 7: _context7.prev = 7; - _context7.t1 = _context7.catch(0); + _context7.t1 = _context7['catch'](0); - _logger2.default.error(`webhookError: ${_context7.t1}`); + _logger2.default.error('webhookError: ' + _context7.t1); case 10: case 'end': @@ -407,48 +397,50 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x7, _x8) { return _ref6.apply(this, arguments); }; - }()); + }(); this.runWebhookThrottled = (0, _throttle2.default)(this.runWebhook, WEBHOOK_THROTTLE_TIME, { leading: false, trailing: true }); this._callWebhook = function (webhook, event, requestOptions) { - return new _promise2.default((resolve, reject) => (0, _request2.default)(requestOptions, (error, response, responseBody) => { - const onResponseError = function onResponseError(errorMessage) { - _this._incrementWebhookErrorCounter(webhook.id); + return new _promise2.default(function (resolve, reject) { + return (0, _request2.default)(requestOptions, function (error, response, responseBody) { + var onResponseError = function onResponseError(errorMessage) { + _this._incrementWebhookErrorCounter(webhook.id); + + _this._eventPublisher.publish({ + data: errorMessage, + isPublic: false, + name: _this._compileErrorResponseTopic(webhook, event), + userID: event.userID + }); + + reject(new Error(errorMessage)); + }; + + if (error) { + onResponseError(error.message); + return; + } + if (response.statusCode >= 400) { + onResponseError(response.statusMessage); + return; + } + + _this._resetWebhookErrorCounter(webhook.id); _this._eventPublisher.publish({ - data: errorMessage, isPublic: false, - name: _this._compileErrorResponseTopic(webhook, event), - userID: event.userID, + name: 'hook-sent/' + event.name, + userID: event.userID }); - reject(new Error(errorMessage)); - }; - - if (error) { - onResponseError(error.message); - return; - } - if (response.statusCode >= 400) { - onResponseError(response.statusMessage); - return; - } - - _this._resetWebhookErrorCounter(webhook.id); - - _this._eventPublisher.publish({ - isPublic: false, - name: `hook-sent/${event.name}`, - userID: event.userID, + resolve(responseBody); }); - - resolve(responseBody); - })); + }); }; this._getEventVariables = function (event) { - const defaultWebhookVariables = { + var defaultWebhookVariables = { PARTICLE_DEVICE_ID: event.deviceID, PARTICLE_EVENT_NAME: event.name, PARTICLE_EVENT_VALUE: event.data, @@ -457,20 +449,20 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher SPARK_CORE_ID: event.deviceID, SPARK_EVENT_NAME: event.name, SPARK_EVENT_VALUE: event.data, - SPARK_PUBLISHED_AT: event.publishedAt, + SPARK_PUBLISHED_AT: event.publishedAt }; - const eventDataVariables = parseEventData(event); + var eventDataVariables = parseEventData(event); return (0, _extends3.default)({}, defaultWebhookVariables, eventDataVariables); }; this._getRequestData = function (customData, event, noDefaults) { - const defaultEventData = { + var defaultEventData = { coreid: event.deviceID, data: event.data, event: event.name, - published_at: event.publishedAt, + published_at: event.publishedAt }; return noDefaults ? customData : (0, _extends3.default)({}, defaultEventData, customData || {}); @@ -485,7 +477,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return undefined; } - const compiledTemplate = _this._compileTemplate((0, _stringify2.default)(template), variables); + var compiledTemplate = _this._compileTemplate((0, _stringify2.default)(template), variables); if (!compiledTemplate) { return undefined; } @@ -494,12 +486,12 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher }; this._compileErrorResponseTopic = function (webhook, event) { - const variables = _this._getEventVariables(event); - return _this._compileTemplate(webhook.errorResponseTopic, variables) || `hook-error/${event.name}`; + var variables = _this._getEventVariables(event); + return _this._compileTemplate(webhook.errorResponseTopic, variables) || 'hook-error/' + event.name; }; this._incrementWebhookErrorCounter = function (webhookID) { - const errorsCount = _this._errorsCountByWebhookID.get(webhookID) || 0; + var errorsCount = _this._errorsCountByWebhookID.get(webhookID) || 0; _this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); }; @@ -511,7 +503,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher this._eventPublisher = eventPublisher; (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { - return _regenerator2.default.wrap((_context8) => { + return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: @@ -530,4 +522,4 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher }))(); }; -exports.default = WebhookManager; +exports.default = WebhookManager; \ No newline at end of file diff --git a/dist/repository/DeviceFirmwareFileRepository.js b/dist/repository/DeviceFirmwareFileRepository.js index 7255f1b7..23239fda 100644 --- a/dist/repository/DeviceFirmwareFileRepository.js +++ b/dist/repository/DeviceFirmwareFileRepository.js @@ -1,33 +1,30 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -let _dec, - _desc, - _value, - _class; +var _dec, _desc, _value, _class; -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -37,7 +34,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -52,7 +51,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], { promise: false }), (_class = (function () { +var DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], { promise: false }), (_class = function () { function DeviceFirmwareFileRepository(path) { (0, _classCallCheck3.default)(this, DeviceFirmwareFileRepository); @@ -62,9 +61,9 @@ const DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], (0, _createClass3.default)(DeviceFirmwareFileRepository, [{ key: 'getByName', value: function getByName(appName) { - return this._fileManager.getFileBuffer(`${appName}.bin`); - }, + return this._fileManager.getFileBuffer(appName + '.bin'); + } }]); return DeviceFirmwareFileRepository; -}()), (_applyDecoratedDescriptor(_class.prototype, 'getByName', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByName'), _class.prototype)), _class)); -exports.default = DeviceFirmwareFileRepository; +}(), (_applyDecoratedDescriptor(_class.prototype, 'getByName', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByName'), _class.prototype)), _class)); +exports.default = DeviceFirmwareFileRepository; \ No newline at end of file diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js index 33353a83..ff6f94a6 100644 --- a/dist/repository/UserFileRepository.js +++ b/dist/repository/UserFileRepository.js @@ -1,67 +1,58 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); +var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); -const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); +var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _desc, _value, _class; -const _uuid = require('uuid'); +var _uuid = require('uuid'); -const _uuid2 = _interopRequireDefault(_uuid); +var _uuid2 = _interopRequireDefault(_uuid); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _PasswordHasher = require('../lib/PasswordHasher'); +var _PasswordHasher = require('../lib/PasswordHasher'); -const _PasswordHasher2 = _interopRequireDefault(_PasswordHasher); +var _PasswordHasher2 = _interopRequireDefault(_PasswordHasher); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -71,7 +62,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -86,20 +79,16 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(['id']), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = (function () { +var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(['id']), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = function () { function UserFileRepository(path) { - const _this = this; + var _this = this; (0, _classCallCheck3.default)(this, UserFileRepository); - this.createWithCredentials = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { - let username, - password, - salt, - passwordHash, - modelToSave; - return _regenerator2.default.wrap((_context) => { + this.createWithCredentials = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + var username, password, salt, passwordHash, modelToSave; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -116,9 +105,9 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, passwordHash = _context.sent; modelToSave = { accessTokens: [], - passwordHash, - salt, - username, + passwordHash: passwordHash, + salt: salt, + username: username }; _context.next = 10; return _this.create(modelToSave); @@ -137,13 +126,12 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x) { return _ref.apply(this, arguments); }; - }()); + }(); - this.validateLogin = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { - let user, - hash; - return _regenerator2.default.wrap((_context2) => { + this.validateLogin = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + var user, hash; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -180,7 +168,7 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 14: _context2.prev = 14; - _context2.t0 = _context2.catch(0); + _context2.t0 = _context2['catch'](0); throw _context2.t0; case 17: @@ -194,11 +182,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x2, _x3) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.getByAccessToken = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(accessToken) { - return _regenerator2.default.wrap((_context3) => { + this.getByAccessToken = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(accessToken) { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -207,7 +195,9 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 2: _context3.t0 = function (user) { - return user.accessTokens.some(tokenObject => tokenObject.accessToken === accessToken); + return user.accessTokens.some(function (tokenObject) { + return tokenObject.accessToken === accessToken; + }); }; return _context3.abrupt('return', _context3.sent.find(_context3.t0)); @@ -223,13 +213,12 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x4) { return _ref3.apply(this, arguments); }; - }()); + }(); - this.deleteAccessToken = (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, token) { - let user, - userToSave; - return _regenerator2.default.wrap((_context4) => { + this.deleteAccessToken = function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, token) { + var user, userToSave; + return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: @@ -248,7 +237,9 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 5: userToSave = (0, _extends3.default)({}, user, { - accessTokens: user.accessTokens.filter(tokenObject => tokenObject.accessToken !== token), + accessTokens: user.accessTokens.filter(function (tokenObject) { + return tokenObject.accessToken !== token; + }) }); _context4.next = 8; return _this.update(userToSave); @@ -264,13 +255,12 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x5, _x6) { return _ref4.apply(this, arguments); }; - }()); + }(); - this.saveAccessToken = (function () { - const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) { - let user, - userToSave; - return _regenerator2.default.wrap((_context5) => { + this.saveAccessToken = function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) { + var user, userToSave; + return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: @@ -289,7 +279,7 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 5: userToSave = (0, _extends3.default)({}, user, { - accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]), + accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]) }); _context5.next = 8; return _this.update(userToSave); @@ -308,17 +298,16 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x7, _x8) { return _ref5.apply(this, arguments); }; - }()); + }(); this._fileManager = new _sparkProtocol.JSONFileManager(path); } (0, _createClass3.default)(UserFileRepository, [{ key: 'create', - value: (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(user) { - let id, - modelToSave; + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(user) { + var id, modelToSave; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { @@ -327,7 +316,7 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 1: _context6.next = 3; - return this._fileManager.hasFile(`${id}.json`); + return this._fileManager.hasFile(id + '.json'); case 3: if (!_context6.sent) { @@ -343,11 +332,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, modelToSave = (0, _extends3.default)({}, user, { created_at: new Date(), created_by: null, - id, + id: id }); - this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + this._fileManager.createFile(modelToSave.id + '.json', modelToSave); return _context6.abrupt('return', modelToSave); case 10: @@ -363,16 +352,16 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return create; - }()), + }() }, { key: 'update', - value: (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(model) { + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(model) { return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: - this._fileManager.writeFile(`${model.id}.json`, model); + this._fileManager.writeFile(model.id + '.json', model); return _context7.abrupt('return', model); case 2: @@ -388,11 +377,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return update; - }()), + }() }, { key: 'getAll', - value: (function () { - const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { + value: function () { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { @@ -412,16 +401,16 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return getAll; - }()), + }() }, { key: 'getById', - value: (function () { - const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) { + value: function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) { return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: - return _context9.abrupt('return', this._fileManager.getFile(`${id}.json`)); + return _context9.abrupt('return', this._fileManager.getFile(id + '.json')); case 1: case 'end': @@ -436,11 +425,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return getById; - }()), + }() }, { key: 'getByUsername', - value: (function () { - const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(username) { + value: function () { + var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(username) { return _regenerator2.default.wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { @@ -468,20 +457,20 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return getByUsername; - }()), + }() // This isn't a good one to memoize as we can't key off user ID and there // isn't a good way to clear the cache. }, { key: 'deleteById', - value: (function () { - const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) { + value: function () { + var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) { return _regenerator2.default.wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: - this._fileManager.deleteFile(`${id}.json`); + this._fileManager.deleteFile(id + '.json'); case 1: case 'end': @@ -496,11 +485,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return deleteById; - }()), + }() }, { key: 'isUserNameInUse', - value: (function () { - const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) { + value: function () { + var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) { return _regenerator2.default.wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { @@ -528,8 +517,8 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return isUserNameInUse; - }()), + }() }]); return UserFileRepository; -}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class)); -exports.default = UserFileRepository; +}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class)); +exports.default = UserFileRepository; \ No newline at end of file diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js index ed4263e6..c2a9da2c 100644 --- a/dist/repository/WebhookFileRepository.js +++ b/dist/repository/WebhookFileRepository.js @@ -1,56 +1,50 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -let _dec, - _dec2, - _dec3, - _dec4, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _desc, _value, _class; -const _uuid = require('uuid'); +var _uuid = require('uuid'); -const _uuid2 = _interopRequireDefault(_uuid); +var _uuid2 = _interopRequireDefault(_uuid); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -60,7 +54,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -75,17 +71,17 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), (_class = (function () { +var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), (_class = function () { function WebhookFileRepository(path) { - const _this = this; + var _this = this; (0, _classCallCheck3.default)(this, WebhookFileRepository); - this.getAll = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { - const userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - let allData; - return _regenerator2.default.wrap((_context) => { + this.getAll = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var allData; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -100,7 +96,9 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = break; } - return _context.abrupt('return', allData.filter(webhook => webhook.ownerID === userID)); + return _context.abrupt('return', allData.filter(function (webhook) { + return webhook.ownerID === userID; + })); case 5: return _context.abrupt('return', allData); @@ -116,13 +114,13 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = return function () { return _ref.apply(this, arguments); }; - }()); + }(); - this.getById = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) { - const userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - let webhook; - return _regenerator2.default.wrap((_context2) => { + this.getById = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) { + var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + var webhook; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -153,11 +151,11 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = return function (_x2) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.update = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { - return _regenerator2.default.wrap((_context3) => { + this.update = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -174,17 +172,16 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = return function (_x4) { return _ref3.apply(this, arguments); }; - }()); + }(); this._fileManager = new _sparkProtocol.JSONFileManager(path); } (0, _createClass3.default)(WebhookFileRepository, [{ key: 'create', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) { - let id, - modelToSave; + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) { + var id, modelToSave; return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -193,7 +190,7 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = case 1: _context4.next = 3; - return this._fileManager.hasFile(`${id}.json`); + return this._fileManager.hasFile(id + '.json'); case 3: if (!_context4.sent) { @@ -208,11 +205,11 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = case 7: modelToSave = (0, _extends3.default)({}, model, { created_at: new Date(), - id, + id: id }); - this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + this._fileManager.createFile(modelToSave.id + '.json', modelToSave); return _context4.abrupt('return', modelToSave); case 10: @@ -228,16 +225,16 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return create; - }()), + }() }, { key: 'deleteById', - value: (function () { - const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) { + value: function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) { return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: - this._fileManager.deleteFile(`${id}.json`); + this._fileManager.deleteFile(id + '.json'); case 1: case 'end': @@ -252,14 +249,14 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return deleteById; - }()), + }() // eslint-disable-next-line no-unused-vars }, { key: '_getAll', - value: (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() { + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() { return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { @@ -279,16 +276,16 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return _getAll; - }()), + }() }, { key: '_getByID', - value: (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) { + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) { return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: - return _context7.abrupt('return', this._fileManager.getFile(`${id}.json`)); + return _context7.abrupt('return', this._fileManager.getFile(id + '.json')); case 1: case 'end': @@ -303,8 +300,8 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return _getByID; - }()), + }() }]); return WebhookFileRepository; -}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class)); -exports.default = WebhookFileRepository; +}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class)); +exports.default = WebhookFileRepository; \ No newline at end of file diff --git a/dist/settings.js b/dist/settings.js index 9b53767c..fd63b211 100644 --- a/dist/settings.js +++ b/dist/settings.js @@ -1,12 +1,12 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _path = require('path'); +var _path = require('path'); -const _path2 = _interopRequireDefault(_path); +var _path2 = _interopRequireDefault(_path); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -28,7 +28,7 @@ exports.default = { LOGIN_ROUTE: '/oauth/token', PORT: 5683, - HOST: 'localhost', + HOST: 'localhost' }; /** * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ * @@ -46,6 +46,6 @@ exports.default = { * * You can download the source here: https://github.com/spark/spark-server * + * * - * - */ + */ \ No newline at end of file diff --git a/dist/types.js b/dist/types.js index 8b137891..a726efc4 100644 --- a/dist/types.js +++ b/dist/types.js @@ -1 +1 @@ - +'use strict'; \ No newline at end of file diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 61e408b1..b3f85d51 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -172,7 +172,8 @@ export default ( error: Error, request: $Request, response: $Response, - next: NextFunction, // eslint-disable-line no-unused-vars + // eslint-disable-next-line no-unused-vars + next: NextFunction, ) => { response .status(400) From cea6b4730fb0a5d7071695602d265aec361de1fb Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 18:39:47 -0800 Subject: [PATCH 326/504] Updating bindings to work with external settings. --- dist/defaultBindings.js | 11 ++++++++++- src/defaultBindings.js | 8 +++++++- test/setup/getDefaultContainer.js | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js index 8522b18a..fe3a8f7f 100644 --- a/dist/defaultBindings.js +++ b/dist/defaultBindings.js @@ -4,6 +4,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); +var _keys = require('babel-runtime/core-js/object/keys'); + +var _keys2 = _interopRequireDefault(_keys); + var _sparkProtocol = require('spark-protocol'); var _DeviceClaimsController = require('./controllers/DeviceClaimsController'); @@ -68,7 +72,12 @@ var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -exports.default = function (container) { +exports.default = function (container, newSettings) { + // Make sure that the spark-server settings match whatever is passed in + (0, _keys2.default)(newSettings).forEach(function (key) { + _settings2.default[key] = newSettings[key]; + }); + // spark protocol container bindings (0, _sparkProtocol.defaultBindings)(container); diff --git a/src/defaultBindings.js b/src/defaultBindings.js index 9ca61adc..89799bb0 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -1,6 +1,7 @@ // @flow import type { Container } from 'constitute'; +import type { Settings } from './types'; import { defaultBindings } from 'spark-protocol'; import DeviceClaimsController from './controllers/DeviceClaimsController'; @@ -19,7 +20,12 @@ import UserFileRepository from './repository/UserFileRepository'; import WebhookFileRepository from './repository/WebhookFileRepository'; import settings from './settings'; -export default (container: Container) => { +export default (container: Container, newSettings: Settings) => { + // Make sure that the spark-server settings match whatever is passed in + Object.keys(newSettings).forEach((key: string) => { + settings[key] = newSettings[key]; + }); + // spark protocol container bindings defaultBindings(container); diff --git a/test/setup/getDefaultContainer.js b/test/setup/getDefaultContainer.js index d03f5889..8eff6ee6 100644 --- a/test/setup/getDefaultContainer.js +++ b/test/setup/getDefaultContainer.js @@ -9,7 +9,7 @@ import DeviceServerMock from './DeviceServerMock'; const container = new Container(); // TODO - we should be creating different bindings per test so we can mock out // different modules to test -defaultBindings(container); +defaultBindings(container, settings); // settings container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); From 0e4c1a8142bc3b195db66480cc8986873dcc1138 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 19:18:45 -0800 Subject: [PATCH 327/504] Passing settings into spark protocol --- dist/defaultBindings.js | 2 +- src/defaultBindings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js index fe3a8f7f..fe4a0cd6 100644 --- a/dist/defaultBindings.js +++ b/dist/defaultBindings.js @@ -79,7 +79,7 @@ exports.default = function (container, newSettings) { }); // spark protocol container bindings - (0, _sparkProtocol.defaultBindings)(container); + (0, _sparkProtocol.defaultBindings)(container, newSettings); // settings container.bindValue('DEVICE_DIRECTORY', _settings2.default.DEVICE_DIRECTORY); diff --git a/src/defaultBindings.js b/src/defaultBindings.js index 89799bb0..2e5fad82 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -27,7 +27,7 @@ export default (container: Container, newSettings: Settings) => { }); // spark protocol container bindings - defaultBindings(container); + defaultBindings(container, newSettings); // settings container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); From 5b496dc1b2d24109f0502729f0ba884b966c3096 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 19:29:32 -0800 Subject: [PATCH 328/504] More work with settings --- dist/main.js | 2 +- src/main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/main.js b/dist/main.js index bb0d541d..fa4fa388 100644 --- a/dist/main.js +++ b/dist/main.js @@ -55,7 +55,7 @@ process.on('uncaughtException', function (exception) { * See https://github.com/justmoon/constitute for more info */ var container = new _constitute.Container(); -(0, _defaultBindings2.default)(container); +(0, _defaultBindings2.default)(container, _settings2.default); var deviceServer = container.constitute('DeviceServer'); deviceServer.start(); diff --git a/src/main.js b/src/main.js index bf60f1bf..4bd6e050 100644 --- a/src/main.js +++ b/src/main.js @@ -30,7 +30,7 @@ process.on('uncaughtException', (exception: Error) => { * See https://github.com/justmoon/constitute for more info */ const container = new Container(); -defaultBindings(container); +defaultBindings(container, settings); const deviceServer = container.constitute('DeviceServer'); deviceServer.start(); From 327908ec751f25b1d85bd46801c8f8d0a1c049dc Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 12 Feb 2017 17:51:48 +0000 Subject: [PATCH 329/504] Update raspberryPi.md --- raspberryPi.md | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/raspberryPi.md b/raspberryPi.md index 763e945f..d70e5930 100644 --- a/raspberryPi.md +++ b/raspberryPi.md @@ -19,19 +19,9 @@ If you're already familiar with the command line, or you are comfortable setting # # Install Node.js - # - sudo apt-get install git htop rng-tools - wget http://node-arm.herokuapp.com/node_latest_armhf.deb - sudo dpkg -i node_latest_armhf.deb - - # - # - # - echo "Enabling hardware random number generator" - sudo modprobe bcm2708-rng - echo "add bcm2708-rng to /etc/modules" - echo "Now you have /dev/hwrng !" - sync + # [You can set up other versions here](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) + curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - + sudo apt-get install -y nodejs # @@ -47,11 +37,15 @@ If you're already familiar with the command line, or you are comfortable setting # - # Install the Spark-CLI + # Install the Particle-CLI # - sudo npm install -g spark-cli + sudo npm install -g particle-cli --unsafe-perm +After this you can follow the normal instructions for setting up the server. I had trouble getting `particle identify` working so I used `ssh` to get my server key and set up the device from my main computer. +https://www.raspberrypi.org/documentation/remote-access/ssh/ +If you want the node server to run whenever the Pi starts up, look into `pm2`: +https://github.com/Unitech/pm2 # # Setup a project folder # From 9f23a1d2068f5ec1c9260a9e94ccc9762cc8a721 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 12 Feb 2017 17:52:24 +0000 Subject: [PATCH 330/504] Update raspberryPi.md --- raspberryPi.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/raspberryPi.md b/raspberryPi.md index d70e5930..c33fb60a 100644 --- a/raspberryPi.md +++ b/raspberryPi.md @@ -41,11 +41,6 @@ If you're already familiar with the command line, or you are comfortable setting # sudo npm install -g particle-cli --unsafe-perm -After this you can follow the normal instructions for setting up the server. I had trouble getting `particle identify` working so I used `ssh` to get my server key and set up the device from my main computer. -https://www.raspberrypi.org/documentation/remote-access/ssh/ - -If you want the node server to run whenever the Pi starts up, look into `pm2`: -https://github.com/Unitech/pm2 # # Setup a project folder # @@ -60,3 +55,9 @@ https://github.com/Unitech/pm2 npm install node main.js ``` + +After this you can follow the normal instructions for setting up the server. I had trouble getting `particle identify` working so I used `ssh` to get my server key and set up the device from my main computer. +https://www.raspberrypi.org/documentation/remote-access/ssh/ + +If you want the node server to run whenever the Pi starts up, look into `pm2`: +https://github.com/Unitech/pm2 From 6148120a1efa93172e8a91119cb3cd521ab413a5 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sun, 12 Feb 2017 20:18:58 +0200 Subject: [PATCH 331/504] add webhook requestType compile with vars --- dist/controllers/WebhooksController.js | 5 --- dist/managers/WebhookManager.js | 23 +++++++--- src/controllers/WebhooksController.js | 12 +----- src/managers/WebhookManager.js | 18 +++++++- src/types.js | 4 +- test/WebhookManager.test.js | 59 ++++++++++++++++++++++++++ test/WebhooksController.test.js | 14 ------ 7 files changed, 95 insertions(+), 40 deletions(-) diff --git a/dist/controllers/WebhooksController.js b/dist/controllers/WebhooksController.js index d9fe1768..ebcc9292 100644 --- a/dist/controllers/WebhooksController.js +++ b/dist/controllers/WebhooksController.js @@ -89,8 +89,6 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -var REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; - var validateWebhookMutator = function validateWebhookMutator(webhookMutator) { if (!webhookMutator.event) { return new _HttpError2.default('no event name provided'); @@ -101,9 +99,6 @@ var validateWebhookMutator = function validateWebhookMutator(webhookMutator) { if (!webhookMutator.requestType) { return new _HttpError2.default('no requestType provided'); } - if (!REQUEST_TYPES.includes(webhookMutator.requestType)) { - return new _HttpError2.default('wrong requestType'); - } return null; }; diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js index 77ff0346..bebf8ddc 100644 --- a/dist/managers/WebhookManager.js +++ b/dist/managers/WebhookManager.js @@ -83,6 +83,14 @@ var splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { return chunks; }; +var validateRequestType = function validateRequestType(requestType) { + if (!REQUEST_TYPES.includes(requestType)) { + throw new _HttpError2.default('wrong requestType'); + } + return requestType; +}; + +var REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; var MAX_WEBHOOK_ERRORS_COUNT = 10; var WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; var MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; @@ -302,7 +310,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) case 0: _context7.prev = 0; return _context7.delegateYield(_regenerator2.default.mark(function _callee6() { - var webhookVariablesObject, requestJson, requestFormData, requestUrl, requestQuery, responseTopic, isJsonRequest, requestOptions, responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks; + var webhookVariablesObject, requestJson, requestFormData, requestUrl, requestQuery, responseTopic, requestType, isJsonRequest, requestOptions, responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { @@ -313,6 +321,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) requestUrl = _this._compileTemplate(webhook.url, webhookVariablesObject); requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject); responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject); + requestType = _this._compileTemplate(webhook.requestType, webhookVariablesObject); isJsonRequest = !!requestJson; requestOptions = { auth: webhook.auth, @@ -320,19 +329,19 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) form: !isJsonRequest ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, headers: webhook.headers, json: true, - method: webhook.requestType, + method: validateRequestType(requestType), qs: requestQuery, strictSSL: webhook.rejectUnauthorized, url: (0, _nullthrows2.default)(requestUrl) }; - _context6.next = 10; + _context6.next = 11; return _this._callWebhook(webhook, event, requestOptions); - case 10: + case 11: responseBody = _context6.sent; if (responseBody) { - _context6.next = 13; + _context6.next = 14; break; } @@ -340,7 +349,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) v: void 0 }); - case 13: + case 14: isResponseBodyAnObject = responseBody === Object(responseBody); responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(responseBody); responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(responseBody) : responseBody); @@ -358,7 +367,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) }); }); - case 18: + case 19: case 'end': return _context6.stop(); } diff --git a/src/controllers/WebhooksController.js b/src/controllers/WebhooksController.js index f22e9ad4..02d5f805 100644 --- a/src/controllers/WebhooksController.js +++ b/src/controllers/WebhooksController.js @@ -1,9 +1,6 @@ // @flow -import type { - RequestType, - WebhookMutator, -} from '../types'; +import type { WebhookMutator } from '../types'; import type WebhookManager from '../managers/WebhookManager'; import Controller from './Controller'; @@ -11,10 +8,6 @@ import HttpError from '../lib/HttpError'; import httpVerb from '../decorators/httpVerb'; import route from '../decorators/route'; -const REQUEST_TYPES: Array = [ - 'DELETE', 'GET', 'POST', 'PUT', -]; - const validateWebhookMutator = (webhookMutator: WebhookMutator): ?HttpError => { if (!webhookMutator.event) { return new HttpError('no event name provided'); @@ -25,9 +18,6 @@ const validateWebhookMutator = (webhookMutator: WebhookMutator): ?HttpError => { if (!webhookMutator.requestType) { return new HttpError('no requestType provided'); } - if (!REQUEST_TYPES.includes(webhookMutator.requestType)) { - return new HttpError('wrong requestType'); - } return null; }; diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index a237e849..bf964bad 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -4,6 +4,7 @@ import type { Event, Repository, RequestOptions, + RequestType, Webhook, WebhookMutator, } from '../types'; @@ -40,6 +41,16 @@ const splitBufferIntoChunks = ( return chunks; }; +const validateRequestType = (requestType: string): RequestType => { + if (!REQUEST_TYPES.includes(requestType)) { + throw new HttpError('wrong requestType'); + } + return requestType; +}; + +const REQUEST_TYPES: Array = [ + 'DELETE', 'GET', 'POST', 'PUT', +]; const MAX_WEBHOOK_ERRORS_COUNT = 10; const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; @@ -178,6 +189,11 @@ class WebhookManager { webhookVariablesObject, ); + const requestType = this._compileTemplate( + webhook.requestType, + webhookVariablesObject, + ); + const isJsonRequest = !!requestJson; const requestOptions = { auth: webhook.auth, @@ -189,7 +205,7 @@ class WebhookManager { : undefined, headers: webhook.headers, json: true, - method: webhook.requestType, + method: validateRequestType(requestType), qs: requestQuery, strictSSL: webhook.rejectUnauthorized, url: nullthrows(requestUrl), diff --git a/src/types.js b/src/types.js index e99c5930..a93da973 100644 --- a/src/types.js +++ b/src/types.js @@ -18,7 +18,7 @@ export type Webhook = { productIdOrSlug?: string, query?: { [key: string]: Object }, rejectUnauthorized?: boolean, - requestType: RequestType, + requestType: string, responseTemplate?: string, responseTopic?: string, url: string, @@ -38,7 +38,7 @@ export type WebhookMutator = { productIdOrSlug?: string, query?: { [key: string]: Object }, rejectUnauthorized?: boolean, - requestType: RequestType, + requestType: string, responseTemplate?: string, responseTopic?: string, url: string, diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index 94cec0a1..97e2977a 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -8,6 +8,7 @@ import { EventPublisher } from 'spark-protocol'; import WebhookFileRepository from '../src/repository/WebhookFileRepository'; import WebhookManager from '../src/managers/WebhookManager'; import TestData from './setup/TestData'; +import Logger from '../src/lib/logger'; const WEBHOOK_BASE = { event: 'test-event', @@ -251,6 +252,64 @@ test( }, ); +test( + 'should compile requestType', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const testRequestType = 'POST'; + const data = `{"t":"123","requestType": "${testRequestType}"}`; + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + requestType: "{{requestType}}", + }; + const defaultRequestData = getDefaultRequestData(event); + + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify(defaultRequestData), + ); + t.is(requestOptions.headers, undefined); + t.is(requestOptions.method, testRequestType); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should throw an error if wrong requestType is provided', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const testRequestType = 'wrongRequestType'; + const data = `{"t":"123","requestType": "${testRequestType}"}`; + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + requestType: "{{requestType}}", + }; + const defaultRequestData = getDefaultRequestData(event); + + + + Logger.error = sinon.spy((message: string) => { + t.is(message, 'webhookError: Error: wrong requestType'); + }); + + manager.runWebhook(webhook, event); + }, +); + test( 'should publish sent event', async t => { diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js index faa5440c..47e9e757 100644 --- a/test/WebhooksController.test.js +++ b/test/WebhooksController.test.js @@ -101,20 +101,6 @@ test('should throw an error if requestType isn\'t provided', async t => { t.is(response.body.error, 'no requestType provided'); }); -test('should throw an error if requestType is wrong', async t => { - const response = await request(app) - .post('/v1/webhooks') - .query({ access_token: userToken }) - .send({ - event: WEBHOOK_MODEL.event, - requestType: 'some random value', - url: WEBHOOK_MODEL.url, - }); - - t.is(response.status, 400); - t.is(response.body.error, 'wrong requestType'); -}); - test.serial('should return all webhooks', async t => { const response = await request(app) .get('/v1/webhooks') From 08e8190a957475a48e0287ca34346304825966b5 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Sun, 12 Feb 2017 20:36:42 +0200 Subject: [PATCH 332/504] add webhook headers and auth header compile with vars --- dist/managers/WebhookManager.js | 18 ++--- src/managers/WebhookManager.js | 14 +++- test/WebhookManager.test.js | 123 ++++++++++++++++++++++---------- 3 files changed, 106 insertions(+), 49 deletions(-) diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js index bebf8ddc..37a8b476 100644 --- a/dist/managers/WebhookManager.js +++ b/dist/managers/WebhookManager.js @@ -310,38 +310,40 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) case 0: _context7.prev = 0; return _context7.delegateYield(_regenerator2.default.mark(function _callee6() { - var webhookVariablesObject, requestJson, requestFormData, requestUrl, requestQuery, responseTopic, requestType, isJsonRequest, requestOptions, responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks; + var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, isJsonRequest, requestOptions, responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: webhookVariablesObject = _this._getEventVariables(event); + requestAuth = _this._compileJsonTemplate(webhook.auth, webhookVariablesObject); requestJson = _this._compileJsonTemplate(webhook.json, webhookVariablesObject); requestFormData = _this._compileJsonTemplate(webhook.form, webhookVariablesObject); + requestHeaders = _this._compileJsonTemplate(webhook.headers, webhookVariablesObject); requestUrl = _this._compileTemplate(webhook.url, webhookVariablesObject); requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject); responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject); requestType = _this._compileTemplate(webhook.requestType, webhookVariablesObject); isJsonRequest = !!requestJson; requestOptions = { - auth: webhook.auth, + auth: requestAuth, body: isJsonRequest ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined, form: !isJsonRequest ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, - headers: webhook.headers, + headers: requestHeaders, json: true, method: validateRequestType(requestType), qs: requestQuery, strictSSL: webhook.rejectUnauthorized, url: (0, _nullthrows2.default)(requestUrl) }; - _context6.next = 11; + _context6.next = 13; return _this._callWebhook(webhook, event, requestOptions); - case 11: + case 13: responseBody = _context6.sent; if (responseBody) { - _context6.next = 14; + _context6.next = 16; break; } @@ -349,7 +351,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) v: void 0 }); - case 14: + case 16: isResponseBodyAnObject = responseBody === Object(responseBody); responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(responseBody); responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(responseBody) : responseBody); @@ -367,7 +369,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) }); }); - case 19: + case 21: case 'end': return _context6.stop(); } diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index bf964bad..b9552e41 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -164,6 +164,11 @@ class WebhookManager { const webhookVariablesObject = this._getEventVariables(event); + const requestAuth = this._compileJsonTemplate( + webhook.auth, + webhookVariablesObject, + ); + const requestJson = this._compileJsonTemplate( webhook.json, webhookVariablesObject, @@ -174,6 +179,11 @@ class WebhookManager { webhookVariablesObject, ); + const requestHeaders = this._compileJsonTemplate( + webhook.headers, + webhookVariablesObject, + ); + const requestUrl = this._compileTemplate( webhook.url, webhookVariablesObject, @@ -196,14 +206,14 @@ class WebhookManager { const isJsonRequest = !!requestJson; const requestOptions = { - auth: webhook.auth, + auth: requestAuth, body: isJsonRequest ? this._getRequestData(requestJson, event, webhook.noDefaults) : undefined, form: !isJsonRequest ? this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, - headers: webhook.headers, + headers: requestHeaders, json: true, method: validateRequestType(requestType), qs: requestQuery, diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index 97e2977a..2b6b3cfe 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -178,6 +178,90 @@ test( }, ); +test( + 'should compile request auth header', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const data = `{"username":"123","password": "foobar"}`; + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + auth: { + "username": "{{username}}", + "password": "{{password}}" + }, + }; + const defaultRequestData = getDefaultRequestData(event); + + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { + t.is( + JSON.stringify(requestOptions.auth), + JSON.stringify({ + username: '123', + password: 'foobar', + }), + ); + t.is(requestOptions.body, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify(defaultRequestData), + ); + t.is(requestOptions.headers, undefined); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + +test( + 'should compile request headers', + async t => { + const manager = + new WebhookManager(t.context.repository, t.context.eventPublisher); + const data = `{"t":"123","g": "foobar"}`; + const event = getEvent(data); + const webhook = { + ...WEBHOOK_BASE, + headers: { + "testHeader1": "{{t}}", + "testHeader2": "{{g}}" + }, + }; + const defaultRequestData = getDefaultRequestData(event); + + manager._callWebhook = sinon.spy(( + webhook: Webhook, + event: Event, + requestOptions: RequestOptions, + ) => { + t.is(requestOptions.auth, undefined); + t.is(requestOptions.body, undefined); + t.is( + JSON.stringify(requestOptions.form), + JSON.stringify(defaultRequestData), + ); + t.is( + JSON.stringify(requestOptions.headers), + JSON.stringify({ + testHeader1: '123', + testHeader2: 'foobar', + }), + ); + t.is(requestOptions.method, WEBHOOK_BASE.requestType); + t.is(requestOptions.url, WEBHOOK_BASE.url); + }); + + manager.runWebhook(webhook, event); + }, +); + test( 'should compile request url', async t => { @@ -331,45 +415,6 @@ test( }, ); -test( - 'should set request headers', - async t => { - const manager = - new WebhookManager(t.context.repository, t.context.eventPublisher); - const event = getEvent(); - const webhook = { - ...WEBHOOK_BASE, - headers: { - 'Custom-Header-1': '123', - 'Custom-Header-2': '123', - }, - }; - const defaultRequestData = getDefaultRequestData(event); - - manager._callWebhook = sinon.spy(( - webhook: Webhook, - event: Event, - requestOptions: RequestOptions, - ) => { - t.is(requestOptions.auth, undefined); - t.is(requestOptions.body, undefined); - t.is( - JSON.stringify(requestOptions.form), - JSON.stringify(defaultRequestData), - ); - t.is( - requestOptions.headers, - webhook.headers, - ); - t.is(requestOptions.method, WEBHOOK_BASE.requestType); - t.is(requestOptions.qs, undefined); - t.is(requestOptions.url, WEBHOOK_BASE.url); - }); - - manager.runWebhook(webhook, event); - }, -); - test( 'should publish default topic', async t => { From 24b81ac9b5586381610a8547a0742cce52c962c2 Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 13 Feb 2017 13:37:44 +0200 Subject: [PATCH 333/504] make typeRequest case insensitive, fix Flow --- dist/managers/WebhookManager.js | 14 +++++++++++--- src/managers/WebhookManager.js | 17 +++++++++++++---- test/WebhookManager.test.js | 5 ++--- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js index 37a8b476..1ba368a0 100644 --- a/dist/managers/WebhookManager.js +++ b/dist/managers/WebhookManager.js @@ -84,10 +84,18 @@ var splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { }; var validateRequestType = function validateRequestType(requestType) { - if (!REQUEST_TYPES.includes(requestType)) { + var upperRequestType = requestType.toUpperCase(); + // Array.includes() breaks flow, so I have to use find() here. + // https://github.com/facebook/flow/issues/2982 + // https://github.com/facebook/flow/issues/2728 + var validRequestType = REQUEST_TYPES.find(function (type) { + return type === upperRequestType; + }); + if (!validRequestType) { throw new _HttpError2.default('wrong requestType'); } - return requestType; + + return validRequestType; }; var REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; @@ -331,7 +339,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) form: !isJsonRequest ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, headers: requestHeaders, json: true, - method: validateRequestType(requestType), + method: validateRequestType((0, _nullthrows2.default)(requestType)), qs: requestQuery, strictSSL: webhook.rejectUnauthorized, url: (0, _nullthrows2.default)(requestUrl) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index b9552e41..6fea0fc2 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -42,10 +42,19 @@ const splitBufferIntoChunks = ( }; const validateRequestType = (requestType: string): RequestType => { - if (!REQUEST_TYPES.includes(requestType)) { + const upperRequestType = requestType.toUpperCase(); + // Array.includes() breaks flow, so I have to use find() here. + // https://github.com/facebook/flow/issues/2982 + // https://github.com/facebook/flow/issues/2728 + const validRequestType = REQUEST_TYPES.find( + (type: RequestType): boolean => + type === upperRequestType, + ); + if (!validRequestType) { throw new HttpError('wrong requestType'); } - return requestType; + + return validRequestType; }; const REQUEST_TYPES: Array = [ @@ -206,7 +215,7 @@ class WebhookManager { const isJsonRequest = !!requestJson; const requestOptions = { - auth: requestAuth, + auth: (requestAuth: any), body: isJsonRequest ? this._getRequestData(requestJson, event, webhook.noDefaults) : undefined, @@ -215,7 +224,7 @@ class WebhookManager { : undefined, headers: requestHeaders, json: true, - method: validateRequestType(requestType), + method: validateRequestType(nullthrows(requestType)), qs: requestQuery, strictSSL: webhook.rejectUnauthorized, url: nullthrows(requestUrl), diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js index 2b6b3cfe..3470570b 100644 --- a/test/WebhookManager.test.js +++ b/test/WebhookManager.test.js @@ -341,8 +341,7 @@ test( async t => { const manager = new WebhookManager(t.context.repository, t.context.eventPublisher); - const testRequestType = 'POST'; - const data = `{"t":"123","requestType": "${testRequestType}"}`; + const data = `{"t":"123","requestType": "post"}`; const event = getEvent(data); const webhook = { ...WEBHOOK_BASE, @@ -362,7 +361,7 @@ test( JSON.stringify(defaultRequestData), ); t.is(requestOptions.headers, undefined); - t.is(requestOptions.method, testRequestType); + t.is(requestOptions.method, 'POST'); t.is(requestOptions.url, WEBHOOK_BASE.url); }); From 689b138f406a3448c934f914344f5aa5f81e894a Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 13 Feb 2017 13:51:03 +0200 Subject: [PATCH 334/504] fix Device varibales & functions types --- src/lib/deviceToAPI.js | 4 ++-- src/types.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js index c0324e62..93add631 100644 --- a/src/lib/deviceToAPI.js +++ b/src/lib/deviceToAPI.js @@ -6,7 +6,7 @@ export type DeviceAPIType = {| cellular: boolean, connected: boolean, current_build_target: string, - functions?: Array, + functions?: ?Array, id: string, imei?: string, last_app: ?string, @@ -18,7 +18,7 @@ export type DeviceAPIType = {| product_id: number, return_value?: mixed, status: string, - variables?: Object, + variables?: ?Object, |}; const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({ diff --git a/src/types.js b/src/types.js index a93da973..06a196ef 100644 --- a/src/types.js +++ b/src/types.js @@ -112,9 +112,9 @@ export type UserCredentials = { export type Device = DeviceAttributes & { connected: boolean, - functions?: Array, + functions?: ?Array, lastFlashedAppName: ?string, - variables?: Object, + variables?: ?Object, }; export type Repository = { From deee30d4e0f8028749f83d122c8449957886bb7d Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 13 Feb 2017 18:30:56 +0200 Subject: [PATCH 335/504] fix requestType Flow casting --- src/managers/WebhookManager.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js index 6fea0fc2..2cdab72f 100644 --- a/src/managers/WebhookManager.js +++ b/src/managers/WebhookManager.js @@ -42,19 +42,12 @@ const splitBufferIntoChunks = ( }; const validateRequestType = (requestType: string): RequestType => { - const upperRequestType = requestType.toUpperCase(); - // Array.includes() breaks flow, so I have to use find() here. - // https://github.com/facebook/flow/issues/2982 - // https://github.com/facebook/flow/issues/2728 - const validRequestType = REQUEST_TYPES.find( - (type: RequestType): boolean => - type === upperRequestType, - ); - if (!validRequestType) { + const upperRequestType = ((requestType.toUpperCase(): any): RequestType); + if (!REQUEST_TYPES.includes(upperRequestType)) { throw new HttpError('wrong requestType'); } - return validRequestType; + return upperRequestType; }; const REQUEST_TYPES: Array = [ From 9235b9fd1b8c0ff3afb93dd6483159d42637f5fd Mon Sep 17 00:00:00 2001 From: Anton Puko Date: Mon, 27 Feb 2017 12:13:48 +0200 Subject: [PATCH 336/504] change passwordHasher function to sha256. --- dist/lib/PasswordHasher.js | 40 ++++++++++++++++----------------- dist/managers/WebhookManager.js | 10 ++------- src/lib/PasswordHasher.js | 2 +- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/dist/lib/PasswordHasher.js b/dist/lib/PasswordHasher.js index 6a694e76..52d90470 100644 --- a/dist/lib/PasswordHasher.js +++ b/dist/lib/PasswordHasher.js @@ -22,26 +22,26 @@ var _crypto2 = _interopRequireDefault(_crypto); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var HASH_DIGEST = 'sha1'; /** - * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * You can download the source here: https://github.com/spark/spark-server - * - * - * - */ +var HASH_DIGEST = 'sha256'; /** + * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can download the source here: https://github.com/spark/spark-server + * + * + * + */ var HASH_ITERATIONS = 30000; var KEY_LENGTH = 64; diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js index 1ba368a0..d9e4bb9f 100644 --- a/dist/managers/WebhookManager.js +++ b/dist/managers/WebhookManager.js @@ -85,17 +85,11 @@ var splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { var validateRequestType = function validateRequestType(requestType) { var upperRequestType = requestType.toUpperCase(); - // Array.includes() breaks flow, so I have to use find() here. - // https://github.com/facebook/flow/issues/2982 - // https://github.com/facebook/flow/issues/2728 - var validRequestType = REQUEST_TYPES.find(function (type) { - return type === upperRequestType; - }); - if (!validRequestType) { + if (!REQUEST_TYPES.includes(upperRequestType)) { throw new _HttpError2.default('wrong requestType'); } - return validRequestType; + return upperRequestType; }; var REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; diff --git a/src/lib/PasswordHasher.js b/src/lib/PasswordHasher.js index 2ad28ad2..a34043c8 100644 --- a/src/lib/PasswordHasher.js +++ b/src/lib/PasswordHasher.js @@ -21,7 +21,7 @@ import crypto from 'crypto'; -const HASH_DIGEST = 'sha1'; +const HASH_DIGEST = 'sha256'; const HASH_ITERATIONS = 30000; const KEY_LENGTH = 64; From 6f62767a4413c6aeea3afea24dad654044ce35f4 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Fri, 10 Feb 2017 08:40:35 -0800 Subject: [PATCH 337/504] Trying to fix packages when installing this package. --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index a8e5241a..468b22fe 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ }, "dependencies": { "array-flatten": "^2.1.1", + "babel": "^6.5.2", + "babel-loader": "^6.2.10", "basic-auth-parser": "0.0.2", "binary-version-reader": "^0.5.0", "body-parser": "^1.15.2", From a81c044628673bdb597854125868b62717a2adf3 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 12:09:30 -0800 Subject: [PATCH 338/504] Trying to fix npm install --- package.json | 5 +++-- src/managers/DeviceManager.js | 4 ++-- test/setup/settings.js | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 468b22fe..5dc1eccb 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,9 @@ }, "dependencies": { "array-flatten": "^2.1.1", - "babel": "^6.5.2", + "babel-cli": "^6.18.0", "babel-loader": "^6.2.10", + "babel-runtime": "^6.22.0", "basic-auth-parser": "0.0.2", "binary-version-reader": "^0.5.0", "body-parser": "^1.15.2", @@ -62,6 +63,7 @@ "express-oauth-server": "^2.0.0-b1", "hogan.js": "^3.0.2", "lodash": "^4.17.4", + "mkdirp": "^0.5.1", "moment": "*", "morgan": "^1.7.0", "multer": "^1.2.1", @@ -75,7 +77,6 @@ }, "devDependencies": { "ava": "^0.17.0", - "babel-cli": "^6.18.0", "babel-eslint": "^7.1.1", "babel-plugin-transform-class-properties": "^6.19.0", "babel-plugin-transform-decorators": "^6.13.0", diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js index 7066b80c..c3c84b80 100644 --- a/src/managers/DeviceManager.js +++ b/src/managers/DeviceManager.js @@ -105,14 +105,14 @@ class DeviceManager { throw new HttpError('No device found', 404); } - return ({ + return { ...attributes, connected: device && device.ping().connected || false, functions: description ? description.state.f : null, lastFlashedAppName: null, lastHeard: device && device.ping().lastPing || attributes.lastHeard, variables: description ? description.state.v : null, - }); + }; }; getAll = async (userID: string): Promise> => { diff --git a/test/setup/settings.js b/test/setup/settings.js index e012d8a5..5d2a3b60 100644 --- a/test/setup/settings.js +++ b/test/setup/settings.js @@ -4,9 +4,11 @@ import path from 'path'; /* eslint-disable sorting/sort-object-props */ export default { + BUILD_DIRECTORY: path.join(__dirname, '../__test_data__/build'), CUSTOM_FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__'), DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'), FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'), + FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../__test_data__/firmware'), SERVER_KEY_FILENAME: 'default_key.pem', SERVER_KEYS_DIRECTORY: path.join(__dirname, '../__test_data__'), USERS_DIRECTORY: path.join(__dirname, '../__test_data__/users'), From 360a63c812536d52572a44d24e0d7c8e723d5d7d Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:23:30 -0800 Subject: [PATCH 339/504] Added exports so this library can be used as a npm package. --- package.json | 4 +++- src/OAuthModel.js | 10 +++++++--- src/RouteConfig.js | 9 +++++---- src/defaultBindings.js | 19 +++++++++---------- src/exports.js | 13 +++++++++++++ 5 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 src/exports.js diff --git a/package.json b/package.json index 5dc1eccb..a86d0e76 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,10 @@ "url": "https://github.com/emilyrose" } ], + "main": "./dist/exports.js", "scripts": { "build": "babel ./src --out-dir ./build", + "build:watch": "babel ./src --out-dir ./dist --watch", "build:clean": "rimraf ./build", "lint": "eslint --fix --max-warnings 0 -- .", "prebuild": "npm run build:clean", @@ -52,7 +54,7 @@ }, "dependencies": { "array-flatten": "^2.1.1", - "babel-cli": "^6.18.0", + "babel-cli": "^6.22.2", "babel-loader": "^6.2.10", "babel-runtime": "^6.22.0", "basic-auth-parser": "0.0.2", diff --git a/src/OAuthModel.js b/src/OAuthModel.js index df4fcb08..73f67821 100644 --- a/src/OAuthModel.js +++ b/src/OAuthModel.js @@ -7,7 +7,11 @@ import type { UserRepository, } from './types'; -import ouathClients from './oauthClients.json'; +const OAUTH_CLIENTS = [{ + clientId: 'CLI2', + clientSecret: 'client_secret_here', + grants: ['password'], +}]; class OauthModel { _userRepository: UserRepository; @@ -37,8 +41,8 @@ class OauthModel { }; }; - getClient = (clientId: string, clientSecret: string): Client => - ouathClients.find((client: Client): boolean => + getClient = (clientId: string, clientSecret: string): ?Client => + OAUTH_CLIENTS.find((client: Client): boolean => client.clientId === clientId && client.clientSecret === clientSecret, ); diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 5e6759fc..61e408b1 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -106,14 +106,15 @@ export default ( const values = argumentNames .map((argument: string): string => request.params[argument]); - const controllerInstance = container.constitute(controllerName); + let controllerInstance = container.constitute(controllerName); // In order parallel requests on the controller, the state // (request/response/user) must be added to the controller. if (controllerInstance === controller) { - throw new Error( - '`Transient.with` must be used when binding controllers', - ); + // throw new Error( + // '`Transient.with` must be used when binding controllers', + // ); + controllerInstance = Object.create(controllerInstance); } controllerInstance.request = request; diff --git a/src/defaultBindings.js b/src/defaultBindings.js index b400e68b..9ca61adc 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -3,7 +3,6 @@ import type { Container } from 'constitute'; import { defaultBindings } from 'spark-protocol'; -import { Transient } from 'constitute'; import DeviceClaimsController from './controllers/DeviceClaimsController'; import DevicesController from './controllers/DevicesController'; import EventsController from './controllers/EventsController'; @@ -36,45 +35,45 @@ export default (container: Container) => { container.bindClass( 'DeviceClaimsController', DeviceClaimsController, - Transient.with([ + [ 'DeviceManager', 'ClaimCodeManager', - ]), + ], ); container.bindClass( 'DevicesController', DevicesController, - Transient.with(['DeviceManager']), + ['DeviceManager'], ); container.bindClass( 'EventsController', EventsController, - Transient.with(['EventManager']), + ['EventManager'], ); container.bindClass( 'OauthClientsController', OauthClientsController, - Transient.with([]), + [], ); container.bindClass( 'ProductsController', ProductsController, - Transient.with([]), + [], ); container.bindClass( 'ProvisioningController', ProvisioningController, - Transient.with(['DeviceManager']), + ['DeviceManager'], ); container.bindClass( 'UsersController', UsersController, - Transient.with(['UserRepository']), + ['UserRepository'], ); container.bindClass( 'WebhooksController', WebhooksController, - Transient.with(['WebhookManager']), + ['WebhookManager'], ); // managers diff --git a/src/exports.js b/src/exports.js new file mode 100644 index 00000000..008a4897 --- /dev/null +++ b/src/exports.js @@ -0,0 +1,13 @@ +// @flow + +import logger from './lib/logger'; +import createApp from './app'; +import defaultBindings from './defaultBindings'; +import settings from './settings'; + +export { + createApp, + defaultBindings, + logger, + settings, +}; From 249c8da2afab71bd8aee3809b663dbda249c8b01 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:31:18 -0800 Subject: [PATCH 340/504] Fixing path for export. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a86d0e76..b6c52ac7 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "url": "https://github.com/emilyrose" } ], - "main": "./dist/exports.js", + "main": "./src/exports.js", "scripts": { "build": "babel ./src --out-dir ./build", "build:watch": "babel ./src --out-dir ./dist --watch", From 6a819078349d6c44365d94ab5a08ef462848a28c Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:37:42 -0800 Subject: [PATCH 341/504] Added dist folder so this repo can be consumed by other repos. --- .eslintignore | 1 + dist/OAuthModel.js | 130 ++++ dist/RouteConfig.js | 225 ++++++ dist/app.js | 56 ++ dist/controllers/Controller.js | 36 + dist/controllers/DeviceClaimsController.js | 136 ++++ dist/controllers/DevicesController.js | 531 ++++++++++++++ dist/controllers/EventsController.js | 289 ++++++++ dist/controllers/OauthClientsController.js | 183 +++++ dist/controllers/ProductsController.js | 432 ++++++++++++ dist/controllers/ProvisioningController.js | 147 ++++ dist/controllers/UsersController.js | 243 +++++++ dist/controllers/WebhooksController.js | 271 ++++++++ dist/controllers/types.js | 1 + dist/decorators/allowUpload.js | 23 + dist/decorators/anonymous.js | 13 + dist/decorators/httpVerb.js | 13 + dist/decorators/route.js | 13 + dist/decorators/serverSentEvents.js | 13 + dist/decorators/types.js | 1 + dist/defaultBindings.js | 102 +++ dist/exports.js | 29 + dist/lib/HttpError.js | 45 ++ dist/lib/PasswordHasher.js | 86 +++ dist/lib/deviceToAPI.js | 27 + dist/lib/eventToApi.js | 15 + dist/lib/logger.js | 63 ++ dist/main.js | 76 ++ dist/managers/DeviceManager.js | 652 ++++++++++++++++++ dist/managers/EventManager.js | 33 + dist/managers/FirmwareCompilationManager.js | 263 +++++++ dist/managers/WebhookManager.js | 533 ++++++++++++++ .../DeviceFirmwareFileRepository.js | 70 ++ dist/repository/UserFileRepository.js | 535 ++++++++++++++ dist/repository/WebhookFileRepository.js | 310 +++++++++ dist/settings.js | 51 ++ dist/types.js | 1 + package.json | 4 +- 38 files changed, 5650 insertions(+), 2 deletions(-) create mode 100644 dist/OAuthModel.js create mode 100644 dist/RouteConfig.js create mode 100644 dist/app.js create mode 100644 dist/controllers/Controller.js create mode 100644 dist/controllers/DeviceClaimsController.js create mode 100644 dist/controllers/DevicesController.js create mode 100644 dist/controllers/EventsController.js create mode 100644 dist/controllers/OauthClientsController.js create mode 100644 dist/controllers/ProductsController.js create mode 100644 dist/controllers/ProvisioningController.js create mode 100644 dist/controllers/UsersController.js create mode 100644 dist/controllers/WebhooksController.js create mode 100644 dist/controllers/types.js create mode 100644 dist/decorators/allowUpload.js create mode 100644 dist/decorators/anonymous.js create mode 100644 dist/decorators/httpVerb.js create mode 100644 dist/decorators/route.js create mode 100644 dist/decorators/serverSentEvents.js create mode 100644 dist/decorators/types.js create mode 100644 dist/defaultBindings.js create mode 100644 dist/exports.js create mode 100644 dist/lib/HttpError.js create mode 100644 dist/lib/PasswordHasher.js create mode 100644 dist/lib/deviceToAPI.js create mode 100644 dist/lib/eventToApi.js create mode 100644 dist/lib/logger.js create mode 100644 dist/main.js create mode 100644 dist/managers/DeviceManager.js create mode 100644 dist/managers/EventManager.js create mode 100644 dist/managers/FirmwareCompilationManager.js create mode 100644 dist/managers/WebhookManager.js create mode 100644 dist/repository/DeviceFirmwareFileRepository.js create mode 100644 dist/repository/UserFileRepository.js create mode 100644 dist/repository/WebhookFileRepository.js create mode 100644 dist/settings.js create mode 100644 dist/types.js diff --git a/.eslintignore b/.eslintignore index 5d21615e..07d7cfc2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ flow-typed # Don't check auto-generated stuff coverage build +dist binaries node_modules diff --git a/dist/OAuthModel.js b/dist/OAuthModel.js new file mode 100644 index 00000000..592e0e98 --- /dev/null +++ b/dist/OAuthModel.js @@ -0,0 +1,130 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const OAUTH_CLIENTS = [{ + clientId: 'CLI2', + clientSecret: 'client_secret_here', + grants: ['password'], +}]; + +const OauthModel = function OauthModel(userRepository) { + const _this = this; + + (0, _classCallCheck3.default)(this, OauthModel); + + this.getAccessToken = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(bearerToken) { + let user, + userTokenObject; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._userRepository.getByAccessToken(bearerToken); + + case 2: + user = _context.sent; + + if (user) { + _context.next = 5; + break; + } + + return _context.abrupt('return', null); + + case 5: + userTokenObject = user.accessTokens.find(tokenObject => tokenObject.accessToken === bearerToken); + + if (userTokenObject) { + _context.next = 8; + break; + } + + return _context.abrupt('return', null); + + case 8: + return _context.abrupt('return', { + accessToken: userTokenObject.accessToken, + user, + }); + + case 9: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x) { + return _ref.apply(this, arguments); + }; + }()); + + this.getClient = function (clientId, clientSecret) { + return OAUTH_CLIENTS.find(client => client.clientId === clientId && client.clientSecret === clientSecret); + }; + + this.getUser = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._userRepository.validateLogin(username, password); + + case 2: + return _context2.abrupt('return', _context2.sent); + + case 3: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x2, _x3) { + return _ref2.apply(this, arguments); + }; + }()); + + this.saveToken = function (tokenObject, client, user) { + _this._userRepository.saveAccessToken(user.id, tokenObject); + return { + accessToken: tokenObject.accessToken, + client, + user, + }; + }; + + this.validateScope = function (user, client, scope) { + return 'true'; + }; + + this._userRepository = userRepository; +} + +// eslint-disable-next-line no-unused-vars +; + +exports.default = OauthModel; diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js new file mode 100644 index 00000000..2efc9fc1 --- /dev/null +++ b/dist/RouteConfig.js @@ -0,0 +1,225 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); + +const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); + +const _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); + +const _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); + +const _create = require('babel-runtime/core-js/object/create'); + +const _create2 = _interopRequireDefault(_create); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names'); + +const _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames); + +const _expressOauthServer = require('express-oauth-server'); + +const _expressOauthServer2 = _interopRequireDefault(_expressOauthServer); + +const _nullthrows = require('nullthrows'); + +const _nullthrows2 = _interopRequireDefault(_nullthrows); + +const _multer = require('multer'); + +const _multer2 = _interopRequireDefault(_multer); + +const _OAuthModel = require('./OAuthModel'); + +const _OAuthModel2 = _interopRequireDefault(_OAuthModel); + +const _HttpError = require('./lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const maybe = function maybe(middleware, condition) { + return function (request, response, next) { + if (condition) { + middleware(request, response, next); + } else { + next(); + } + }; +}; + +const injectUserMiddleware = function injectUserMiddleware() { + return function (request, response, next) { + const oauthInfo = response.locals.oauth; + if (oauthInfo) { + const token = oauthInfo.token; + // eslint-disable-next-line no-param-reassign + request.user = token && token.user; + } + next(); + }; +}; + +// in old codebase there was _keepAlive() function in controllers , which +// prevents of closing server-sent-events stream if there aren't events for +// a long time, but according to the docs sse keep connection alive automatically. +// if there will be related issues in the future, we can return _keepAlive() back. +const serverSentEventsMiddleware = function serverSentEventsMiddleware() { + return function (request, response, next) { + request.socket.setNoDelay(); + response.writeHead(200, { + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + }); + + next(); + }; +}; + +exports.default = function (app, container, controllers, settings) { + const oauth = new _expressOauthServer2.default({ + ACCESS_TOKEN_LIFETIME: settings.ACCESS_TOKEN_LIFETIME, + allowBearerTokensInQueryString: true, + model: new _OAuthModel2.default(container.constitute('UserRepository')), + }); + + const filesMiddleware = function filesMiddleware() { + const allowedUploads = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + return (0, _nullthrows2.default)(allowedUploads).length ? (0, _multer2.default)().fields(allowedUploads) : (0, _multer2.default)().any(); + }; + + app.post(settings.LOGIN_ROUTE, oauth.token()); + + controllers.forEach((controllerName) => { + const controller = container.constitute(controllerName); + (0, _getOwnPropertyNames2.default)((0, _getPrototypeOf2.default)(controller)).forEach((functionName) => { + const mappedFunction = controller[functionName]; + let allowedUploads = mappedFunction.allowedUploads, + anonymous = mappedFunction.anonymous, + httpVerb = mappedFunction.httpVerb, + route = mappedFunction.route, + serverSentEvents = mappedFunction.serverSentEvents; + + + if (!httpVerb) { + return; + } + app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), maybe(filesMiddleware(allowedUploads), allowedUploads), (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) { + let argumentNames, + values, + controllerInstance, + _request$body, + access_token, + body, + functionResult, + result, + httpError; + + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + argumentNames = (route.match(/:[\w]*/g) || []).map(argumentName => argumentName.replace(':', '')); + values = argumentNames.map(argument => request.params[argument]); + controllerInstance = container.constitute(controllerName); + + // In order parallel requests on the controller, the state + // (request/response/user) must be added to the controller. + + if (controllerInstance === controller) { + // throw new Error( + // '`Transient.with` must be used when binding controllers', + // ); + controllerInstance = (0, _create2.default)(controllerInstance); + } + + controllerInstance.request = request; + controllerInstance.response = response; + controllerInstance.user = request.user; + + // Take access token out if it's posted. + _request$body = request.body, access_token = _request$body.access_token, body = (0, _objectWithoutProperties3.default)(_request$body, ['access_token']); + _context.prev = 8; + functionResult = mappedFunction.call(...[controllerInstance].concat((0, _toConsumableArray3.default)(values), [body])); + + if (!functionResult.then) { + _context.next = 17; + break; + } + + _context.next = 13; + return _promise2.default.race([functionResult, !serverSentEvents ? new _promise2.default((resolve, reject) => setTimeout(() => reject(new Error('timeout')), settings.API_TIMEOUT)) : null]); + + case 13: + result = _context.sent; + + response.status((0, _nullthrows2.default)(result).status).json((0, _nullthrows2.default)(result).data); + _context.next = 18; + break; + + case 17: + response.status(functionResult.status).json(functionResult.data); + + case 18: + _context.next = 24; + break; + + case 20: + _context.prev = 20; + _context.t0 = _context.catch(8); + httpError = new _HttpError2.default(_context.t0); + + response.status(httpError.status).json({ + error: httpError.message, + ok: false, + }); + + case 24: + case 'end': + return _context.stop(); + } + } + }, _callee, undefined, [[8, 20]]); + })); + + return function (_x2, _x3) { + return _ref.apply(this, arguments); + }; + }())); + }); + }); + + app.all('*', (request, response) => { + response.sendStatus(404); + }); + + app.use((error, request, response, next, // eslint-disable-line no-unused-vars + ) => { + response.status(400).json({ + error: error.code ? error.code : error, + ok: false, + }); + }); +}; diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 00000000..6c1dfc67 --- /dev/null +++ b/dist/app.js @@ -0,0 +1,56 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _bodyParser = require('body-parser'); + +const _bodyParser2 = _interopRequireDefault(_bodyParser); + +const _express = require('express'); + +const _express2 = _interopRequireDefault(_express); + +const _morgan = require('morgan'); + +const _morgan2 = _interopRequireDefault(_morgan); + +const _RouteConfig = require('./RouteConfig'); + +const _RouteConfig2 = _interopRequireDefault(_RouteConfig); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (container, settings) { + const app = (0, _express2.default)(); + + const setCORSHeaders = function setCORSHeaders(request, response, next) { + if (request.method === 'OPTIONS') { + response.set({ + 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Max-Age': '300', + }); + return response.sendStatus(204); + } + response.set({ 'Access-Control-Allow-Origin': '*' }); + return next(); + }; + + if (settings.LOG_REQUESTS) { + app.use((0, _morgan2.default)('combined')); + } + + app.use(_bodyParser2.default.json()); + app.use(_bodyParser2.default.urlencoded({ extended: true })); + app.use(setCORSHeaders); + + (0, _RouteConfig2.default)(app, container, ['DeviceClaimsController', + // to avoid routes collisions EventsController should be placed + // before DevicesController + 'EventsController', 'DevicesController', 'OauthClientsController', 'ProductsController', 'ProvisioningController', 'UsersController', 'WebhooksController'], settings); + + return app; +}; diff --git a/dist/controllers/Controller.js b/dist/controllers/Controller.js new file mode 100644 index 00000000..ecb1cbec --- /dev/null +++ b/dist/controllers/Controller.js @@ -0,0 +1,36 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +exports.default = undefined; + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const Controller = function Controller() { + (0, _classCallCheck3.default)(this, Controller); + + this.bad = function (message) { + const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + return { + data: { + error: message, + ok: false, + }, + status, + }; + }; + + this.ok = function (output) { + return { + data: output, + status: 200, + }; + }; +}; + +exports.default = Controller; diff --git a/dist/controllers/DeviceClaimsController.js b/dist/controllers/DeviceClaimsController.js new file mode 100644 index 00000000..6e465768 --- /dev/null +++ b/dist/controllers/DeviceClaimsController.js @@ -0,0 +1,136 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/device_claims'), (_class = (function (_Controller) { + (0, _inherits3.default)(DeviceClaimsController, _Controller); + + function DeviceClaimsController(deviceManager, claimCodeManager) { + (0, _classCallCheck3.default)(this, DeviceClaimsController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (DeviceClaimsController.__proto__ || (0, _getPrototypeOf2.default)(DeviceClaimsController)).call(this)); + + _this._deviceManager = deviceManager; + _this._claimCodeManager = claimCodeManager; + return _this; + } + + (0, _createClass3.default)(DeviceClaimsController, [{ + key: 'createClaimCode', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + let claimCode, + devices, + deviceIDs; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + claimCode = this._claimCodeManager.createClaimCode(this.user.id); + _context.next = 3; + return this._deviceManager.getAll(this.user.id); + + case 3: + devices = _context.sent; + deviceIDs = devices.map(device => device.deviceID); + return _context.abrupt('return', this.ok({ claim_code: claimCode, device_ids: deviceIDs })); + + case 6: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function createClaimCode() { + return _ref.apply(this, arguments); + } + + return createClaimCode; + }()), + }]); + return DeviceClaimsController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClaimCode', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClaimCode'), _class.prototype)), _class)); +exports.default = DeviceClaimsController; diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js new file mode 100644 index 00000000..a6cc6beb --- /dev/null +++ b/dist/controllers/DevicesController.js @@ -0,0 +1,531 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _dec10, + _dec11, + _dec12, + _dec13, + _dec14, + _dec15, + _dec16, + _dec17, + _dec18, + _dec19, + _dec20, + _desc, + _value, + _class; + +const _nullthrows = require('nullthrows'); + +const _nullthrows2 = _interopRequireDefault(_nullthrows); + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _FirmwareCompilationManager = require('../managers/FirmwareCompilationManager'); + +const _FirmwareCompilationManager2 = _interopRequireDefault(_FirmwareCompilationManager); + +const _allowUpload = require('../decorators/allowUpload'); + +const _allowUpload2 = _interopRequireDefault(_allowUpload); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +const _deviceToAPI = require('../lib/deviceToAPI'); + +const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = (function (_Controller) { + (0, _inherits3.default)(DevicesController, _Controller); + + function DevicesController(deviceManager) { + (0, _classCallCheck3.default)(this, DevicesController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (DevicesController.__proto__ || (0, _getPrototypeOf2.default)(DevicesController)).call(this)); + + _this._deviceManager = deviceManager; + return _this; + } + + (0, _createClass3.default)(DevicesController, [{ + key: 'claimDevice', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(postBody) { + let deviceID; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + deviceID = postBody.id; + _context.next = 3; + return this._deviceManager.claimDevice(deviceID, this.user.id); + + case 3: + return _context.abrupt('return', this.ok({ ok: true })); + + case 4: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function claimDevice(_x) { + return _ref.apply(this, arguments); + } + + return claimDevice; + }()), + }, { + key: 'getAppFirmware', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(binaryID) { + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + return _context2.abrupt('return', this.ok(_FirmwareCompilationManager2.default.getBinaryForID(binaryID))); + + case 1: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function getAppFirmware(_x2) { + return _ref2.apply(this, arguments); + } + + return getAppFirmware; + }()), + }, { + key: 'compileSources', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(postBody) { + let response; + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _FirmwareCompilationManager2.default.compileSource((0, _nullthrows2.default)(postBody.platform_id || postBody.product_id), this.request.files); + + case 2: + response = _context3.sent; + + if (response) { + _context3.next = 5; + break; + } + + throw new _HttpError2.default('Error during compilation'); + + case 5: + return _context3.abrupt('return', this.ok((0, _extends3.default)({}, response, { + binary_url: `/v1/binaries/${response.binary_id}`, + ok: true, + }))); + + case 6: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function compileSources(_x3) { + return _ref3.apply(this, arguments); + } + + return compileSources; + }()), + }, { + key: 'unclaimDevice', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) { + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return this._deviceManager.unclaimDevice(deviceID, this.user.id); + + case 2: + return _context4.abrupt('return', this.ok({ ok: true })); + + case 3: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function unclaimDevice(_x4) { + return _ref4.apply(this, arguments); + } + + return unclaimDevice; + }()), + }, { + key: 'getDevices', + value: (function () { + const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { + let devices; + return _regenerator2.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + _context5.prev = 0; + _context5.next = 3; + return this._deviceManager.getAll(this.user.id); + + case 3: + devices = _context5.sent; + return _context5.abrupt('return', this.ok(devices.map(device => (0, _deviceToAPI2.default)(device)))); + + case 7: + _context5.prev = 7; + _context5.t0 = _context5.catch(0); + return _context5.abrupt('return', this.ok([])); + + case 10: + case 'end': + return _context5.stop(); + } + } + }, _callee5, this, [[0, 7]]); + })); + + function getDevices() { + return _ref5.apply(this, arguments); + } + + return getDevices; + }()), + }, { + key: 'getDevice', + value: (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) { + let device; + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + _context6.next = 2; + return this._deviceManager.getDetailsByID(deviceID, this.user.id); + + case 2: + device = _context6.sent; + return _context6.abrupt('return', this.ok((0, _deviceToAPI2.default)(device))); + + case 4: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function getDevice(_x5) { + return _ref6.apply(this, arguments); + } + + return getDevice; + }()), + }, { + key: 'getVariableValue', + value: (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, varName) { + let varValue, + errorMessage; + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + _context7.prev = 0; + _context7.next = 3; + return this._deviceManager.getVariableValue(deviceID, this.user.id, varName); + + case 3: + varValue = _context7.sent; + return _context7.abrupt('return', this.ok({ result: varValue })); + + case 7: + _context7.prev = 7; + _context7.t0 = _context7.catch(0); + errorMessage = _context7.t0.message; + + if (!errorMessage.match('Variable not found')) { + _context7.next = 12; + break; + } + + throw new _HttpError2.default('Variable not found', 404); + + case 12: + throw _context7.t0; + + case 13: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this, [[0, 7]]); + })); + + function getVariableValue(_x6, _x7) { + return _ref7.apply(this, arguments); + } + + return getVariableValue; + }()), + }, { + key: 'updateDevice', + value: (function () { + const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) { + let updatedAttributes, + flashStatus, + file, + _flashStatus; + + return _regenerator2.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + if (!postBody.name) { + _context8.next = 5; + break; + } + + _context8.next = 3; + return this._deviceManager.renameDevice(deviceID, this.user.id, postBody.name); + + case 3: + updatedAttributes = _context8.sent; + return _context8.abrupt('return', this.ok({ name: updatedAttributes.name, ok: true })); + + case 5: + if (!postBody.app_id) { + _context8.next = 10; + break; + } + + _context8.next = 8; + return this._deviceManager.flashKnownApp(deviceID, this.user.id, postBody.app_id); + + case 8: + flashStatus = _context8.sent; + return _context8.abrupt('return', this.ok({ id: deviceID, status: flashStatus })); + + case 10: + if (!(this.request.files && !this.request.files.file)) { + _context8.next = 12; + break; + } + + throw new Error('Firmware file not provided'); + + case 12: + file = this.request.files && this.request.files.file[0]; + + if (!(file && file.originalname.endsWith('.bin'))) { + _context8.next = 18; + break; + } + + _context8.next = 16; + return this._deviceManager.flashBinary(deviceID, file); + + case 16: + _flashStatus = _context8.sent; + return _context8.abrupt('return', this.ok({ id: deviceID, status: _flashStatus })); + + case 18: + if (!postBody.signal) { + _context8.next = 24; + break; + } + + if (['1', '0'].includes(postBody.signal)) { + _context8.next = 21; + break; + } + + throw new _HttpError2.default('Wrong signal value'); + + case 21: + _context8.next = 23; + return this._deviceManager.raiseYourHand(deviceID, this.user.id, !!parseInt(postBody.signal, 10)); + + case 23: + return _context8.abrupt('return', this.ok({ id: deviceID, ok: true })); + + case 24: + throw new _HttpError2.default('Did not update device'); + + case 25: + case 'end': + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function updateDevice(_x8, _x9) { + return _ref8.apply(this, arguments); + } + + return updateDevice; + }()), + }, { + key: 'callDeviceFunction', + value: (function () { + const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) { + let result, + device, + errorMessage; + return _regenerator2.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + _context9.prev = 0; + _context9.next = 3; + return this._deviceManager.callFunction(deviceID, this.user.id, functionName, postBody); + + case 3: + result = _context9.sent; + _context9.next = 6; + return this._deviceManager.getByID(deviceID, this.user.id); + + case 6: + device = _context9.sent; + return _context9.abrupt('return', this.ok((0, _deviceToAPI2.default)(device, result))); + + case 10: + _context9.prev = 10; + _context9.t0 = _context9.catch(0); + errorMessage = _context9.t0.message; + + if (!(errorMessage.indexOf('Unknown Function') >= 0)) { + _context9.next = 15; + break; + } + + throw new _HttpError2.default('Function not found', 404); + + case 15: + throw _context9.t0; + + case 16: + case 'end': + return _context9.stop(); + } + } + }, _callee9, this, [[0, 10]]); + })); + + function callDeviceFunction(_x10, _x11, _x12) { + return _ref9.apply(this, arguments); + } + + return callDeviceFunction; + }()), + }]); + return DevicesController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype)), _class)); +exports.default = DevicesController; diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js new file mode 100644 index 00000000..5208feb2 --- /dev/null +++ b/dist/controllers/EventsController.js @@ -0,0 +1,289 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _stringify = require('babel-runtime/core-js/json/stringify'); + +const _stringify2 = _interopRequireDefault(_stringify); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _dec10, + _dec11, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _serverSentEvents = require('../decorators/serverSentEvents'); + +const _serverSentEvents2 = _interopRequireDefault(_serverSentEvents); + +const _logger = require('../lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _eventToApi = require('../lib/eventToApi'); + +const _eventToApi2 = _interopRequireDefault(_eventToApi); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec3 = (0, _serverSentEvents2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('post'), _dec11 = (0, _route2.default)('/v1/devices/events'), (_class = (function (_Controller) { + (0, _inherits3.default)(EventsController, _Controller); + + function EventsController(eventManager) { + (0, _classCallCheck3.default)(this, EventsController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (EventsController.__proto__ || (0, _getPrototypeOf2.default)(EventsController)).call(this)); + + _this._eventManager = eventManager; + return _this; + } + + (0, _createClass3.default)(EventsController, [{ + key: '_closeStream', + value: function _closeStream(subscriptionID) { + const _this2 = this; + + return new _promise2.default((resolve) => { + const closeStreamHandler = function closeStreamHandler() { + _this2._eventManager.unsubscribe(subscriptionID); + resolve(); + }; + + _this2.request.on('close', closeStreamHandler); + _this2.request.on('end', closeStreamHandler); + _this2.response.on('finish', closeStreamHandler); + _this2.response.on('end', closeStreamHandler); + }); + }, + }, { + key: '_pipeEvent', + value: function _pipeEvent(event) { + try { + this.response.write(`event: ${event.name}\n`); + this.response.write(`data: ${(0, _stringify2.default)((0, _eventToApi2.default)(event))}\n\n`); + } catch (error) { + _logger2.default.error(`pipeEvents - write error: ${error}`); + throw error; + } + }, + }, { + key: 'getEvents', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(eventNamePrefix) { + let subscriptionID; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { userID: this.user.id }); + _context.next = 3; + return this._closeStream(subscriptionID); + + case 3: + return _context.abrupt('return', this.ok()); + + case 4: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function getEvents(_x) { + return _ref.apply(this, arguments); + } + + return getEvents; + }()), + }, { + key: 'getMyEvents', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) { + let subscriptionID; + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { + mydevices: true, + userID: this.user.id, + }); + _context2.next = 3; + return this._closeStream(subscriptionID); + + case 3: + return _context2.abrupt('return', this.ok()); + + case 4: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function getMyEvents(_x2) { + return _ref2.apply(this, arguments); + } + + return getMyEvents; + }()), + }, { + key: 'getDeviceEvents', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, eventNamePrefix) { + let subscriptionID; + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { + deviceID, + userID: this.user.id, + }); + _context3.next = 3; + return this._closeStream(subscriptionID); + + case 3: + return _context3.abrupt('return', this.ok()); + + case 4: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function getDeviceEvents(_x3, _x4) { + return _ref3.apply(this, arguments); + } + + return getDeviceEvents; + }()), + }, { + key: 'publish', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(postBody) { + let eventData; + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + eventData = { + data: postBody.data, + isPublic: !postBody.private, + name: postBody.name, + ttl: postBody.ttl, + userID: this.user.id, + }; + + + this._eventManager.publish(eventData); + return _context4.abrupt('return', this.ok({ ok: true })); + + case 3: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function publish(_x5) { + return _ref4.apply(this, arguments); + } + + return publish; + }()), + }]); + return EventsController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class)); +exports.default = EventsController; diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js new file mode 100644 index 00000000..e9ff1c86 --- /dev/null +++ b/dist/controllers/OauthClientsController.js @@ -0,0 +1,183 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = (function (_Controller) { + (0, _inherits3.default)(OauthClientsController, _Controller); + + function OauthClientsController() { + (0, _classCallCheck3.default)(this, OauthClientsController); + return (0, _possibleConstructorReturn3.default)(this, (OauthClientsController.__proto__ || (0, _getPrototypeOf2.default)(OauthClientsController)).apply(this, arguments)); + } + + (0, _createClass3.default)(OauthClientsController, [{ + key: 'createClient', + + // eslint-disable-next-line class-methods-use-this + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function createClient() { + return _ref.apply(this, arguments); + } + + return createClient; + }()), + }, { + key: 'editClient', + + // eslint-disable-next-line class-methods-use-this + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function editClient() { + return _ref2.apply(this, arguments); + } + + return editClient; + }()), + }, { + key: 'deleteClient', + + // eslint-disable-next-line class-methods-use-this + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function deleteClient() { + return _ref3.apply(this, arguments); + } + + return deleteClient; + }()), + }]); + return OauthClientsController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class)); +exports.default = OauthClientsController; diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js new file mode 100644 index 00000000..0ec9cf30 --- /dev/null +++ b/dist/controllers/ProductsController.js @@ -0,0 +1,432 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _dec10, + _dec11, + _dec12, + _dec13, + _dec14, + _dec15, + _dec16, + _dec17, + _dec18, + _dec19, + _dec20, + _dec21, + _dec22, + _dec23, + _dec24, + _desc, + _value, + _class; +/* eslint-disable */ + +var _Controller2 = require('./Controller'); + +var _Controller3 = _interopRequireDefault(_Controller2); + +var _httpVerb = require('../decorators/httpVerb'); + +var _httpVerb2 = _interopRequireDefault(_httpVerb); + +var _route = require('../decorators/route'); + +var _route2 = _interopRequireDefault(_route); + +var _HttpError = require('../lib/HttpError'); + +var _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec7 = (0, _httpVerb2.default)('post'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/device_claims'), _dec9 = (0, _httpVerb2.default)('get'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec11 = (0, _httpVerb2.default)('post'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec15 = (0, _httpVerb2.default)('put'), _dec16 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec17 = (0, _httpVerb2.default)('delete'), _dec18 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec19 = (0, _httpVerb2.default)('get'), _dec20 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec21 = (0, _httpVerb2.default)('get'), _dec22 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec23 = (0, _httpVerb2.default)('delete'), _dec24 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) { + (0, _inherits3.default)(ProductsController, _Controller); + + function ProductsController(deviceManager) { + (0, _classCallCheck3.default)(this, ProductsController); + + var _this = (0, _possibleConstructorReturn3.default)(this, (ProductsController.__proto__ || (0, _getPrototypeOf2.default)(ProductsController)).call(this)); + + _this._deviceManager = deviceManager; + return _this; + } + + (0, _createClass3.default)(ProductsController, [{ + key: 'getProducts', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function getProducts() { + return _ref.apply(this, arguments); + } + + return getProducts; + }() + }, { + key: 'createProduct', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function createProduct() { + return _ref2.apply(this, arguments); + } + + return createProduct; + }() + }, { + key: 'getProduct', + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function getProduct(_x) { + return _ref3.apply(this, arguments); + } + + return getProduct; + }() + }, { + key: 'generateClaimCode', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function generateClaimCode(_x2) { + return _ref4.apply(this, arguments); + } + + return generateClaimCode; + }() + }, { + key: 'getFirmware', + value: function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context5.stop(); + } + } + }, _callee5, this); + })); + + function getFirmware(_x3) { + return _ref5.apply(this, arguments); + } + + return getFirmware; + }() + + // {version: number, name: 'current', binary: File, title: string, description: string} + + }, { + key: 'getFirmware', + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function getFirmware(_x4) { + return _ref6.apply(this, arguments); + } + + return getFirmware; + }() + }, { + key: 'getDevices', + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this); + })); + + function getDevices(_x5) { + return _ref7.apply(this, arguments); + } + + return getDevices; + }() + }, { + key: 'setFirmwareVersion', + value: function () { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productIdOrSlug, deviceID, body) { + return _regenerator2.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function setFirmwareVersion(_x6, _x7, _x8) { + return _ref8.apply(this, arguments); + } + + return setFirmwareVersion; + }() + }, { + key: 'removeDeviceFromProduct', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIdOrSlug, deviceID) { + return _regenerator2.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context9.stop(); + } + } + }, _callee9, this); + })); + + function removeDeviceFromProduct(_x9, _x10) { + return _ref9.apply(this, arguments); + } + + return removeDeviceFromProduct; + }() + }, { + key: 'getConfig', + value: function () { + var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(productIdOrSlug) { + return _regenerator2.default.wrap(function _callee10$(_context10) { + while (1) { + switch (_context10.prev = _context10.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context10.stop(); + } + } + }, _callee10, this); + })); + + function getConfig(_x11) { + return _ref10.apply(this, arguments); + } + + return getConfig; + }() + }, { + key: 'getEvents', + value: function () { + var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(productIdOrSlug, eventName) { + return _regenerator2.default.wrap(function _callee11$(_context11) { + while (1) { + switch (_context11.prev = _context11.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context11.stop(); + } + } + }, _callee11, this); + })); + + function getEvents(_x12, _x13) { + return _ref11.apply(this, arguments); + } + + return getEvents; + }() + }, { + key: 'removeTeamMember', + + // eslint-disable-next-line class-methods-use-this + value: function () { + var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug, username) { + return _regenerator2.default.wrap(function _callee12$(_context12) { + while (1) { + switch (_context12.prev = _context12.next) { + case 0: + throw new _HttpError2.default('not supported in the current server version'); + + case 1: + case 'end': + return _context12.stop(); + } + } + }, _callee12, this); + })); + + function removeTeamMember(_x14, _x15) { + return _ref12.apply(this, arguments); + } + + return removeTeamMember; + }() + }]); + return ProductsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'generateClaimCode', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'generateClaimCode'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec23, _dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class)); +exports.default = ProductsController; +/* eslint-enable */ diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js new file mode 100644 index 00000000..a65f7fdd --- /dev/null +++ b/dist/controllers/ProvisioningController.js @@ -0,0 +1,147 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +const _deviceToAPI = require('../lib/deviceToAPI'); + +const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/provisioning/:coreID'), (_class = (function (_Controller) { + (0, _inherits3.default)(ProvisioningController, _Controller); + + function ProvisioningController(deviceManager) { + (0, _classCallCheck3.default)(this, ProvisioningController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (ProvisioningController.__proto__ || (0, _getPrototypeOf2.default)(ProvisioningController)).call(this)); + + _this._deviceManager = deviceManager; + return _this; + } + + (0, _createClass3.default)(ProvisioningController, [{ + key: 'provision', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(coreID, postBody) { + let device; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (postBody.publicKey) { + _context.next = 2; + break; + } + + throw new _HttpError2.default('No key provided'); + + case 2: + _context.next = 4; + return this._deviceManager.provision(coreID, this.user.id, postBody.publicKey); + + case 4: + device = _context.sent; + return _context.abrupt('return', this.ok((0, _deviceToAPI2.default)(device))); + + case 6: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function provision(_x, _x2) { + return _ref.apply(this, arguments); + } + + return provision; + }()), + }]); + return ProvisioningController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'provision', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'provision'), _class.prototype)), _class)); +exports.default = ProvisioningController; diff --git a/dist/controllers/UsersController.js b/dist/controllers/UsersController.js new file mode 100644 index 00000000..86db684a --- /dev/null +++ b/dist/controllers/UsersController.js @@ -0,0 +1,243 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _dec9, + _desc, + _value, + _class; + +const _basicAuthParser3 = require('basic-auth-parser'); + +const _basicAuthParser4 = _interopRequireDefault(_basicAuthParser3); + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _anonymous = require('../decorators/anonymous'); + +const _anonymous2 = _interopRequireDefault(_anonymous); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/users'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('delete'), _dec5 = (0, _route2.default)('/v1/access_tokens/:token'), _dec6 = (0, _anonymous2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/access_tokens'), _dec9 = (0, _anonymous2.default)(), (_class = (function (_Controller) { + (0, _inherits3.default)(UsersController, _Controller); + + function UsersController(userRepository) { + (0, _classCallCheck3.default)(this, UsersController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (UsersController.__proto__ || (0, _getPrototypeOf2.default)(UsersController)).call(this)); + + _this._userRepository = userRepository; + return _this; + } + + (0, _createClass3.default)(UsersController, [{ + key: 'createUser', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + let isUserNameInUse; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.prev = 0; + _context.next = 3; + return this._userRepository.isUserNameInUse(userCredentials.username); + + case 3: + isUserNameInUse = _context.sent; + + if (!isUserNameInUse) { + _context.next = 6; + break; + } + + throw new _HttpError2.default('user with the username already exists'); + + case 6: + _context.next = 8; + return this._userRepository.createWithCredentials(userCredentials); + + case 8: + return _context.abrupt('return', this.ok({ ok: true })); + + case 11: + _context.prev = 11; + _context.t0 = _context.catch(0); + return _context.abrupt('return', this.bad(_context.t0.message)); + + case 14: + case 'end': + return _context.stop(); + } + } + }, _callee, this, [[0, 11]]); + })); + + function createUser(_x) { + return _ref.apply(this, arguments); + } + + return createUser; + }()), + }, { + key: 'deleteAccessToken', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(token) { + let _basicAuthParser, + username, + password, + user; + + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _basicAuthParser = (0, _basicAuthParser4.default)(this.request.get('authorization')), username = _basicAuthParser.username, password = _basicAuthParser.password; + _context2.next = 3; + return this._userRepository.validateLogin(username, password); + + case 3: + user = _context2.sent; + + + this._userRepository.deleteAccessToken(user.id, token); + + return _context2.abrupt('return', this.ok({ ok: true })); + + case 6: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function deleteAccessToken(_x2) { + return _ref2.apply(this, arguments); + } + + return deleteAccessToken; + }()), + }, { + key: 'getAccessTokens', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + let _basicAuthParser2, + username, + password, + user; + + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _basicAuthParser2 = (0, _basicAuthParser4.default)(this.request.get('authorization')), username = _basicAuthParser2.username, password = _basicAuthParser2.password; + _context3.next = 3; + return this._userRepository.validateLogin(username, password); + + case 3: + user = _context3.sent; + return _context3.abrupt('return', this.ok(user.accessTokens)); + + case 5: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function getAccessTokens() { + return _ref3.apply(this, arguments); + } + + return getAccessTokens; + }()), + }]); + return UsersController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createUser', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createUser'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteAccessToken', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteAccessToken'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAccessTokens', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAccessTokens'), _class.prototype)), _class)); +exports.default = UsersController; diff --git a/dist/controllers/WebhooksController.js b/dist/controllers/WebhooksController.js new file mode 100644 index 00000000..9783ca59 --- /dev/null +++ b/dist/controllers/WebhooksController.js @@ -0,0 +1,271 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _dec8, + _desc, + _value, + _class; + +const _Controller2 = require('./Controller'); + +const _Controller3 = _interopRequireDefault(_Controller2); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _httpVerb = require('../decorators/httpVerb'); + +const _httpVerb2 = _interopRequireDefault(_httpVerb); + +const _route = require('../decorators/route'); + +const _route2 = _interopRequireDefault(_route); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; + +const validateWebhookMutator = function validateWebhookMutator(webhookMutator) { + if (!webhookMutator.event) { + return new _HttpError2.default('no event name provided'); + } + if (!webhookMutator.url) { + return new _HttpError2.default('no url provided'); + } + if (!webhookMutator.requestType) { + return new _HttpError2.default('no requestType provided'); + } + if (!REQUEST_TYPES.includes(webhookMutator.requestType)) { + return new _HttpError2.default('wrong requestType'); + } + + return null; +}; + +const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookId'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookId'), (_class = (function (_Controller) { + (0, _inherits3.default)(WebhooksController, _Controller); + + function WebhooksController(webhookManager) { + (0, _classCallCheck3.default)(this, WebhooksController); + + const _this = (0, _possibleConstructorReturn3.default)(this, (WebhooksController.__proto__ || (0, _getPrototypeOf2.default)(WebhooksController)).call(this)); + + _this._webhookManager = webhookManager; + return _this; + } + + (0, _createClass3.default)(WebhooksController, [{ + key: 'getAll', + value: (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.t0 = this; + _context.next = 3; + return this._webhookManager.getAll(this.user.id); + + case 3: + _context.t1 = _context.sent; + return _context.abrupt('return', _context.t0.ok.call(_context.t0, _context.t1)); + + case 5: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function getAll() { + return _ref.apply(this, arguments); + } + + return getAll; + }()), + }, { + key: 'getById', + value: (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookId) { + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.t0 = this; + _context2.next = 3; + return this._webhookManager.getByID(webhookId, this.user.id); + + case 3: + _context2.t1 = _context2.sent; + return _context2.abrupt('return', _context2.t0.ok.call(_context2.t0, _context2.t1)); + + case 5: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + + function getById(_x) { + return _ref2.apply(this, arguments); + } + + return getById; + }()), + }, { + key: 'create', + value: (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + let validateError, + newWebhook; + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + validateError = validateWebhookMutator(model); + + if (!validateError) { + _context3.next = 3; + break; + } + + throw validateError; + + case 3: + _context3.next = 5; + return this._webhookManager.create((0, _extends3.default)({}, model, { + ownerID: this.user.id, + })); + + case 5: + newWebhook = _context3.sent; + return _context3.abrupt('return', this.ok({ + created_at: newWebhook.created_at, + event: newWebhook.event, + id: newWebhook.id, + ok: true, + url: newWebhook.url, + })); + + case 7: + case 'end': + return _context3.stop(); + } + } + }, _callee3, this); + })); + + function create(_x2) { + return _ref3.apply(this, arguments); + } + + return create; + }()), + }, { + key: 'deleteById', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookId) { + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return this._webhookManager.deleteByID(webhookId, this.user.id); + + case 2: + return _context4.abrupt('return', this.ok({ ok: true })); + + case 3: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function deleteById(_x3) { + return _ref4.apply(this, arguments); + } + + return deleteById; + }()), + }]); + return WebhooksController; +}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype)), _class)); +exports.default = WebhooksController; diff --git a/dist/controllers/types.js b/dist/controllers/types.js new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dist/controllers/types.js @@ -0,0 +1 @@ + diff --git a/dist/decorators/allowUpload.js b/dist/decorators/allowUpload.js new file mode 100644 index 00000000..53e1d236 --- /dev/null +++ b/dist/decorators/allowUpload.js @@ -0,0 +1,23 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function () { + const fileName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; + const maxCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + return function (target, name, descriptor) { + const allowedUploads = target[name].allowedUploads || []; + if (fileName) { + allowedUploads.push({ + maxCount, + name: fileName, + }); + } + + target[name].allowedUploads = allowedUploads; + return descriptor; + }; +}; diff --git a/dist/decorators/anonymous.js b/dist/decorators/anonymous.js new file mode 100644 index 00000000..78a9fa94 --- /dev/null +++ b/dist/decorators/anonymous.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function () { + return function (target, name, descriptor) { + target[name].anonymous = true; + return descriptor; + }; +}; diff --git a/dist/decorators/httpVerb.js b/dist/decorators/httpVerb.js new file mode 100644 index 00000000..a3d2df25 --- /dev/null +++ b/dist/decorators/httpVerb.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function (httpVerb) { + return function (target, name, descriptor) { + target[name].httpVerb = httpVerb; + return descriptor; + }; +}; diff --git a/dist/decorators/route.js b/dist/decorators/route.js new file mode 100644 index 00000000..d58c4772 --- /dev/null +++ b/dist/decorators/route.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function (route) { + return function (target, name, descriptor) { + target[name].route = route; + return descriptor; + }; +}; diff --git a/dist/decorators/serverSentEvents.js b/dist/decorators/serverSentEvents.js new file mode 100644 index 00000000..ddd1ca6a --- /dev/null +++ b/dist/decorators/serverSentEvents.js @@ -0,0 +1,13 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +/* eslint-disable no-param-reassign */ +exports.default = function () { + return function (target, name, descriptor) { + target[name].serverSentEvents = true; + return descriptor; + }; +}; diff --git a/dist/decorators/types.js b/dist/decorators/types.js new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dist/decorators/types.js @@ -0,0 +1 @@ + diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js new file mode 100644 index 00000000..7e801d93 --- /dev/null +++ b/dist/defaultBindings.js @@ -0,0 +1,102 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _sparkProtocol = require('spark-protocol'); + +const _DeviceClaimsController = require('./controllers/DeviceClaimsController'); + +const _DeviceClaimsController2 = _interopRequireDefault(_DeviceClaimsController); + +const _DevicesController = require('./controllers/DevicesController'); + +const _DevicesController2 = _interopRequireDefault(_DevicesController); + +const _EventsController = require('./controllers/EventsController'); + +const _EventsController2 = _interopRequireDefault(_EventsController); + +const _OauthClientsController = require('./controllers/OauthClientsController'); + +const _OauthClientsController2 = _interopRequireDefault(_OauthClientsController); + +const _ProductsController = require('./controllers/ProductsController'); + +const _ProductsController2 = _interopRequireDefault(_ProductsController); + +const _ProvisioningController = require('./controllers/ProvisioningController'); + +const _ProvisioningController2 = _interopRequireDefault(_ProvisioningController); + +const _UsersController = require('./controllers/UsersController'); + +const _UsersController2 = _interopRequireDefault(_UsersController); + +const _WebhooksController = require('./controllers/WebhooksController'); + +const _WebhooksController2 = _interopRequireDefault(_WebhooksController); + +const _WebhookManager = require('./managers/WebhookManager'); + +const _WebhookManager2 = _interopRequireDefault(_WebhookManager); + +const _EventManager = require('./managers/EventManager'); + +const _EventManager2 = _interopRequireDefault(_EventManager); + +const _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepository'); + +const _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository); + +const _DeviceManager = require('./managers/DeviceManager'); + +const _DeviceManager2 = _interopRequireDefault(_DeviceManager); + +const _UserFileRepository = require('./repository/UserFileRepository'); + +const _UserFileRepository2 = _interopRequireDefault(_UserFileRepository); + +const _WebhookFileRepository = require('./repository/WebhookFileRepository'); + +const _WebhookFileRepository2 = _interopRequireDefault(_WebhookFileRepository); + +const _settings = require('./settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (container) { + // spark protocol container bindings + (0, _sparkProtocol.defaultBindings)(container); + + // settings + container.bindValue('DEVICE_DIRECTORY', _settings2.default.DEVICE_DIRECTORY); + container.bindValue('FIRMWARE_DIRECTORY', _settings2.default.FIRMWARE_DIRECTORY); + container.bindValue('SERVER_KEY_FILENAME', _settings2.default.SERVER_KEY_FILENAME); + container.bindValue('SERVER_KEYS_DIRECTORY', _settings2.default.SERVER_KEYS_DIRECTORY); + container.bindValue('USERS_DIRECTORY', _settings2.default.USERS_DIRECTORY); + container.bindValue('WEBHOOKS_DIRECTORY', _settings2.default.WEBHOOKS_DIRECTORY); + + // controllers + container.bindClass('DeviceClaimsController', _DeviceClaimsController2.default, ['DeviceManager', 'ClaimCodeManager']); + container.bindClass('DevicesController', _DevicesController2.default, ['DeviceManager']); + container.bindClass('EventsController', _EventsController2.default, ['EventManager']); + container.bindClass('OauthClientsController', _OauthClientsController2.default, []); + container.bindClass('ProductsController', _ProductsController2.default, []); + container.bindClass('ProvisioningController', _ProvisioningController2.default, ['DeviceManager']); + container.bindClass('UsersController', _UsersController2.default, ['UserRepository']); + container.bindClass('WebhooksController', _WebhooksController2.default, ['WebhookManager']); + + // managers + container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'DeviceServer']); + container.bindClass('EventManager', _EventManager2.default, ['EventPublisher']); + container.bindClass('WebhookManager', _WebhookManager2.default, ['WebhookRepository', 'EventPublisher']); + + // Repositories + container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']); + container.bindClass('UserRepository', _UserFileRepository2.default, ['USERS_DIRECTORY']); + container.bindClass('WebhookRepository', _WebhookFileRepository2.default, ['WEBHOOKS_DIRECTORY']); +}; diff --git a/dist/exports.js b/dist/exports.js new file mode 100644 index 00000000..3bcb0cf9 --- /dev/null +++ b/dist/exports.js @@ -0,0 +1,29 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +exports.settings = exports.logger = exports.defaultBindings = exports.createApp = undefined; + +const _logger = require('./lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _app = require('./app'); + +const _app2 = _interopRequireDefault(_app); + +const _defaultBindings = require('./defaultBindings'); + +const _defaultBindings2 = _interopRequireDefault(_defaultBindings); + +const _settings = require('./settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.createApp = _app2.default; +exports.defaultBindings = _defaultBindings2.default; +exports.logger = _logger2.default; +exports.settings = _settings2.default; diff --git a/dist/lib/HttpError.js b/dist/lib/HttpError.js new file mode 100644 index 00000000..949d149a --- /dev/null +++ b/dist/lib/HttpError.js @@ -0,0 +1,45 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +const _inherits2 = require('babel-runtime/helpers/inherits'); + +const _inherits3 = _interopRequireDefault(_inherits2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const HttpError = (function (_Error) { + (0, _inherits3.default)(HttpError, _Error); + + function HttpError(error) { + const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + (0, _classCallCheck3.default)(this, HttpError); + + const _this = (0, _possibleConstructorReturn3.default)(this, (HttpError.__proto__ || (0, _getPrototypeOf2.default)(HttpError)).call(this, error.message || error)); + + if (typeof error.status === 'number') { + _this.status = error.status; + } else { + _this.status = status; + } + return _this; + } + + return HttpError; +}(Error)); + +exports.default = HttpError; diff --git a/dist/lib/PasswordHasher.js b/dist/lib/PasswordHasher.js new file mode 100644 index 00000000..d2cbd8e9 --- /dev/null +++ b/dist/lib/PasswordHasher.js @@ -0,0 +1,86 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +const _crypto = require('crypto'); + +const _crypto2 = _interopRequireDefault(_crypto); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const HASH_DIGEST = 'sha1'; /** + * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can download the source here: https://github.com/spark/spark-server + * + * + * + */ + +const HASH_ITERATIONS = 30000; +const KEY_LENGTH = 64; + +const PasswordHasher = (function () { + function PasswordHasher() { + (0, _classCallCheck3.default)(this, PasswordHasher); + } + + (0, _createClass3.default)(PasswordHasher, null, [{ + key: 'generateSalt', + value: function generateSalt() { + const size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 64; + + return new _promise2.default((resolve, reject) => { + _crypto2.default.randomBytes(size, (error, buffer) => { + if (error) { + reject(error); + return; + } + resolve(buffer.toString('base64')); + }); + }); + }, + }, { + key: 'hash', + value: function hash(password, salt) { + return new _promise2.default((resolve, reject) => { + _crypto2.default.pbkdf2(password, salt, HASH_ITERATIONS, KEY_LENGTH, HASH_DIGEST, (error, key) => { + if (error) { + reject(error); + return; + } + resolve(key.toString('base64')); + }); + }); + }, + }]); + return PasswordHasher; +}()); + +exports.default = PasswordHasher; diff --git a/dist/lib/deviceToAPI.js b/dist/lib/deviceToAPI.js new file mode 100644 index 00000000..f9f6c09e --- /dev/null +++ b/dist/lib/deviceToAPI.js @@ -0,0 +1,27 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +const deviceToAPI = function deviceToAPI(device, result) { + return { + cellular: device.isCellular, + connected: device.connected, + current_build_target: device.currentBuildTarget, + functions: device.functions, + id: device.deviceID, + imei: device.imei, + last_app: device.lastFlashedAppName, + last_heard: device.lastHeard, + last_iccid: device.last_iccid, + last_ip_address: device.ip, + name: device.name, + platform_id: device.particleProductId, + product_id: device.particleProductId, + return_value: result, + status: 'normal', + variables: device.variables, + }; +}; + +exports.default = deviceToAPI; diff --git a/dist/lib/eventToApi.js b/dist/lib/eventToApi.js new file mode 100644 index 00000000..80a3b835 --- /dev/null +++ b/dist/lib/eventToApi.js @@ -0,0 +1,15 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); +const eventToApi = function eventToApi(event) { + return { + coreid: event.deviceID || null, + data: event.data || null, + published_at: event.publishedAt, + ttl: event.ttl, + }; +}; + +exports.default = eventToApi; diff --git a/dist/lib/logger.js b/dist/lib/logger.js new file mode 100644 index 00000000..8d78a751 --- /dev/null +++ b/dist/lib/logger.js @@ -0,0 +1,63 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** +* Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +* +* You can download the source here: https://github.com/spark/spark-server +* +* +* +*/ + +const Logger = (function () { + function Logger() { + (0, _classCallCheck3.default)(this, Logger); + } + + (0, _createClass3.default)(Logger, null, [{ + key: 'log', + value: function log() { + let _console; + + // eslint-disable-next-line prefer-rest-params + (_console = console).log.apply(_console, arguments); + }, + }, { + key: 'error', + value: function error() { + let _console2; + + // eslint-disable-next-line prefer-rest-params + (_console2 = console).error.apply(_console2, arguments); + }, + }]); + return Logger; +}()); + +exports.default = Logger; diff --git a/dist/main.js b/dist/main.js new file mode 100644 index 00000000..28a70416 --- /dev/null +++ b/dist/main.js @@ -0,0 +1,76 @@ + + +const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); + +const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); + +const _entries = require('babel-runtime/core-js/object/entries'); + +const _entries2 = _interopRequireDefault(_entries); + +const _constitute = require('constitute'); + +const _os = require('os'); + +const _os2 = _interopRequireDefault(_os); + +const _arrayFlatten = require('array-flatten'); + +const _arrayFlatten2 = _interopRequireDefault(_arrayFlatten); + +const _logger = require('./lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _app = require('./app'); + +const _app2 = _interopRequireDefault(_app); + +const _defaultBindings = require('./defaultBindings'); + +const _defaultBindings2 = _interopRequireDefault(_defaultBindings); + +const _settings = require('./settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const NODE_PORT = process.env.NODE_PORT || 8080; + +process.on('uncaughtException', (exception) => { + _logger2.default.error('uncaughtException', { message: exception.message, stack: exception.stack }); // logging with MetaData + process.exit(1); // exit with failure +}); + +/* This is the container used app-wide for dependency injection. If you want to + * override any of the implementations, create your module with the new + * implementation and use: + * + * container.bindAlias(DefaultImplementation, MyNewImplementation); + * + * You can also set a new value + * container.bindAlias(DefaultValue, 12345); + * + * See https://github.com/justmoon/constitute for more info + */ +const container = new _constitute.Container(); +(0, _defaultBindings2.default)(container); + +const deviceServer = container.constitute('DeviceServer'); +deviceServer.start(); + +const app = (0, _app2.default)(container, _settings2.default); + +app.listen(NODE_PORT, () => console.log(`express server started on port ${NODE_PORT}`)); + +const addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map( +// eslint-disable-next-line no-unused-vars +(_ref) => { + let _ref2 = (0, _slicedToArray3.default)(_ref, 2), + name = _ref2[0], + nic = _ref2[1]; + + return nic.filter(address => address.family === 'IPv4' && address.address !== '127.0.0.1').map(address => address.address); +})); +addresses.forEach(address => console.log(`Your device server IP address is: ${address}`)); diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js new file mode 100644 index 00000000..bc9a550a --- /dev/null +++ b/dist/managers/DeviceManager.js @@ -0,0 +1,652 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); + +const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _ursa = require('ursa'); + +const _ursa2 = _interopRequireDefault(_ursa); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer) { + const _this = this; + + (0, _classCallCheck3.default)(this, DeviceManager); + + this.claimDevice = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) { + let deviceAttributes, + attributesToSave; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._deviceAttributeRepository.getById(deviceID); + + case 2: + deviceAttributes = _context.sent; + + if (deviceAttributes) { + _context.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + if (!(deviceAttributes.ownerID && deviceAttributes.ownerID !== userID)) { + _context.next = 7; + break; + } + + throw new _HttpError2.default('The device belongs to someone else.'); + + case 7: + attributesToSave = (0, _extends3.default)({}, deviceAttributes, { + ownerID: userID, + }); + _context.next = 10; + return _this._deviceAttributeRepository.update(attributesToSave); + + case 10: + return _context.abrupt('return', _context.sent); + + case 11: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x, _x2) { + return _ref.apply(this, arguments); + }; + }()); + + this.unclaimDevice = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID, userID) { + let deviceAttributes, + attributesToSave; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._deviceAttributeRepository.getById(deviceID, userID); + + case 2: + deviceAttributes = _context2.sent; + + if (deviceAttributes) { + _context2.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + attributesToSave = (0, _extends3.default)({}, deviceAttributes, { + ownerID: null, + }); + _context2.next = 8; + return _this._deviceAttributeRepository.update(attributesToSave); + + case 8: + return _context2.abrupt('return', _context2.sent); + + case 9: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x3, _x4) { + return _ref2.apply(this, arguments); + }; + }()); + + this.getByID = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, userID) { + let attributes, + device; + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _this._deviceAttributeRepository.getById(deviceID, userID); + + case 2: + attributes = _context3.sent; + + if (attributes) { + _context3.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + device = _this._deviceServer.getDevice(attributes.deviceID); + return _context3.abrupt('return', (0, _extends3.default)({}, attributes, { + connected: device && device.ping().connected || false, + lastFlashedAppName: null, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, + })); + + case 7: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x5, _x6) { + return _ref3.apply(this, arguments); + }; + }()); + + this.getDetailsByID = (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, userID) { + let device, + _ref5, + _ref6, + attributes, + description; + + return _regenerator2.default.wrap((_context4) => { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + device = _this._deviceServer.getDevice(deviceID); + _context4.next = 3; + return _promise2.default.all([_this._deviceAttributeRepository.getById(deviceID, userID), device && device.getDescription()]); + + case 3: + _ref5 = _context4.sent; + _ref6 = (0, _slicedToArray3.default)(_ref5, 2); + attributes = _ref6[0]; + description = _ref6[1]; + + if (attributes) { + _context4.next = 9; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 9: + return _context4.abrupt('return', (0, _extends3.default)({}, attributes, { + connected: device && device.ping().connected || false, + functions: description ? description.state.f : null, + lastFlashedAppName: null, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, + variables: description ? description.state.v : null, + })); + + case 10: + case 'end': + return _context4.stop(); + } + } + }, _callee4, _this); + })); + + return function (_x7, _x8) { + return _ref4.apply(this, arguments); + }; + }()); + + this.getAll = (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(userID) { + let devicesAttributes, + devicePromises; + return _regenerator2.default.wrap((_context6) => { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + _context6.next = 2; + return _this._deviceAttributeRepository.getAll(userID); + + case 2: + devicesAttributes = _context6.sent; + devicePromises = devicesAttributes.map(function () { + const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) { + let device; + return _regenerator2.default.wrap((_context5) => { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + device = _this._deviceServer.getDevice(attributes.deviceID); + return _context5.abrupt('return', (0, _extends3.default)({}, attributes, { + connected: device && device.ping().connected || false, + lastFlashedAppName: null, + lastHeard: device && device.ping().lastPing || attributes.lastHeard, + })); + + case 2: + case 'end': + return _context5.stop(); + } + } + }, _callee5, _this); + })); + + return function (_x10) { + return _ref8.apply(this, arguments); + }; + }()); + return _context6.abrupt('return', _promise2.default.all(devicePromises)); + + case 5: + case 'end': + return _context6.stop(); + } + } + }, _callee6, _this); + })); + + return function (_x9) { + return _ref7.apply(this, arguments); + }; + }()); + + this.callFunction = (function () { + const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, userID, functionName, functionArguments) { + let doesUserHaveAccess, + device; + return _regenerator2.default.wrap((_context7) => { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + _context7.next = 2; + return _this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + doesUserHaveAccess = _context7.sent; + + if (doesUserHaveAccess) { + _context7.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context7.next = 8; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 8: + _context7.next = 10; + return device.callFunction(functionName, functionArguments); + + case 10: + return _context7.abrupt('return', _context7.sent); + + case 11: + case 'end': + return _context7.stop(); + } + } + }, _callee7, _this); + })); + + return function (_x11, _x12, _x13, _x14) { + return _ref9.apply(this, arguments); + }; + }()); + + this.getVariableValue = (function () { + const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, userID, varName) { + let doesUserHaveAccess, + device; + return _regenerator2.default.wrap((_context8) => { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + _context8.next = 2; + return _this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + doesUserHaveAccess = _context8.sent; + + if (doesUserHaveAccess) { + _context8.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context8.next = 8; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 8: + _context8.next = 10; + return device.getVariableValue(varName); + + case 10: + return _context8.abrupt('return', _context8.sent); + + case 11: + case 'end': + return _context8.stop(); + } + } + }, _callee8, _this); + })); + + return function (_x15, _x16, _x17) { + return _ref10.apply(this, arguments); + }; + }()); + + this.flashBinary = (function () { + const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) { + let device; + return _regenerator2.default.wrap((_context9) => { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context9.next = 3; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 3: + _context9.next = 5; + return device.flash(file.buffer); + + case 5: + return _context9.abrupt('return', _context9.sent); + + case 6: + case 'end': + return _context9.stop(); + } + } + }, _callee9, _this); + })); + + return function (_x18, _x19) { + return _ref11.apply(this, arguments); + }; + }()); + + this.flashKnownApp = (function () { + const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, userID, appName) { + let knownFirmware, + device; + return _regenerator2.default.wrap((_context10) => { + while (1) { + switch (_context10.prev = _context10.next) { + case 0: + _context10.next = 2; + return !_this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + if (!_context10.sent) { + _context10.next = 4; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 4: + knownFirmware = _this._deviceFirmwareRepository.getByName(appName); + + if (knownFirmware) { + _context10.next = 7; + break; + } + + throw new _HttpError2.default(`No firmware ${appName} found`, 404); + + case 7: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context10.next = 10; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 10: + _context10.next = 12; + return device.flash(knownFirmware); + + case 12: + return _context10.abrupt('return', _context10.sent); + + case 13: + case 'end': + return _context10.stop(); + } + } + }, _callee10, _this); + })); + + return function (_x20, _x21, _x22) { + return _ref12.apply(this, arguments); + }; + }()); + + this.provision = (function () { + const _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey) { + let createdKey, + existingAttributes, + attributes; + return _regenerator2.default.wrap((_context11) => { + while (1) { + switch (_context11.prev = _context11.next) { + case 0: + _context11.prev = 0; + createdKey = _ursa2.default.createPublicKey(publicKey); + + if (_ursa2.default.isPublicKey(createdKey)) { + _context11.next = 4; + break; + } + + throw new _HttpError2.default('Not a public key'); + + case 4: + _context11.next = 9; + break; + + case 6: + _context11.prev = 6; + _context11.t0 = _context11.catch(0); + throw new _HttpError2.default(`Key error ${_context11.t0}`); + + case 9: + _context11.next = 11; + return _this._deviceKeyRepository.update(deviceID, publicKey); + + case 11: + _context11.next = 13; + return _this._deviceAttributeRepository.getById(deviceID); + + case 13: + existingAttributes = _context11.sent; + attributes = (0, _extends3.default)({ + deviceID, + }, existingAttributes, { + ownerID: userID, + registrar: userID, + timestamp: new Date(), + }); + _context11.next = 17; + return _this._deviceAttributeRepository.update(attributes); + + case 17: + _context11.next = 19; + return _this.getByID(deviceID, userID); + + case 19: + return _context11.abrupt('return', _context11.sent); + + case 20: + case 'end': + return _context11.stop(); + } + } + }, _callee11, _this, [[0, 6]]); + })); + + return function (_x23, _x24, _x25) { + return _ref13.apply(this, arguments); + }; + }()); + + this.raiseYourHand = (function () { + const _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, userID, shouldShowSignal) { + let device; + return _regenerator2.default.wrap((_context12) => { + while (1) { + switch (_context12.prev = _context12.next) { + case 0: + _context12.next = 2; + return !_this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID); + + case 2: + if (!_context12.sent) { + _context12.next = 4; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 4: + device = _this._deviceServer.getDevice(deviceID); + + if (device) { + _context12.next = 7; + break; + } + + throw new _HttpError2.default('Could not get device for ID', 404); + + case 7: + _context12.next = 9; + return device.raiseYourHand(shouldShowSignal); + + case 9: + return _context12.abrupt('return', _context12.sent); + + case 10: + case 'end': + return _context12.stop(); + } + } + }, _callee12, _this); + })); + + return function (_x26, _x27, _x28) { + return _ref14.apply(this, arguments); + }; + }()); + + this.renameDevice = (function () { + const _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, userID, name) { + let attributes, + attributesToSave; + return _regenerator2.default.wrap((_context13) => { + while (1) { + switch (_context13.prev = _context13.next) { + case 0: + _context13.next = 2; + return _this._deviceAttributeRepository.getById(deviceID, userID); + + case 2: + attributes = _context13.sent; + + if (attributes) { + _context13.next = 5; + break; + } + + throw new _HttpError2.default('No device found', 404); + + case 5: + attributesToSave = (0, _extends3.default)({}, attributes, { + name, + }); + _context13.next = 8; + return _this._deviceAttributeRepository.update(attributesToSave); + + case 8: + return _context13.abrupt('return', _context13.sent); + + case 9: + case 'end': + return _context13.stop(); + } + } + }, _callee13, _this); + })); + + return function (_x29, _x30, _x31) { + return _ref15.apply(this, arguments); + }; + }()); + + this._deviceAttributeRepository = deviceAttributeRepository; + this._deviceFirmwareRepository = deviceFirmwareRepository; + this._deviceKeyRepository = deviceKeyRepository; + this._deviceServer = deviceServer; +}; + +exports.default = DeviceManager; diff --git a/dist/managers/EventManager.js b/dist/managers/EventManager.js new file mode 100644 index 00000000..44c33acb --- /dev/null +++ b/dist/managers/EventManager.js @@ -0,0 +1,33 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const EventManager = function EventManager(eventPublisher) { + const _this = this; + + (0, _classCallCheck3.default)(this, EventManager); + + this.subscribe = function (eventNamePrefix, eventHandler, filterOptions) { + return _this._eventPublisher.subscribe(eventNamePrefix, eventHandler, filterOptions); + }; + + this.unsubscribe = function (subscriptionID) { + return _this._eventPublisher.unsubscribe(subscriptionID); + }; + + this.publish = function (eventData) { + return _this._eventPublisher.publish(eventData); + }; + + this._eventPublisher = eventPublisher; +}; + +exports.default = EventManager; diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js new file mode 100644 index 00000000..1766ca29 --- /dev/null +++ b/dist/managers/FirmwareCompilationManager.js @@ -0,0 +1,263 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _stringify = require('babel-runtime/core-js/json/stringify'); + +const _stringify2 = _interopRequireDefault(_stringify); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _map = require('babel-runtime/core-js/map'); + +const _map2 = _interopRequireDefault(_map); + +const _crypto = require('crypto'); + +const _crypto2 = _interopRequireDefault(_crypto); + +const _fs = require('fs'); + +const _fs2 = _interopRequireDefault(_fs); + +const _path = require('path'); + +const _path2 = _interopRequireDefault(_path); + +const _mkdirp = require('mkdirp'); + +const _mkdirp2 = _interopRequireDefault(_mkdirp); + +const _rmfr = require('rmfr'); + +const _rmfr2 = _interopRequireDefault(_rmfr); + +const _child_process = require('child_process'); + +const _sparkProtocol = require('spark-protocol'); + +const _settings = require('../settings'); + +const _settings2 = _interopRequireDefault(_settings); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const IS_COMPILATION_ENABLED = _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); + +const USER_APP_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications'); +const BIN_PATH = _path2.default.join(_settings2.default.BUILD_DIRECTORY, 'bin'); +const MAKE_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'main'); + +const FILE_NAME_BY_KEY = new _map2.default(); + +const getKey = function getKey() { + return _crypto2.default.randomBytes(24).toString('hex').substring(0, 24); +}; + +const getUniqueKey = function getUniqueKey() { + let key = getKey(); + while (FILE_NAME_BY_KEY.has(key)) { + key = getKey(); + } + return key; +}; + +const FirmwareCompilationManager = function FirmwareCompilationManager() { + (0, _classCallCheck3.default)(this, FirmwareCompilationManager); +}; + +FirmwareCompilationManager.firmwareDirectoryExists = function () { + return _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); +}; + +FirmwareCompilationManager.getBinaryForID = function (id) { + if (!FirmwareCompilationManager.firmwareDirectoryExists()) { + return null; + } + + const binaryPath = _path2.default.join(BIN_PATH, id); + if (!_fs2.default.existsSync(binaryPath)) { + return null; + } + + const binFileName = _fs2.default.readdirSync(binaryPath).find(file => file.endsWith('.bin')); + + if (!binFileName) { + return null; + } + + return _fs2.default.readFileSync(_path2.default.join(binaryPath, binFileName)); +}; + +FirmwareCompilationManager.compileSource = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(platformID, files) { + let platformName, + appFolder, + appPath, + id, + binPath, + makeProcess, + errors, + sizeInfo, + date, + config; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + if (FirmwareCompilationManager.firmwareDirectoryExists()) { + _context.next = 2; + break; + } + + return _context.abrupt('return', null); + + case 2: + platformName = _sparkProtocol.knownPlatforms[platformID]; + + if (platformName) { + _context.next = 5; + break; + } + + return _context.abrupt('return', null); + + case 5: + + platformName = platformName.toLowerCase(); + appFolder = (`${platformName}_firmware_${new Date().getTime()}`).toLowerCase(); + appPath = _path2.default.join(USER_APP_PATH, appFolder); + + _mkdirp2.default.sync(appPath); + + files.forEach((file) => { + const fileName = file.originalname; + const fileExtension = _path2.default.extname(fileName); + let iterator = 0; + let combinedPath = _path2.default.join(appPath, fileName); + + while (_fs2.default.existsSync(combinedPath)) { + combinedPath = _path2.default.join(appPath, `${_path2.default.basename(fileName, fileExtension)}_${iterator++}${fileExtension}`); + } + + _fs2.default.writeFileSync(combinedPath, file.buffer); + }); + + id = getUniqueKey(); + binPath = _path2.default.join(BIN_PATH, id); + makeProcess = (0, _child_process.spawn)('make', [`APP=${appFolder}`, `PLATFORM_ID=${platformID}`, `TARGET_DIR=${_path2.default.relative(MAKE_PATH, binPath).replace(/\\/g, '/')}`], { cwd: MAKE_PATH }); + errors = []; + + makeProcess.stderr.on('data', (data) => { + console.log(`${data}`); + errors.push(`${data}`); + }); + + sizeInfo = 'not implemented'; + + makeProcess.stdout.on('data', (data) => { + const output = `${data}`; + + if (output.includes('text\t')) { + sizeInfo = output; + } + }); + + _context.next = 19; + return new _promise2.default((resolve) => { + makeProcess.on('exit', () => resolve()); + }); + + case 19: + date = new Date(); + + date.setDate(date.getDate() + 1); + config = { + binary_id: id, + errors, + // expire in one day + expires_at: date, + + // TODO: this variable has a bunch of extra crap including file names. + // we should filter out the string to only show the file sizes + sizeInfo, + }; + + + FirmwareCompilationManager.addFirmwareCleanupTask(appPath, config); + + return _context.abrupt('return', config); + + case 24: + case 'end': + return _context.stop(); + } + } + }, _callee, undefined); + })); + + return function (_x, _x2) { + return _ref.apply(this, arguments); + }; +}()); + +FirmwareCompilationManager.addFirmwareCleanupTask = function (appFolderPath, config) { + const configPath = _path2.default.join(appFolderPath, 'config.json'); + if (!_fs2.default.existsSync(configPath)) { + _fs2.default.writeFileSync(configPath, (0, _stringify2.default)(config)); + } + const currentDate = new Date(); + const difference = new Date(config.expires_at).getTime() - currentDate.getTime(); + setTimeout(() => (0, _rmfr2.default)(appFolderPath), difference); +}; + +if (IS_COMPILATION_ENABLED) { + // Delete all expired binaries or queue them up to eventually be deleted. + if (!_fs2.default.existsSync(_settings2.default.BUILD_DIRECTORY)) { + _mkdirp2.default.sync(_settings2.default.BUILD_DIRECTORY); + } + if (!_fs2.default.existsSync(BIN_PATH)) { + _mkdirp2.default.sync(BIN_PATH); + } + + _fs2.default.readdirSync(USER_APP_PATH).forEach((file) => { + const appFolder = _path2.default.join(USER_APP_PATH, file); + const configPath = _path2.default.join(appFolder, 'config.json'); + if (!_fs2.default.existsSync(configPath)) { + return; + } + + const configString = _fs2.default.readFileSync(configPath, 'utf8'); + if (!configString) { + return; + } + const config = JSON.parse(configString); + if (config.expires_at < new Date()) { + // TODO - clean up artifacts in the firmware folder. Every binary will have + // files in firmare/build/target/user & firmware/build/target/user-part + // It might make the most sense to just create a custom MAKE file to do this + (0, _rmfr2.default)(configPath); + (0, _rmfr2.default)(_path2.default.join(BIN_PATH, config.binary_id)); + } else { + FirmwareCompilationManager.addFirmwareCleanupTask(appFolder, config); + } + }); +} + +exports.default = FirmwareCompilationManager; diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js new file mode 100644 index 00000000..b93dbcf0 --- /dev/null +++ b/dist/managers/WebhookManager.js @@ -0,0 +1,533 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _promise = require('babel-runtime/core-js/promise'); + +const _promise2 = _interopRequireDefault(_promise); + +const _typeof2 = require('babel-runtime/helpers/typeof'); + +const _typeof3 = _interopRequireDefault(_typeof2); + +const _stringify = require('babel-runtime/core-js/json/stringify'); + +const _stringify2 = _interopRequireDefault(_stringify); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _map = require('babel-runtime/core-js/map'); + +const _map2 = _interopRequireDefault(_map); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _hogan = require('hogan.js'); + +const _hogan2 = _interopRequireDefault(_hogan); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +const _logger = require('../lib/logger'); + +const _logger2 = _interopRequireDefault(_logger); + +const _nullthrows = require('nullthrows'); + +const _nullthrows2 = _interopRequireDefault(_nullthrows); + +const _request = require('request'); + +const _request2 = _interopRequireDefault(_request); + +const _throttle = require('lodash/throttle'); + +const _throttle2 = _interopRequireDefault(_throttle); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const parseEventData = function parseEventData(event) { + try { + if (event.data) { + return JSON.parse(event.data); + } + return {}; + } catch (error) { + return {}; + } +}; + +const splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { + const chunks = []; + let ii = 0; + while (ii < buffer.length) { + chunks.push(buffer.slice(ii, ii += chunkSize)); + } + + return chunks; +}; + +const MAX_WEBHOOK_ERRORS_COUNT = 10; +const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; +const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; +const MAX_RESPONSE_MESSAGE_SIZE = 100000; + +const WebhookManager = function WebhookManager(webhookRepository, eventPublisher) { + const _this = this; + + (0, _classCallCheck3.default)(this, WebhookManager); + this._subscriptionIDsByWebhookID = new _map2.default(); + this._errorsCountByWebhookID = new _map2.default(); + + this.create = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) { + let webhook; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._webhookRepository.create(model); + + case 2: + webhook = _context.sent; + + _this._subscribeWebhook(webhook); + return _context.abrupt('return', webhook); + + case 5: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x) { + return _ref.apply(this, arguments); + }; + }()); + + this.deleteByID = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID, userID) { + let webhook; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._webhookRepository.getById(webhookID, userID); + + case 2: + webhook = _context2.sent; + + if (webhook) { + _context2.next = 5; + break; + } + + throw new _HttpError2.default('no webhook found', 404); + + case 5: + _context2.next = 7; + return _this._webhookRepository.deleteById(webhookID); + + case 7: + _this._unsubscribeWebhookByID(webhookID); + + case 8: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x2, _x3) { + return _ref2.apply(this, arguments); + }; + }()); + + this.getAll = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID) { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _this._webhookRepository.getAll(userID); + + case 2: + return _context3.abrupt('return', _context3.sent); + + case 3: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x4) { + return _ref3.apply(this, arguments); + }; + }()); + + this.getByID = (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID, userID) { + let webhook; + return _regenerator2.default.wrap((_context4) => { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return _this._webhookRepository.getById(webhookID, userID); + + case 2: + webhook = _context4.sent; + + if (webhook) { + _context4.next = 5; + break; + } + + throw new _HttpError2.default('no webhook found', 404); + + case 5: + return _context4.abrupt('return', webhook); + + case 6: + case 'end': + return _context4.stop(); + } + } + }, _callee4, _this); + })); + + return function (_x5, _x6) { + return _ref4.apply(this, arguments); + }; + }()); + + this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { + let allWebhooks; + return _regenerator2.default.wrap((_context5) => { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + _context5.next = 2; + return _this._webhookRepository.getAll(); + + case 2: + allWebhooks = _context5.sent; + + allWebhooks.forEach(webhook => _this._subscribeWebhook(webhook)); + + case 4: + case 'end': + return _context5.stop(); + } + } + }, _callee5, _this); + })); + + this._subscribeWebhook = function (webhook) { + const subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), { + deviceID: webhook.deviceID, + mydevices: webhook.mydevices, + userID: webhook.ownerID, + }); + _this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID); + }; + + this._unsubscribeWebhookByID = function (webhookID) { + const subscriptionID = _this._subscriptionIDsByWebhookID.get(webhookID); + if (!subscriptionID) { + return; + } + + _this._eventPublisher.unsubscribe(subscriptionID); + _this._subscriptionIDsByWebhookID.delete(webhookID); + }; + + this._onNewWebhookEvent = function (webhook) { + return function (event) { + try { + const webhookErrorCount = _this._errorsCountByWebhookID.get(webhook.id) || 0; + + if (webhookErrorCount < MAX_WEBHOOK_ERRORS_COUNT) { + _this.runWebhook(webhook, event); + return; + } + + _this._eventPublisher.publish({ + data: 'Too many errors, webhook disabled', + isPublic: false, + name: _this._compileErrorResponseTopic(webhook, event), + userID: event.userID, + }); + + _this.runWebhookThrottled(webhook, event); + } catch (error) { + _logger2.default.error(`webhookError: ${error}`); + } + }; + }; + + this.runWebhook = (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(webhook, event) { + let _ret; + + return _regenerator2.default.wrap((_context7) => { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + _context7.prev = 0; + return _context7.delegateYield(_regenerator2.default.mark(function _callee6() { + let webhookVariablesObject, + requestJson, + requestFormData, + requestUrl, + requestQuery, + responseTopic, + isJsonRequest, + requestOptions, + responseBody, + isResponseBodyAnObject, + responseTemplate, + responseEventData, + chunks; + return _regenerator2.default.wrap((_context6) => { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + webhookVariablesObject = _this._getEventVariables(event); + requestJson = _this._compileJsonTemplate(webhook.json, webhookVariablesObject); + requestFormData = _this._compileJsonTemplate(webhook.form, webhookVariablesObject); + requestUrl = _this._compileTemplate(webhook.url, webhookVariablesObject); + requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject); + responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject); + isJsonRequest = !!requestJson; + requestOptions = { + auth: webhook.auth, + body: isJsonRequest ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined, + form: !isJsonRequest ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined, + headers: webhook.headers, + json: true, + method: webhook.requestType, + qs: requestQuery, + strictSSL: webhook.rejectUnauthorized, + url: (0, _nullthrows2.default)(requestUrl), + }; + _context6.next = 10; + return _this._callWebhook(webhook, event, requestOptions); + + case 10: + responseBody = _context6.sent; + + if (responseBody) { + _context6.next = 13; + break; + } + + return _context6.abrupt('return', { + v: void 0, + }); + + case 13: + isResponseBodyAnObject = responseBody === Object(responseBody); + responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(responseBody); + responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(responseBody) : responseBody); + chunks = splitBufferIntoChunks(Buffer.from(responseEventData).slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE); + + + chunks.forEach((chunk, index) => { + const responseEventName = responseTopic && `${responseTopic}/${index}` || `hook-response/${event.name}/${index}`; + + _this._eventPublisher.publish({ + data: chunk, + isPublic: false, + name: responseEventName, + userID: event.userID, + }); + }); + + case 18: + case 'end': + return _context6.stop(); + } + } + }, _callee6, _this); + })(), 't0', 2); + + case 2: + _ret = _context7.t0; + + if (!((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === 'object')) { + _context7.next = 5; + break; + } + + return _context7.abrupt('return', _ret.v); + + case 5: + _context7.next = 10; + break; + + case 7: + _context7.prev = 7; + _context7.t1 = _context7.catch(0); + + _logger2.default.error(`webhookError: ${_context7.t1}`); + + case 10: + case 'end': + return _context7.stop(); + } + } + }, _callee7, _this, [[0, 7]]); + })); + + return function (_x7, _x8) { + return _ref6.apply(this, arguments); + }; + }()); + + this.runWebhookThrottled = (0, _throttle2.default)(this.runWebhook, WEBHOOK_THROTTLE_TIME, { leading: false, trailing: true }); + + this._callWebhook = function (webhook, event, requestOptions) { + return new _promise2.default((resolve, reject) => (0, _request2.default)(requestOptions, (error, response, responseBody) => { + const onResponseError = function onResponseError(errorMessage) { + _this._incrementWebhookErrorCounter(webhook.id); + + _this._eventPublisher.publish({ + data: errorMessage, + isPublic: false, + name: _this._compileErrorResponseTopic(webhook, event), + userID: event.userID, + }); + + reject(new Error(errorMessage)); + }; + + if (error) { + onResponseError(error.message); + return; + } + if (response.statusCode >= 400) { + onResponseError(response.statusMessage); + return; + } + + _this._resetWebhookErrorCounter(webhook.id); + + _this._eventPublisher.publish({ + isPublic: false, + name: `hook-sent/${event.name}`, + userID: event.userID, + }); + + resolve(responseBody); + })); + }; + + this._getEventVariables = function (event) { + const defaultWebhookVariables = { + PARTICLE_DEVICE_ID: event.deviceID, + PARTICLE_EVENT_NAME: event.name, + PARTICLE_EVENT_VALUE: event.data, + PARTICLE_PUBLISHED_AT: event.publishedAt, + // old event names, added for compatibility + SPARK_CORE_ID: event.deviceID, + SPARK_EVENT_NAME: event.name, + SPARK_EVENT_VALUE: event.data, + SPARK_PUBLISHED_AT: event.publishedAt, + }; + + const eventDataVariables = parseEventData(event); + + return (0, _extends3.default)({}, defaultWebhookVariables, eventDataVariables); + }; + + this._getRequestData = function (customData, event, noDefaults) { + const defaultEventData = { + coreid: event.deviceID, + data: event.data, + event: event.name, + published_at: event.publishedAt, + }; + + return noDefaults ? customData : (0, _extends3.default)({}, defaultEventData, customData || {}); + }; + + this._compileTemplate = function (template, variables) { + return template && _hogan2.default.compile(template).render(variables); + }; + + this._compileJsonTemplate = function (template, variables) { + if (!template) { + return undefined; + } + + const compiledTemplate = _this._compileTemplate((0, _stringify2.default)(template), variables); + if (!compiledTemplate) { + return undefined; + } + + return JSON.parse(compiledTemplate); + }; + + this._compileErrorResponseTopic = function (webhook, event) { + const variables = _this._getEventVariables(event); + return _this._compileTemplate(webhook.errorResponseTopic, variables) || `hook-error/${event.name}`; + }; + + this._incrementWebhookErrorCounter = function (webhookID) { + const errorsCount = _this._errorsCountByWebhookID.get(webhookID) || 0; + _this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); + }; + + this._resetWebhookErrorCounter = function (webhookID) { + _this._errorsCountByWebhookID.set(webhookID, 0); + }; + + this._webhookRepository = webhookRepository; + this._eventPublisher = eventPublisher; + + (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { + return _regenerator2.default.wrap((_context8) => { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + _context8.next = 2; + return _this._init(); + + case 2: + return _context8.abrupt('return', _context8.sent); + + case 3: + case 'end': + return _context8.stop(); + } + } + }, _callee8, _this); + }))(); +}; + +exports.default = WebhookManager; diff --git a/dist/repository/DeviceFirmwareFileRepository.js b/dist/repository/DeviceFirmwareFileRepository.js new file mode 100644 index 00000000..7255f1b7 --- /dev/null +++ b/dist/repository/DeviceFirmwareFileRepository.js @@ -0,0 +1,70 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +let _dec, + _desc, + _value, + _class; + +const _sparkProtocol = require('spark-protocol'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], { promise: false }), (_class = (function () { + function DeviceFirmwareFileRepository(path) { + (0, _classCallCheck3.default)(this, DeviceFirmwareFileRepository); + + this._fileManager = new _sparkProtocol.FileManager(path, false); + } + + (0, _createClass3.default)(DeviceFirmwareFileRepository, [{ + key: 'getByName', + value: function getByName(appName) { + return this._fileManager.getFileBuffer(`${appName}.bin`); + }, + }]); + return DeviceFirmwareFileRepository; +}()), (_applyDecoratedDescriptor(_class.prototype, 'getByName', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByName'), _class.prototype)), _class)); +exports.default = DeviceFirmwareFileRepository; diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js new file mode 100644 index 00000000..33353a83 --- /dev/null +++ b/dist/repository/UserFileRepository.js @@ -0,0 +1,535 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); + +const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +let _dec, + _dec2, + _dec3, + _dec4, + _dec5, + _dec6, + _dec7, + _desc, + _value, + _class; + +const _uuid = require('uuid'); + +const _uuid2 = _interopRequireDefault(_uuid); + +const _sparkProtocol = require('spark-protocol'); + +const _PasswordHasher = require('../lib/PasswordHasher'); + +const _PasswordHasher2 = _interopRequireDefault(_PasswordHasher); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(['id']), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = (function () { + function UserFileRepository(path) { + const _this = this; + + (0, _classCallCheck3.default)(this, UserFileRepository); + + this.createWithCredentials = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + let username, + password, + salt, + passwordHash, + modelToSave; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + username = userCredentials.username, password = userCredentials.password; + _context.next = 3; + return _PasswordHasher2.default.generateSalt(); + + case 3: + salt = _context.sent; + _context.next = 6; + return _PasswordHasher2.default.hash(password, salt); + + case 6: + passwordHash = _context.sent; + modelToSave = { + accessTokens: [], + passwordHash, + salt, + username, + }; + _context.next = 10; + return _this.create(modelToSave); + + case 10: + return _context.abrupt('return', _context.sent); + + case 11: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function (_x) { + return _ref.apply(this, arguments); + }; + }()); + + this.validateLogin = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + let user, + hash; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.prev = 0; + _context2.next = 3; + return _this.getByUsername(username); + + case 3: + user = _context2.sent; + + if (user) { + _context2.next = 6; + break; + } + + throw new Error('User doesn\'t exist'); + + case 6: + _context2.next = 8; + return _PasswordHasher2.default.hash(password, user.salt); + + case 8: + hash = _context2.sent; + + if (!(hash !== user.passwordHash)) { + _context2.next = 11; + break; + } + + throw new Error('Wrong password'); + + case 11: + return _context2.abrupt('return', user); + + case 14: + _context2.prev = 14; + _context2.t0 = _context2.catch(0); + throw _context2.t0; + + case 17: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this, [[0, 14]]); + })); + + return function (_x2, _x3) { + return _ref2.apply(this, arguments); + }; + }()); + + this.getByAccessToken = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(accessToken) { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _this.getAll(); + + case 2: + _context3.t0 = function (user) { + return user.accessTokens.some(tokenObject => tokenObject.accessToken === accessToken); + }; + + return _context3.abrupt('return', _context3.sent.find(_context3.t0)); + + case 4: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x4) { + return _ref3.apply(this, arguments); + }; + }()); + + this.deleteAccessToken = (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, token) { + let user, + userToSave; + return _regenerator2.default.wrap((_context4) => { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + _context4.next = 2; + return _this.getById(userID); + + case 2: + user = _context4.sent; + + if (user) { + _context4.next = 5; + break; + } + + throw new Error('User doesn\'t exist'); + + case 5: + userToSave = (0, _extends3.default)({}, user, { + accessTokens: user.accessTokens.filter(tokenObject => tokenObject.accessToken !== token), + }); + _context4.next = 8; + return _this.update(userToSave); + + case 8: + case 'end': + return _context4.stop(); + } + } + }, _callee4, _this); + })); + + return function (_x5, _x6) { + return _ref4.apply(this, arguments); + }; + }()); + + this.saveAccessToken = (function () { + const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) { + let user, + userToSave; + return _regenerator2.default.wrap((_context5) => { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + _context5.next = 2; + return _this.getById(userID); + + case 2: + user = _context5.sent; + + if (user) { + _context5.next = 5; + break; + } + + throw new _HttpError2.default('Could not find user for user ID'); + + case 5: + userToSave = (0, _extends3.default)({}, user, { + accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]), + }); + _context5.next = 8; + return _this.update(userToSave); + + case 8: + return _context5.abrupt('return', _context5.sent); + + case 9: + case 'end': + return _context5.stop(); + } + } + }, _callee5, _this); + })); + + return function (_x7, _x8) { + return _ref5.apply(this, arguments); + }; + }()); + + this._fileManager = new _sparkProtocol.JSONFileManager(path); + } + + (0, _createClass3.default)(UserFileRepository, [{ + key: 'create', + value: (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(user) { + let id, + modelToSave; + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + id = (0, _uuid2.default)(); + + case 1: + _context6.next = 3; + return this._fileManager.hasFile(`${id}.json`); + + case 3: + if (!_context6.sent) { + _context6.next = 7; + break; + } + + id = (0, _uuid2.default)(); + _context6.next = 1; + break; + + case 7: + modelToSave = (0, _extends3.default)({}, user, { + created_at: new Date(), + created_by: null, + id, + }); + + + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + return _context6.abrupt('return', modelToSave); + + case 10: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function create(_x9) { + return _ref6.apply(this, arguments); + } + + return create; + }()), + }, { + key: 'update', + value: (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(model) { + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + this._fileManager.writeFile(`${model.id}.json`, model); + return _context7.abrupt('return', model); + + case 2: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this); + })); + + function update(_x10) { + return _ref7.apply(this, arguments); + } + + return update; + }()), + }, { + key: 'getAll', + value: (function () { + const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { + return _regenerator2.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + return _context8.abrupt('return', this._fileManager.getAllData()); + + case 1: + case 'end': + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function getAll() { + return _ref8.apply(this, arguments); + } + + return getAll; + }()), + }, { + key: 'getById', + value: (function () { + const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) { + return _regenerator2.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + return _context9.abrupt('return', this._fileManager.getFile(`${id}.json`)); + + case 1: + case 'end': + return _context9.stop(); + } + } + }, _callee9, this); + })); + + function getById(_x11) { + return _ref9.apply(this, arguments); + } + + return getById; + }()), + }, { + key: 'getByUsername', + value: (function () { + const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(username) { + return _regenerator2.default.wrap(function _callee10$(_context10) { + while (1) { + switch (_context10.prev = _context10.next) { + case 0: + _context10.next = 2; + return this.getAll(); + + case 2: + _context10.t0 = function (user) { + return user.username === username; + }; + + return _context10.abrupt('return', _context10.sent.find(_context10.t0)); + + case 4: + case 'end': + return _context10.stop(); + } + } + }, _callee10, this); + })); + + function getByUsername(_x12) { + return _ref10.apply(this, arguments); + } + + return getByUsername; + }()), + + // This isn't a good one to memoize as we can't key off user ID and there + // isn't a good way to clear the cache. + + }, { + key: 'deleteById', + value: (function () { + const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) { + return _regenerator2.default.wrap(function _callee11$(_context11) { + while (1) { + switch (_context11.prev = _context11.next) { + case 0: + this._fileManager.deleteFile(`${id}.json`); + + case 1: + case 'end': + return _context11.stop(); + } + } + }, _callee11, this); + })); + + function deleteById(_x13) { + return _ref11.apply(this, arguments); + } + + return deleteById; + }()), + }, { + key: 'isUserNameInUse', + value: (function () { + const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) { + return _regenerator2.default.wrap(function _callee12$(_context12) { + while (1) { + switch (_context12.prev = _context12.next) { + case 0: + _context12.next = 2; + return this.getAll(); + + case 2: + _context12.t0 = function (user) { + return user.username === username; + }; + + return _context12.abrupt('return', _context12.sent.some(_context12.t0)); + + case 4: + case 'end': + return _context12.stop(); + } + } + }, _callee12, this); + })); + + function isUserNameInUse(_x14) { + return _ref12.apply(this, arguments); + } + + return isUserNameInUse; + }()), + }]); + return UserFileRepository; +}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class)); +exports.default = UserFileRepository; diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js new file mode 100644 index 00000000..ed4263e6 --- /dev/null +++ b/dist/repository/WebhookFileRepository.js @@ -0,0 +1,310 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); + +const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); + +const _extends2 = require('babel-runtime/helpers/extends'); + +const _extends3 = _interopRequireDefault(_extends2); + +const _regenerator = require('babel-runtime/regenerator'); + +const _regenerator2 = _interopRequireDefault(_regenerator); + +const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +const _createClass2 = require('babel-runtime/helpers/createClass'); + +const _createClass3 = _interopRequireDefault(_createClass2); + +let _dec, + _dec2, + _dec3, + _dec4, + _desc, + _value, + _class; + +const _uuid = require('uuid'); + +const _uuid2 = _interopRequireDefault(_uuid); + +const _sparkProtocol = require('spark-protocol'); + +const _HttpError = require('../lib/HttpError'); + +const _HttpError2 = _interopRequireDefault(_HttpError); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + let desc = {}; + Object['ke' + 'ys'](descriptor).forEach((key) => { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), (_class = (function () { + function WebhookFileRepository(path) { + const _this = this; + + (0, _classCallCheck3.default)(this, WebhookFileRepository); + + this.getAll = (function () { + const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + const userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let allData; + return _regenerator2.default.wrap((_context) => { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _this._getAll(); + + case 2: + allData = _context.sent; + + if (!userID) { + _context.next = 5; + break; + } + + return _context.abrupt('return', allData.filter(webhook => webhook.ownerID === userID)); + + case 5: + return _context.abrupt('return', allData); + + case 6: + case 'end': + return _context.stop(); + } + } + }, _callee, _this); + })); + + return function () { + return _ref.apply(this, arguments); + }; + }()); + + this.getById = (function () { + const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) { + const userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + let webhook; + return _regenerator2.default.wrap((_context2) => { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _this._getByID(id); + + case 2: + webhook = _context2.sent; + + if (!(!webhook || webhook.ownerID !== userID)) { + _context2.next = 5; + break; + } + + return _context2.abrupt('return', null); + + case 5: + return _context2.abrupt('return', webhook); + + case 6: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this); + })); + + return function (_x2) { + return _ref2.apply(this, arguments); + }; + }()); + + this.update = (function () { + const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + return _regenerator2.default.wrap((_context3) => { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + throw new _HttpError2.default('Not implemented'); + + case 1: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this); + })); + + return function (_x4) { + return _ref3.apply(this, arguments); + }; + }()); + + this._fileManager = new _sparkProtocol.JSONFileManager(path); + } + + (0, _createClass3.default)(WebhookFileRepository, [{ + key: 'create', + value: (function () { + const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) { + let id, + modelToSave; + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + id = (0, _uuid2.default)(); + + case 1: + _context4.next = 3; + return this._fileManager.hasFile(`${id}.json`); + + case 3: + if (!_context4.sent) { + _context4.next = 7; + break; + } + + id = (0, _uuid2.default)(); + _context4.next = 1; + break; + + case 7: + modelToSave = (0, _extends3.default)({}, model, { + created_at: new Date(), + id, + }); + + + this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + return _context4.abrupt('return', modelToSave); + + case 10: + case 'end': + return _context4.stop(); + } + } + }, _callee4, this); + })); + + function create(_x5) { + return _ref4.apply(this, arguments); + } + + return create; + }()), + }, { + key: 'deleteById', + value: (function () { + const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) { + return _regenerator2.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + this._fileManager.deleteFile(`${id}.json`); + + case 1: + case 'end': + return _context5.stop(); + } + } + }, _callee5, this); + })); + + function deleteById(_x6) { + return _ref5.apply(this, arguments); + } + + return deleteById; + }()), + + // eslint-disable-next-line no-unused-vars + + }, { + key: '_getAll', + value: (function () { + const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() { + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + return _context6.abrupt('return', this._fileManager.getAllData()); + + case 1: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this); + })); + + function _getAll() { + return _ref6.apply(this, arguments); + } + + return _getAll; + }()), + }, { + key: '_getByID', + value: (function () { + const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) { + return _regenerator2.default.wrap(function _callee7$(_context7) { + while (1) { + switch (_context7.prev = _context7.next) { + case 0: + return _context7.abrupt('return', this._fileManager.getFile(`${id}.json`)); + + case 1: + case 'end': + return _context7.stop(); + } + } + }, _callee7, this); + })); + + function _getByID(_x7) { + return _ref7.apply(this, arguments); + } + + return _getByID; + }()), + }]); + return WebhookFileRepository; +}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class)); +exports.default = WebhookFileRepository; diff --git a/dist/settings.js b/dist/settings.js new file mode 100644 index 00000000..9b53767c --- /dev/null +++ b/dist/settings.js @@ -0,0 +1,51 @@ + + +Object.defineProperty(exports, '__esModule', { + value: true, +}); + +const _path = require('path'); + +const _path2 = _interopRequireDefault(_path); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* eslint-disable sorting/sort-object-props */ +exports.default = { + BUILD_DIRECTORY: _path2.default.join(__dirname, '../data/build'), + DEVICE_DIRECTORY: _path2.default.join(__dirname, '../data/deviceKeys'), + FIRMWARE_DIRECTORY: _path2.default.join(__dirname, '../data/knownApps'), + FIRMWARE_REPOSITORY_DIRECTORY: _path2.default.join(__dirname, '../../spark-firmware'), + SERVER_KEY_FILENAME: 'default_key.pem', + SERVER_KEYS_DIRECTORY: _path2.default.join(__dirname, '../data'), + USERS_DIRECTORY: _path2.default.join(__dirname, '../data/users'), + WEBHOOKS_DIRECTORY: _path2.default.join(__dirname, '../data/webhooks'), + + ACCESS_TOKEN_LIFETIME: 7776000, // 90 days, + API_TIMEOUT: 30000, // Timeout for API requests. + CRYPTO_SALT: 'aes-128-cbc', + LOG_REQUESTS: true, + LOGIN_ROUTE: '/oauth/token', + + PORT: 5683, + HOST: 'localhost', +}; /** + * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * You can download the source here: https://github.com/spark/spark-server + * + * + * + */ diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dist/types.js @@ -0,0 +1 @@ + diff --git a/package.json b/package.json index b6c52ac7..4621da23 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "url": "https://github.com/emilyrose" } ], - "main": "./src/exports.js", + "main": "./dist/exports.js", "scripts": { - "build": "babel ./src --out-dir ./build", + "build": "babel ./src --out-dir ./dist", "build:watch": "babel ./src --out-dir ./dist --watch", "build:clean": "rimraf ./build", "lint": "eslint --fix --max-warnings 0 -- .", From e657e212fe229c930ecb5d7218d4a40a98eb68b1 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 14:43:36 -0800 Subject: [PATCH 342/504] Fixing build... some comments broke node. --- dist/OAuthModel.js | 61 ++-- dist/RouteConfig.js | 145 +++++----- dist/app.js | 32 +-- dist/controllers/Controller.js | 22 +- dist/controllers/DeviceClaimsController.js | 86 +++--- dist/controllers/DevicesController.js | 202 ++++++-------- dist/controllers/EventsController.js | 157 +++++------ dist/controllers/OauthClientsController.js | 102 ++++--- dist/controllers/ProductsController.js | 68 ++--- dist/controllers/ProvisioningController.js | 88 +++--- dist/controllers/UsersController.js | 123 ++++----- dist/controllers/WebhooksController.js | 121 ++++---- dist/controllers/types.js | 2 +- dist/decorators/allowUpload.js | 18 +- dist/decorators/anonymous.js | 8 +- dist/decorators/httpVerb.js | 8 +- dist/decorators/route.js | 8 +- dist/decorators/serverSentEvents.js | 8 +- dist/decorators/types.js | 2 +- dist/defaultBindings.js | 70 ++--- dist/exports.js | 24 +- dist/lib/HttpError.js | 32 +-- dist/lib/PasswordHasher.js | 50 ++-- dist/lib/deviceToAPI.js | 12 +- dist/lib/eventToApi.js | 12 +- dist/lib/logger.js | 34 +-- dist/main.js | 72 ++--- dist/managers/DeviceManager.js | 208 +++++++------- dist/managers/EventManager.js | 16 +- dist/managers/FirmwareCompilationManager.js | 159 ++++++----- dist/managers/WebhookManager.js | 260 +++++++++--------- .../DeviceFirmwareFileRepository.js | 43 ++- dist/repository/UserFileRepository.js | 205 +++++++------- dist/repository/WebhookFileRepository.js | 137 +++++---- dist/settings.js | 16 +- dist/types.js | 2 +- src/RouteConfig.js | 3 +- 37 files changed, 1246 insertions(+), 1370 deletions(-) diff --git a/dist/OAuthModel.js b/dist/OAuthModel.js index 592e0e98..6c7ca2dd 100644 --- a/dist/OAuthModel.js +++ b/dist/OAuthModel.js @@ -1,39 +1,38 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const OAUTH_CLIENTS = [{ +var OAUTH_CLIENTS = [{ clientId: 'CLI2', clientSecret: 'client_secret_here', - grants: ['password'], + grants: ['password'] }]; -const OauthModel = function OauthModel(userRepository) { - const _this = this; +var OauthModel = function OauthModel(userRepository) { + var _this = this; (0, _classCallCheck3.default)(this, OauthModel); - this.getAccessToken = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(bearerToken) { - let user, - userTokenObject; - return _regenerator2.default.wrap((_context) => { + this.getAccessToken = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(bearerToken) { + var user, userTokenObject; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -51,7 +50,9 @@ const OauthModel = function OauthModel(userRepository) { return _context.abrupt('return', null); case 5: - userTokenObject = user.accessTokens.find(tokenObject => tokenObject.accessToken === bearerToken); + userTokenObject = user.accessTokens.find(function (tokenObject) { + return tokenObject.accessToken === bearerToken; + }); if (userTokenObject) { _context.next = 8; @@ -63,7 +64,7 @@ const OauthModel = function OauthModel(userRepository) { case 8: return _context.abrupt('return', { accessToken: userTokenObject.accessToken, - user, + user: user }); case 9: @@ -77,15 +78,17 @@ const OauthModel = function OauthModel(userRepository) { return function (_x) { return _ref.apply(this, arguments); }; - }()); + }(); this.getClient = function (clientId, clientSecret) { - return OAUTH_CLIENTS.find(client => client.clientId === clientId && client.clientSecret === clientSecret); + return OAUTH_CLIENTS.find(function (client) { + return client.clientId === clientId && client.clientSecret === clientSecret; + }); }; - this.getUser = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { - return _regenerator2.default.wrap((_context2) => { + this.getUser = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -106,14 +109,14 @@ const OauthModel = function OauthModel(userRepository) { return function (_x2, _x3) { return _ref2.apply(this, arguments); }; - }()); + }(); this.saveToken = function (tokenObject, client, user) { _this._userRepository.saveAccessToken(user.id, tokenObject); return { accessToken: tokenObject.accessToken, - client, - user, + client: client, + user: user }; }; @@ -127,4 +130,4 @@ const OauthModel = function OauthModel(userRepository) { // eslint-disable-next-line no-unused-vars ; -exports.default = OauthModel; +exports.default = OauthModel; \ No newline at end of file diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js index 2efc9fc1..166e71eb 100644 --- a/dist/RouteConfig.js +++ b/dist/RouteConfig.js @@ -1,64 +1,64 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); +var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); -const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); +var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); -const _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); +var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); -const _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); +var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); -const _create = require('babel-runtime/core-js/object/create'); +var _create = require('babel-runtime/core-js/object/create'); -const _create2 = _interopRequireDefault(_create); +var _create2 = _interopRequireDefault(_create); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names'); +var _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names'); -const _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames); +var _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames); -const _expressOauthServer = require('express-oauth-server'); +var _expressOauthServer = require('express-oauth-server'); -const _expressOauthServer2 = _interopRequireDefault(_expressOauthServer); +var _expressOauthServer2 = _interopRequireDefault(_expressOauthServer); -const _nullthrows = require('nullthrows'); +var _nullthrows = require('nullthrows'); -const _nullthrows2 = _interopRequireDefault(_nullthrows); +var _nullthrows2 = _interopRequireDefault(_nullthrows); -const _multer = require('multer'); +var _multer = require('multer'); -const _multer2 = _interopRequireDefault(_multer); +var _multer2 = _interopRequireDefault(_multer); -const _OAuthModel = require('./OAuthModel'); +var _OAuthModel = require('./OAuthModel'); -const _OAuthModel2 = _interopRequireDefault(_OAuthModel); +var _OAuthModel2 = _interopRequireDefault(_OAuthModel); -const _HttpError = require('./lib/HttpError'); +var _HttpError = require('./lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const maybe = function maybe(middleware, condition) { +var maybe = function maybe(middleware, condition) { return function (request, response, next) { if (condition) { middleware(request, response, next); @@ -68,11 +68,11 @@ const maybe = function maybe(middleware, condition) { }; }; -const injectUserMiddleware = function injectUserMiddleware() { +var injectUserMiddleware = function injectUserMiddleware() { return function (request, response, next) { - const oauthInfo = response.locals.oauth; + var oauthInfo = response.locals.oauth; if (oauthInfo) { - const token = oauthInfo.token; + var token = oauthInfo.token; // eslint-disable-next-line no-param-reassign request.user = token && token.user; } @@ -84,13 +84,13 @@ const injectUserMiddleware = function injectUserMiddleware() { // prevents of closing server-sent-events stream if there aren't events for // a long time, but according to the docs sse keep connection alive automatically. // if there will be related issues in the future, we can return _keepAlive() back. -const serverSentEventsMiddleware = function serverSentEventsMiddleware() { +var serverSentEventsMiddleware = function serverSentEventsMiddleware() { return function (request, response, next) { request.socket.setNoDelay(); response.writeHead(200, { 'Cache-Control': 'no-cache', Connection: 'keep-alive', - 'Content-Type': 'text/event-stream', + 'Content-Type': 'text/event-stream' }); next(); @@ -98,51 +98,47 @@ const serverSentEventsMiddleware = function serverSentEventsMiddleware() { }; exports.default = function (app, container, controllers, settings) { - const oauth = new _expressOauthServer2.default({ + var oauth = new _expressOauthServer2.default({ ACCESS_TOKEN_LIFETIME: settings.ACCESS_TOKEN_LIFETIME, allowBearerTokensInQueryString: true, - model: new _OAuthModel2.default(container.constitute('UserRepository')), + model: new _OAuthModel2.default(container.constitute('UserRepository')) }); - const filesMiddleware = function filesMiddleware() { - const allowedUploads = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var filesMiddleware = function filesMiddleware() { + var allowedUploads = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; return (0, _nullthrows2.default)(allowedUploads).length ? (0, _multer2.default)().fields(allowedUploads) : (0, _multer2.default)().any(); }; app.post(settings.LOGIN_ROUTE, oauth.token()); - controllers.forEach((controllerName) => { - const controller = container.constitute(controllerName); - (0, _getOwnPropertyNames2.default)((0, _getPrototypeOf2.default)(controller)).forEach((functionName) => { - const mappedFunction = controller[functionName]; - let allowedUploads = mappedFunction.allowedUploads, - anonymous = mappedFunction.anonymous, - httpVerb = mappedFunction.httpVerb, - route = mappedFunction.route, - serverSentEvents = mappedFunction.serverSentEvents; + controllers.forEach(function (controllerName) { + var controller = container.constitute(controllerName); + (0, _getOwnPropertyNames2.default)((0, _getPrototypeOf2.default)(controller)).forEach(function (functionName) { + var mappedFunction = controller[functionName]; + var allowedUploads = mappedFunction.allowedUploads, + anonymous = mappedFunction.anonymous, + httpVerb = mappedFunction.httpVerb, + route = mappedFunction.route, + serverSentEvents = mappedFunction.serverSentEvents; if (!httpVerb) { return; } - app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), maybe(filesMiddleware(allowedUploads), allowedUploads), (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) { - let argumentNames, - values, - controllerInstance, - _request$body, - access_token, - body, - functionResult, - result, - httpError; - - return _regenerator2.default.wrap((_context) => { + app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), maybe(filesMiddleware(allowedUploads), allowedUploads), function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) { + var argumentNames, values, controllerInstance, _request$body, access_token, body, functionResult, result, httpError; + + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: - argumentNames = (route.match(/:[\w]*/g) || []).map(argumentName => argumentName.replace(':', '')); - values = argumentNames.map(argument => request.params[argument]); + argumentNames = (route.match(/:[\w]*/g) || []).map(function (argumentName) { + return argumentName.replace(':', ''); + }); + values = argumentNames.map(function (argument) { + return request.params[argument]; + }); controllerInstance = container.constitute(controllerName); // In order parallel requests on the controller, the state @@ -162,7 +158,7 @@ exports.default = function (app, container, controllers, settings) { // Take access token out if it's posted. _request$body = request.body, access_token = _request$body.access_token, body = (0, _objectWithoutProperties3.default)(_request$body, ['access_token']); _context.prev = 8; - functionResult = mappedFunction.call(...[controllerInstance].concat((0, _toConsumableArray3.default)(values), [body])); + functionResult = mappedFunction.call.apply(mappedFunction, [controllerInstance].concat((0, _toConsumableArray3.default)(values), [body])); if (!functionResult.then) { _context.next = 17; @@ -170,7 +166,11 @@ exports.default = function (app, container, controllers, settings) { } _context.next = 13; - return _promise2.default.race([functionResult, !serverSentEvents ? new _promise2.default((resolve, reject) => setTimeout(() => reject(new Error('timeout')), settings.API_TIMEOUT)) : null]); + return _promise2.default.race([functionResult, !serverSentEvents ? new _promise2.default(function (resolve, reject) { + return setTimeout(function () { + return reject(new Error('timeout')); + }, settings.API_TIMEOUT); + }) : null]); case 13: result = _context.sent; @@ -188,12 +188,12 @@ exports.default = function (app, container, controllers, settings) { case 20: _context.prev = 20; - _context.t0 = _context.catch(8); + _context.t0 = _context['catch'](8); httpError = new _HttpError2.default(_context.t0); response.status(httpError.status).json({ error: httpError.message, - ok: false, + ok: false }); case 24: @@ -207,19 +207,18 @@ exports.default = function (app, container, controllers, settings) { return function (_x2, _x3) { return _ref.apply(this, arguments); }; - }())); + }()); }); }); - app.all('*', (request, response) => { + app.all('*', function (request, response) { response.sendStatus(404); }); - app.use((error, request, response, next, // eslint-disable-line no-unused-vars - ) => { + app.use(function (error, request, response, next) { response.status(400).json({ error: error.code ? error.code : error, - ok: false, + ok: false }); }); -}; +}; \ No newline at end of file diff --git a/dist/app.js b/dist/app.js index 6c1dfc67..3364ac19 100644 --- a/dist/app.js +++ b/dist/app.js @@ -1,37 +1,37 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _bodyParser = require('body-parser'); +var _bodyParser = require('body-parser'); -const _bodyParser2 = _interopRequireDefault(_bodyParser); +var _bodyParser2 = _interopRequireDefault(_bodyParser); -const _express = require('express'); +var _express = require('express'); -const _express2 = _interopRequireDefault(_express); +var _express2 = _interopRequireDefault(_express); -const _morgan = require('morgan'); +var _morgan = require('morgan'); -const _morgan2 = _interopRequireDefault(_morgan); +var _morgan2 = _interopRequireDefault(_morgan); -const _RouteConfig = require('./RouteConfig'); +var _RouteConfig = require('./RouteConfig'); -const _RouteConfig2 = _interopRequireDefault(_RouteConfig); +var _RouteConfig2 = _interopRequireDefault(_RouteConfig); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = function (container, settings) { - const app = (0, _express2.default)(); + var app = (0, _express2.default)(); - const setCORSHeaders = function setCORSHeaders(request, response, next) { + var setCORSHeaders = function setCORSHeaders(request, response, next) { if (request.method === 'OPTIONS') { response.set({ 'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Accept, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Origin': '*', - 'Access-Control-Max-Age': '300', + 'Access-Control-Max-Age': '300' }); return response.sendStatus(204); } @@ -50,7 +50,7 @@ exports.default = function (container, settings) { (0, _RouteConfig2.default)(app, container, ['DeviceClaimsController', // to avoid routes collisions EventsController should be placed // before DevicesController - 'EventsController', 'DevicesController', 'OauthClientsController', 'ProductsController', 'ProvisioningController', 'UsersController', 'WebhooksController'], settings); + 'EventsController', 'DevicesController', 'OauthClientsController', 'ProductsController', 'ProvisioningController', 'UsersController', 'WebhooksController'], settings); return app; -}; +}; \ No newline at end of file diff --git a/dist/controllers/Controller.js b/dist/controllers/Controller.js index ecb1cbec..b11205a2 100644 --- a/dist/controllers/Controller.js +++ b/dist/controllers/Controller.js @@ -1,36 +1,36 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); exports.default = undefined; -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const Controller = function Controller() { +var Controller = function Controller() { (0, _classCallCheck3.default)(this, Controller); this.bad = function (message) { - const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + var status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; return { data: { error: message, - ok: false, + ok: false }, - status, + status: status }; }; this.ok = function (output) { return { data: output, - status: 200, + status: 200 }; }; }; -exports.default = Controller; +exports.default = Controller; \ No newline at end of file diff --git a/dist/controllers/DeviceClaimsController.js b/dist/controllers/DeviceClaimsController.js index 6e465768..1789c62b 100644 --- a/dist/controllers/DeviceClaimsController.js +++ b/dist/controllers/DeviceClaimsController.js @@ -1,64 +1,60 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _desc, - _value, - _class; +var _dec, _dec2, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -68,7 +64,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -83,13 +81,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/device_claims'), (_class = (function (_Controller) { +var DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/device_claims'), (_class = function (_Controller) { (0, _inherits3.default)(DeviceClaimsController, _Controller); function DeviceClaimsController(deviceManager, claimCodeManager) { (0, _classCallCheck3.default)(this, DeviceClaimsController); - const _this = (0, _possibleConstructorReturn3.default)(this, (DeviceClaimsController.__proto__ || (0, _getPrototypeOf2.default)(DeviceClaimsController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (DeviceClaimsController.__proto__ || (0, _getPrototypeOf2.default)(DeviceClaimsController)).call(this)); _this._deviceManager = deviceManager; _this._claimCodeManager = claimCodeManager; @@ -98,11 +96,9 @@ const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _createClass3.default)(DeviceClaimsController, [{ key: 'createClaimCode', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { - let claimCode, - devices, - deviceIDs; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + var claimCode, devices, deviceIDs; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -113,7 +109,9 @@ const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = case 3: devices = _context.sent; - deviceIDs = devices.map(device => device.deviceID); + deviceIDs = devices.map(function (device) { + return device.deviceID; + }); return _context.abrupt('return', this.ok({ claim_code: claimCode, device_ids: deviceIDs })); case 6: @@ -129,8 +127,8 @@ const DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return createClaimCode; - }()), + }() }]); return DeviceClaimsController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClaimCode', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClaimCode'), _class.prototype)), _class)); -exports.default = DeviceClaimsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createClaimCode', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClaimCode'), _class.prototype)), _class)); +exports.default = DeviceClaimsController; \ No newline at end of file diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js index a6cc6beb..43223ede 100644 --- a/dist/controllers/DevicesController.js +++ b/dist/controllers/DevicesController.js @@ -1,106 +1,84 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _dec10, - _dec11, - _dec12, - _dec13, - _dec14, - _dec15, - _dec16, - _dec17, - _dec18, - _dec19, - _dec20, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _desc, _value, _class; -const _nullthrows = require('nullthrows'); +var _nullthrows = require('nullthrows'); -const _nullthrows2 = _interopRequireDefault(_nullthrows); +var _nullthrows2 = _interopRequireDefault(_nullthrows); -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _FirmwareCompilationManager = require('../managers/FirmwareCompilationManager'); +var _FirmwareCompilationManager = require('../managers/FirmwareCompilationManager'); -const _FirmwareCompilationManager2 = _interopRequireDefault(_FirmwareCompilationManager); +var _FirmwareCompilationManager2 = _interopRequireDefault(_FirmwareCompilationManager); -const _allowUpload = require('../decorators/allowUpload'); +var _allowUpload = require('../decorators/allowUpload'); -const _allowUpload2 = _interopRequireDefault(_allowUpload); +var _allowUpload2 = _interopRequireDefault(_allowUpload); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); -const _deviceToAPI = require('../lib/deviceToAPI'); +var _deviceToAPI = require('../lib/deviceToAPI'); -const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); +var _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -110,7 +88,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -125,13 +105,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = (function (_Controller) { +var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = function (_Controller) { (0, _inherits3.default)(DevicesController, _Controller); function DevicesController(deviceManager) { (0, _classCallCheck3.default)(this, DevicesController); - const _this = (0, _possibleConstructorReturn3.default)(this, (DevicesController.__proto__ || (0, _getPrototypeOf2.default)(DevicesController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (DevicesController.__proto__ || (0, _getPrototypeOf2.default)(DevicesController)).call(this)); _this._deviceManager = deviceManager; return _this; @@ -139,9 +119,9 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ (0, _createClass3.default)(DevicesController, [{ key: 'claimDevice', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(postBody) { - let deviceID; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(postBody) { + var deviceID; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -166,11 +146,11 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return claimDevice; - }()), + }() }, { key: 'getAppFirmware', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(binaryID) { + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(binaryID) { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { @@ -190,12 +170,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getAppFirmware; - }()), + }() }, { key: 'compileSources', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(postBody) { - let response; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(postBody) { + var response; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { @@ -215,8 +195,8 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 5: return _context3.abrupt('return', this.ok((0, _extends3.default)({}, response, { - binary_url: `/v1/binaries/${response.binary_id}`, - ok: true, + binary_url: '/v1/binaries/' + response.binary_id, + ok: true }))); case 6: @@ -232,11 +212,11 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return compileSources; - }()), + }() }, { key: 'unclaimDevice', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) { + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -260,12 +240,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return unclaimDevice; - }()), + }() }, { key: 'getDevices', - value: (function () { - const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { - let devices; + value: function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { + var devices; return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { @@ -276,11 +256,13 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 3: devices = _context5.sent; - return _context5.abrupt('return', this.ok(devices.map(device => (0, _deviceToAPI2.default)(device)))); + return _context5.abrupt('return', this.ok(devices.map(function (device) { + return (0, _deviceToAPI2.default)(device); + }))); case 7: _context5.prev = 7; - _context5.t0 = _context5.catch(0); + _context5.t0 = _context5['catch'](0); return _context5.abrupt('return', this.ok([])); case 10: @@ -296,12 +278,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getDevices; - }()), + }() }, { key: 'getDevice', - value: (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) { - let device; + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) { + var device; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { @@ -326,13 +308,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getDevice; - }()), + }() }, { key: 'getVariableValue', - value: (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, varName) { - let varValue, - errorMessage; + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, varName) { + var varValue, errorMessage; return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { @@ -347,7 +328,7 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 7: _context7.prev = 7; - _context7.t0 = _context7.catch(0); + _context7.t0 = _context7['catch'](0); errorMessage = _context7.t0.message; if (!errorMessage.match('Variable not found')) { @@ -373,15 +354,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return getVariableValue; - }()), + }() }, { key: 'updateDevice', - value: (function () { - const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) { - let updatedAttributes, - flashStatus, - file, - _flashStatus; + value: function () { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) { + var updatedAttributes, flashStatus, file, _flashStatus; return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { @@ -471,14 +449,12 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return updateDevice; - }()), + }() }, { key: 'callDeviceFunction', - value: (function () { - const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) { - let result, - device, - errorMessage; + value: function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) { + var result, device, errorMessage; return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { @@ -498,7 +474,7 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ case 10: _context9.prev = 10; - _context9.t0 = _context9.catch(0); + _context9.t0 = _context9['catch'](0); errorMessage = _context9.t0.message; if (!(errorMessage.indexOf('Unknown Function') >= 0)) { @@ -524,8 +500,8 @@ const DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ } return callDeviceFunction; - }()), + }() }]); return DevicesController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype)), _class)); -exports.default = DevicesController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype)), _class)); +exports.default = DevicesController; \ No newline at end of file diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js index 5208feb2..4860098e 100644 --- a/dist/controllers/EventsController.js +++ b/dist/controllers/EventsController.js @@ -1,93 +1,80 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _stringify = require('babel-runtime/core-js/json/stringify'); +var _stringify = require('babel-runtime/core-js/json/stringify'); -const _stringify2 = _interopRequireDefault(_stringify); +var _stringify2 = _interopRequireDefault(_stringify); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _dec10, - _dec11, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _serverSentEvents = require('../decorators/serverSentEvents'); +var _serverSentEvents = require('../decorators/serverSentEvents'); -const _serverSentEvents2 = _interopRequireDefault(_serverSentEvents); +var _serverSentEvents2 = _interopRequireDefault(_serverSentEvents); -const _logger = require('../lib/logger'); +var _logger = require('../lib/logger'); -const _logger2 = _interopRequireDefault(_logger); +var _logger2 = _interopRequireDefault(_logger); -const _eventToApi = require('../lib/eventToApi'); +var _eventToApi = require('../lib/eventToApi'); -const _eventToApi2 = _interopRequireDefault(_eventToApi); +var _eventToApi2 = _interopRequireDefault(_eventToApi); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -97,7 +84,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -112,13 +101,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec3 = (0, _serverSentEvents2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('post'), _dec11 = (0, _route2.default)('/v1/devices/events'), (_class = (function (_Controller) { +var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec3 = (0, _serverSentEvents2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('post'), _dec11 = (0, _route2.default)('/v1/devices/events'), (_class = function (_Controller) { (0, _inherits3.default)(EventsController, _Controller); function EventsController(eventManager) { (0, _classCallCheck3.default)(this, EventsController); - const _this = (0, _possibleConstructorReturn3.default)(this, (EventsController.__proto__ || (0, _getPrototypeOf2.default)(EventsController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (EventsController.__proto__ || (0, _getPrototypeOf2.default)(EventsController)).call(this)); _this._eventManager = eventManager; return _this; @@ -127,10 +116,10 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro (0, _createClass3.default)(EventsController, [{ key: '_closeStream', value: function _closeStream(subscriptionID) { - const _this2 = this; + var _this2 = this; - return new _promise2.default((resolve) => { - const closeStreamHandler = function closeStreamHandler() { + return new _promise2.default(function (resolve) { + var closeStreamHandler = function closeStreamHandler() { _this2._eventManager.unsubscribe(subscriptionID); resolve(); }; @@ -140,23 +129,23 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro _this2.response.on('finish', closeStreamHandler); _this2.response.on('end', closeStreamHandler); }); - }, + } }, { key: '_pipeEvent', value: function _pipeEvent(event) { try { - this.response.write(`event: ${event.name}\n`); - this.response.write(`data: ${(0, _stringify2.default)((0, _eventToApi2.default)(event))}\n\n`); + this.response.write('event: ' + event.name + '\n'); + this.response.write('data: ' + (0, _stringify2.default)((0, _eventToApi2.default)(event)) + '\n\n'); } catch (error) { - _logger2.default.error(`pipeEvents - write error: ${error}`); + _logger2.default.error('pipeEvents - write error: ' + error); throw error; } - }, + } }, { key: 'getEvents', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(eventNamePrefix) { - let subscriptionID; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(eventNamePrefix) { + var subscriptionID; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -181,19 +170,19 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return getEvents; - }()), + }() }, { key: 'getMyEvents', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) { - let subscriptionID; + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) { + var subscriptionID; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { mydevices: true, - userID: this.user.id, + userID: this.user.id }); _context2.next = 3; return this._closeStream(subscriptionID); @@ -214,19 +203,19 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return getMyEvents; - }()), + }() }, { key: 'getDeviceEvents', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, eventNamePrefix) { - let subscriptionID; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, eventNamePrefix) { + var subscriptionID; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { - deviceID, - userID: this.user.id, + deviceID: deviceID, + userID: this.user.id }); _context3.next = 3; return this._closeStream(subscriptionID); @@ -247,12 +236,12 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return getDeviceEvents; - }()), + }() }, { key: 'publish', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(postBody) { - let eventData; + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(postBody) { + var eventData; return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -262,7 +251,7 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro isPublic: !postBody.private, name: postBody.name, ttl: postBody.ttl, - userID: this.user.id, + userID: this.user.id }; @@ -282,8 +271,8 @@ const EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro } return publish; - }()), + }() }]); return EventsController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class)); -exports.default = EventsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class)); +exports.default = EventsController; \ No newline at end of file diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js index e9ff1c86..69a818aa 100644 --- a/dist/controllers/OauthClientsController.js +++ b/dist/controllers/OauthClientsController.js @@ -1,72 +1,64 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -76,7 +68,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -91,7 +85,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = (function (_Controller) { +var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = function (_Controller) { (0, _inherits3.default)(OauthClientsController, _Controller); function OauthClientsController() { @@ -103,9 +97,9 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = key: 'createClient', // eslint-disable-next-line class-methods-use-this - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { - return _regenerator2.default.wrap((_context) => { + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -124,14 +118,14 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return createClient; - }()), + }() }, { key: 'editClient', // eslint-disable-next-line class-methods-use-this - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { - return _regenerator2.default.wrap((_context2) => { + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() { + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -150,14 +144,14 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return editClient; - }()), + }() }, { key: 'deleteClient', // eslint-disable-next-line class-methods-use-this - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { - return _regenerator2.default.wrap((_context3) => { + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -176,8 +170,8 @@ const OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return deleteClient; - }()), + }() }]); return OauthClientsController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class)); -exports.default = OauthClientsController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class)); +exports.default = OauthClientsController; \ No newline at end of file diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js index 0ec9cf30..e9382aa3 100644 --- a/dist/controllers/ProductsController.js +++ b/dist/controllers/ProductsController.js @@ -1,68 +1,42 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _dec10, - _dec11, - _dec12, - _dec13, - _dec14, - _dec15, - _dec16, - _dec17, - _dec18, - _dec19, - _dec20, - _dec21, - _dec22, - _dec23, - _dec24, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _desc, _value, _class; /* eslint-disable */ var _Controller2 = require('./Controller'); @@ -429,4 +403,4 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro return ProductsController; }(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'generateClaimCode', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'generateClaimCode'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec23, _dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class)); exports.default = ProductsController; -/* eslint-enable */ +/* eslint-enable */ \ No newline at end of file diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js index a65f7fdd..88ffdb80 100644 --- a/dist/controllers/ProvisioningController.js +++ b/dist/controllers/ProvisioningController.js @@ -1,72 +1,68 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _desc, - _value, - _class; +var _dec, _dec2, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); -const _deviceToAPI = require('../lib/deviceToAPI'); +var _deviceToAPI = require('../lib/deviceToAPI'); -const _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); +var _deviceToAPI2 = _interopRequireDefault(_deviceToAPI); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -76,7 +72,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -91,13 +89,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/provisioning/:coreID'), (_class = (function (_Controller) { +var ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/provisioning/:coreID'), (_class = function (_Controller) { (0, _inherits3.default)(ProvisioningController, _Controller); function ProvisioningController(deviceManager) { (0, _classCallCheck3.default)(this, ProvisioningController); - const _this = (0, _possibleConstructorReturn3.default)(this, (ProvisioningController.__proto__ || (0, _getPrototypeOf2.default)(ProvisioningController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (ProvisioningController.__proto__ || (0, _getPrototypeOf2.default)(ProvisioningController)).call(this)); _this._deviceManager = deviceManager; return _this; @@ -105,9 +103,9 @@ const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _createClass3.default)(ProvisioningController, [{ key: 'provision', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(coreID, postBody) { - let device; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(coreID, postBody) { + var device; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -140,8 +138,8 @@ const ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = } return provision; - }()), + }() }]); return ProvisioningController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'provision', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'provision'), _class.prototype)), _class)); -exports.default = ProvisioningController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'provision', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'provision'), _class.prototype)), _class)); +exports.default = ProvisioningController; \ No newline at end of file diff --git a/dist/controllers/UsersController.js b/dist/controllers/UsersController.js index 86db684a..6b9179d4 100644 --- a/dist/controllers/UsersController.js +++ b/dist/controllers/UsersController.js @@ -1,83 +1,72 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _dec9, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _desc, _value, _class; -const _basicAuthParser3 = require('basic-auth-parser'); +var _basicAuthParser3 = require('basic-auth-parser'); -const _basicAuthParser4 = _interopRequireDefault(_basicAuthParser3); +var _basicAuthParser4 = _interopRequireDefault(_basicAuthParser3); -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _anonymous = require('../decorators/anonymous'); +var _anonymous = require('../decorators/anonymous'); -const _anonymous2 = _interopRequireDefault(_anonymous); +var _anonymous2 = _interopRequireDefault(_anonymous); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -87,7 +76,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -102,13 +93,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/users'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('delete'), _dec5 = (0, _route2.default)('/v1/access_tokens/:token'), _dec6 = (0, _anonymous2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/access_tokens'), _dec9 = (0, _anonymous2.default)(), (_class = (function (_Controller) { +var UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/users'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('delete'), _dec5 = (0, _route2.default)('/v1/access_tokens/:token'), _dec6 = (0, _anonymous2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/access_tokens'), _dec9 = (0, _anonymous2.default)(), (_class = function (_Controller) { (0, _inherits3.default)(UsersController, _Controller); function UsersController(userRepository) { (0, _classCallCheck3.default)(this, UsersController); - const _this = (0, _possibleConstructorReturn3.default)(this, (UsersController.__proto__ || (0, _getPrototypeOf2.default)(UsersController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (UsersController.__proto__ || (0, _getPrototypeOf2.default)(UsersController)).call(this)); _this._userRepository = userRepository; return _this; @@ -116,9 +107,9 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro (0, _createClass3.default)(UsersController, [{ key: 'createUser', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { - let isUserNameInUse; + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + var isUserNameInUse; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -146,7 +137,7 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro case 11: _context.prev = 11; - _context.t0 = _context.catch(0); + _context.t0 = _context['catch'](0); return _context.abrupt('return', this.bad(_context.t0.message)); case 14: @@ -162,15 +153,12 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro } return createUser; - }()), + }() }, { key: 'deleteAccessToken', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(token) { - let _basicAuthParser, - username, - password, - user; + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(token) { + var _basicAuthParser, username, password, user; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { @@ -201,15 +189,12 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro } return deleteAccessToken; - }()), + }() }, { key: 'getAccessTokens', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { - let _basicAuthParser2, - username, - password, - user; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() { + var _basicAuthParser2, username, password, user; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { @@ -236,8 +221,8 @@ const UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro } return getAccessTokens; - }()), + }() }]); return UsersController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'createUser', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createUser'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteAccessToken', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteAccessToken'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAccessTokens', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAccessTokens'), _class.prototype)), _class)); -exports.default = UsersController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createUser', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createUser'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteAccessToken', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteAccessToken'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAccessTokens', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAccessTokens'), _class.prototype)), _class)); +exports.default = UsersController; \ No newline at end of file diff --git a/dist/controllers/WebhooksController.js b/dist/controllers/WebhooksController.js index 9783ca59..d9fe1768 100644 --- a/dist/controllers/WebhooksController.js +++ b/dist/controllers/WebhooksController.js @@ -1,78 +1,68 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _dec8, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _desc, _value, _class; -const _Controller2 = require('./Controller'); +var _Controller2 = require('./Controller'); -const _Controller3 = _interopRequireDefault(_Controller2); +var _Controller3 = _interopRequireDefault(_Controller2); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _httpVerb = require('../decorators/httpVerb'); +var _httpVerb = require('../decorators/httpVerb'); -const _httpVerb2 = _interopRequireDefault(_httpVerb); +var _httpVerb2 = _interopRequireDefault(_httpVerb); -const _route = require('../decorators/route'); +var _route = require('../decorators/route'); -const _route2 = _interopRequireDefault(_route); +var _route2 = _interopRequireDefault(_route); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -82,7 +72,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -97,9 +89,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; +var REQUEST_TYPES = ['DELETE', 'GET', 'POST', 'PUT']; -const validateWebhookMutator = function validateWebhookMutator(webhookMutator) { +var validateWebhookMutator = function validateWebhookMutator(webhookMutator) { if (!webhookMutator.event) { return new _HttpError2.default('no event name provided'); } @@ -116,13 +108,13 @@ const validateWebhookMutator = function validateWebhookMutator(webhookMutator) { return null; }; -const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookId'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookId'), (_class = (function (_Controller) { +var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookId'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookId'), (_class = function (_Controller) { (0, _inherits3.default)(WebhooksController, _Controller); function WebhooksController(webhookManager) { (0, _classCallCheck3.default)(this, WebhooksController); - const _this = (0, _possibleConstructorReturn3.default)(this, (WebhooksController.__proto__ || (0, _getPrototypeOf2.default)(WebhooksController)).call(this)); + var _this = (0, _possibleConstructorReturn3.default)(this, (WebhooksController.__proto__ || (0, _getPrototypeOf2.default)(WebhooksController)).call(this)); _this._webhookManager = webhookManager; return _this; @@ -130,8 +122,8 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ (0, _createClass3.default)(WebhooksController, [{ key: 'getAll', - value: (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { @@ -157,11 +149,11 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return getAll; - }()), + }() }, { key: 'getById', - value: (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookId) { + value: function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookId) { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { @@ -187,13 +179,12 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return getById; - }()), + }() }, { key: 'create', - value: (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { - let validateError, - newWebhook; + value: function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + var validateError, newWebhook; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { @@ -210,7 +201,7 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ case 3: _context3.next = 5; return this._webhookManager.create((0, _extends3.default)({}, model, { - ownerID: this.user.id, + ownerID: this.user.id })); case 5: @@ -220,7 +211,7 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ event: newWebhook.event, id: newWebhook.id, ok: true, - url: newWebhook.url, + url: newWebhook.url })); case 7: @@ -236,11 +227,11 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return create; - }()), + }() }, { key: 'deleteById', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookId) { + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookId) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -264,8 +255,8 @@ const WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ } return deleteById; - }()), + }() }]); return WebhooksController; -}(_Controller3.default)), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype)), _class)); -exports.default = WebhooksController; +}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype)), _class)); +exports.default = WebhooksController; \ No newline at end of file diff --git a/dist/controllers/types.js b/dist/controllers/types.js index 8b137891..9a390c31 100644 --- a/dist/controllers/types.js +++ b/dist/controllers/types.js @@ -1 +1 @@ - +"use strict"; \ No newline at end of file diff --git a/dist/decorators/allowUpload.js b/dist/decorators/allowUpload.js index 53e1d236..700de3f9 100644 --- a/dist/decorators/allowUpload.js +++ b/dist/decorators/allowUpload.js @@ -1,23 +1,23 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ exports.default = function () { - const fileName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; - const maxCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + var fileName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; + var maxCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return function (target, name, descriptor) { - const allowedUploads = target[name].allowedUploads || []; + var allowedUploads = target[name].allowedUploads || []; if (fileName) { allowedUploads.push({ - maxCount, - name: fileName, + maxCount: maxCount, + name: fileName }); } target[name].allowedUploads = allowedUploads; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/anonymous.js b/dist/decorators/anonymous.js index 78a9fa94..d139c96e 100644 --- a/dist/decorators/anonymous.js +++ b/dist/decorators/anonymous.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function () { target[name].anonymous = true; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/httpVerb.js b/dist/decorators/httpVerb.js index a3d2df25..09aac162 100644 --- a/dist/decorators/httpVerb.js +++ b/dist/decorators/httpVerb.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function (httpVerb) { target[name].httpVerb = httpVerb; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/route.js b/dist/decorators/route.js index d58c4772..0cbce75e 100644 --- a/dist/decorators/route.js +++ b/dist/decorators/route.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function (route) { target[name].route = route; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/serverSentEvents.js b/dist/decorators/serverSentEvents.js index ddd1ca6a..bdd4ea05 100644 --- a/dist/decorators/serverSentEvents.js +++ b/dist/decorators/serverSentEvents.js @@ -1,7 +1,7 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); /* eslint-disable no-param-reassign */ @@ -10,4 +10,4 @@ exports.default = function () { target[name].serverSentEvents = true; return descriptor; }; -}; +}; \ No newline at end of file diff --git a/dist/decorators/types.js b/dist/decorators/types.js index 8b137891..a726efc4 100644 --- a/dist/decorators/types.js +++ b/dist/decorators/types.js @@ -1 +1 @@ - +'use strict'; \ No newline at end of file diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js index 7e801d93..8522b18a 100644 --- a/dist/defaultBindings.js +++ b/dist/defaultBindings.js @@ -1,70 +1,70 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _DeviceClaimsController = require('./controllers/DeviceClaimsController'); +var _DeviceClaimsController = require('./controllers/DeviceClaimsController'); -const _DeviceClaimsController2 = _interopRequireDefault(_DeviceClaimsController); +var _DeviceClaimsController2 = _interopRequireDefault(_DeviceClaimsController); -const _DevicesController = require('./controllers/DevicesController'); +var _DevicesController = require('./controllers/DevicesController'); -const _DevicesController2 = _interopRequireDefault(_DevicesController); +var _DevicesController2 = _interopRequireDefault(_DevicesController); -const _EventsController = require('./controllers/EventsController'); +var _EventsController = require('./controllers/EventsController'); -const _EventsController2 = _interopRequireDefault(_EventsController); +var _EventsController2 = _interopRequireDefault(_EventsController); -const _OauthClientsController = require('./controllers/OauthClientsController'); +var _OauthClientsController = require('./controllers/OauthClientsController'); -const _OauthClientsController2 = _interopRequireDefault(_OauthClientsController); +var _OauthClientsController2 = _interopRequireDefault(_OauthClientsController); -const _ProductsController = require('./controllers/ProductsController'); +var _ProductsController = require('./controllers/ProductsController'); -const _ProductsController2 = _interopRequireDefault(_ProductsController); +var _ProductsController2 = _interopRequireDefault(_ProductsController); -const _ProvisioningController = require('./controllers/ProvisioningController'); +var _ProvisioningController = require('./controllers/ProvisioningController'); -const _ProvisioningController2 = _interopRequireDefault(_ProvisioningController); +var _ProvisioningController2 = _interopRequireDefault(_ProvisioningController); -const _UsersController = require('./controllers/UsersController'); +var _UsersController = require('./controllers/UsersController'); -const _UsersController2 = _interopRequireDefault(_UsersController); +var _UsersController2 = _interopRequireDefault(_UsersController); -const _WebhooksController = require('./controllers/WebhooksController'); +var _WebhooksController = require('./controllers/WebhooksController'); -const _WebhooksController2 = _interopRequireDefault(_WebhooksController); +var _WebhooksController2 = _interopRequireDefault(_WebhooksController); -const _WebhookManager = require('./managers/WebhookManager'); +var _WebhookManager = require('./managers/WebhookManager'); -const _WebhookManager2 = _interopRequireDefault(_WebhookManager); +var _WebhookManager2 = _interopRequireDefault(_WebhookManager); -const _EventManager = require('./managers/EventManager'); +var _EventManager = require('./managers/EventManager'); -const _EventManager2 = _interopRequireDefault(_EventManager); +var _EventManager2 = _interopRequireDefault(_EventManager); -const _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepository'); +var _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepository'); -const _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository); +var _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository); -const _DeviceManager = require('./managers/DeviceManager'); +var _DeviceManager = require('./managers/DeviceManager'); -const _DeviceManager2 = _interopRequireDefault(_DeviceManager); +var _DeviceManager2 = _interopRequireDefault(_DeviceManager); -const _UserFileRepository = require('./repository/UserFileRepository'); +var _UserFileRepository = require('./repository/UserFileRepository'); -const _UserFileRepository2 = _interopRequireDefault(_UserFileRepository); +var _UserFileRepository2 = _interopRequireDefault(_UserFileRepository); -const _WebhookFileRepository = require('./repository/WebhookFileRepository'); +var _WebhookFileRepository = require('./repository/WebhookFileRepository'); -const _WebhookFileRepository2 = _interopRequireDefault(_WebhookFileRepository); +var _WebhookFileRepository2 = _interopRequireDefault(_WebhookFileRepository); -const _settings = require('./settings'); +var _settings = require('./settings'); -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -99,4 +99,4 @@ exports.default = function (container) { container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']); container.bindClass('UserRepository', _UserFileRepository2.default, ['USERS_DIRECTORY']); container.bindClass('WebhookRepository', _WebhookFileRepository2.default, ['WEBHOOKS_DIRECTORY']); -}; +}; \ No newline at end of file diff --git a/dist/exports.js b/dist/exports.js index 3bcb0cf9..a7506238 100644 --- a/dist/exports.js +++ b/dist/exports.js @@ -1,29 +1,29 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); exports.settings = exports.logger = exports.defaultBindings = exports.createApp = undefined; -const _logger = require('./lib/logger'); +var _logger = require('./lib/logger'); -const _logger2 = _interopRequireDefault(_logger); +var _logger2 = _interopRequireDefault(_logger); -const _app = require('./app'); +var _app = require('./app'); -const _app2 = _interopRequireDefault(_app); +var _app2 = _interopRequireDefault(_app); -const _defaultBindings = require('./defaultBindings'); +var _defaultBindings = require('./defaultBindings'); -const _defaultBindings2 = _interopRequireDefault(_defaultBindings); +var _defaultBindings2 = _interopRequireDefault(_defaultBindings); -const _settings = require('./settings'); +var _settings = require('./settings'); -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.createApp = _app2.default; exports.defaultBindings = _defaultBindings2.default; exports.logger = _logger2.default; -exports.settings = _settings2.default; +exports.settings = _settings2.default; \ No newline at end of file diff --git a/dist/lib/HttpError.js b/dist/lib/HttpError.js index 949d149a..e133f7ec 100644 --- a/dist/lib/HttpError.js +++ b/dist/lib/HttpError.js @@ -1,35 +1,35 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); -const _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); -const _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); -const _inherits2 = require('babel-runtime/helpers/inherits'); +var _inherits2 = require('babel-runtime/helpers/inherits'); -const _inherits3 = _interopRequireDefault(_inherits2); +var _inherits3 = _interopRequireDefault(_inherits2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const HttpError = (function (_Error) { +var HttpError = function (_Error) { (0, _inherits3.default)(HttpError, _Error); function HttpError(error) { - const status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; + var status = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 400; (0, _classCallCheck3.default)(this, HttpError); - const _this = (0, _possibleConstructorReturn3.default)(this, (HttpError.__proto__ || (0, _getPrototypeOf2.default)(HttpError)).call(this, error.message || error)); + var _this = (0, _possibleConstructorReturn3.default)(this, (HttpError.__proto__ || (0, _getPrototypeOf2.default)(HttpError)).call(this, error.message || error)); if (typeof error.status === 'number') { _this.status = error.status; @@ -40,6 +40,6 @@ const HttpError = (function (_Error) { } return HttpError; -}(Error)); +}(Error); -exports.default = HttpError; +exports.default = HttpError; \ No newline at end of file diff --git a/dist/lib/PasswordHasher.js b/dist/lib/PasswordHasher.js index d2cbd8e9..6a694e76 100644 --- a/dist/lib/PasswordHasher.js +++ b/dist/lib/PasswordHasher.js @@ -1,28 +1,28 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -const _crypto = require('crypto'); +var _crypto = require('crypto'); -const _crypto2 = _interopRequireDefault(_crypto); +var _crypto2 = _interopRequireDefault(_crypto); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const HASH_DIGEST = 'sha1'; /** +var HASH_DIGEST = 'sha1'; /** * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ * * This program is free software: you can redistribute it and/or modify @@ -39,14 +39,14 @@ const HASH_DIGEST = 'sha1'; /** * * You can download the source here: https://github.com/spark/spark-server * - * + * * */ -const HASH_ITERATIONS = 30000; -const KEY_LENGTH = 64; +var HASH_ITERATIONS = 30000; +var KEY_LENGTH = 64; -const PasswordHasher = (function () { +var PasswordHasher = function () { function PasswordHasher() { (0, _classCallCheck3.default)(this, PasswordHasher); } @@ -54,10 +54,10 @@ const PasswordHasher = (function () { (0, _createClass3.default)(PasswordHasher, null, [{ key: 'generateSalt', value: function generateSalt() { - const size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 64; + var size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 64; - return new _promise2.default((resolve, reject) => { - _crypto2.default.randomBytes(size, (error, buffer) => { + return new _promise2.default(function (resolve, reject) { + _crypto2.default.randomBytes(size, function (error, buffer) { if (error) { reject(error); return; @@ -65,12 +65,12 @@ const PasswordHasher = (function () { resolve(buffer.toString('base64')); }); }); - }, + } }, { key: 'hash', value: function hash(password, salt) { - return new _promise2.default((resolve, reject) => { - _crypto2.default.pbkdf2(password, salt, HASH_ITERATIONS, KEY_LENGTH, HASH_DIGEST, (error, key) => { + return new _promise2.default(function (resolve, reject) { + _crypto2.default.pbkdf2(password, salt, HASH_ITERATIONS, KEY_LENGTH, HASH_DIGEST, function (error, key) { if (error) { reject(error); return; @@ -78,9 +78,9 @@ const PasswordHasher = (function () { resolve(key.toString('base64')); }); }); - }, + } }]); return PasswordHasher; -}()); +}(); -exports.default = PasswordHasher; +exports.default = PasswordHasher; \ No newline at end of file diff --git a/dist/lib/deviceToAPI.js b/dist/lib/deviceToAPI.js index f9f6c09e..d332c6ab 100644 --- a/dist/lib/deviceToAPI.js +++ b/dist/lib/deviceToAPI.js @@ -1,9 +1,9 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const deviceToAPI = function deviceToAPI(device, result) { +var deviceToAPI = function deviceToAPI(device, result) { return { cellular: device.isCellular, connected: device.connected, @@ -20,8 +20,8 @@ const deviceToAPI = function deviceToAPI(device, result) { product_id: device.particleProductId, return_value: result, status: 'normal', - variables: device.variables, + variables: device.variables }; }; -exports.default = deviceToAPI; +exports.default = deviceToAPI; \ No newline at end of file diff --git a/dist/lib/eventToApi.js b/dist/lib/eventToApi.js index 80a3b835..10e30672 100644 --- a/dist/lib/eventToApi.js +++ b/dist/lib/eventToApi.js @@ -1,15 +1,15 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const eventToApi = function eventToApi(event) { +var eventToApi = function eventToApi(event) { return { coreid: event.deviceID || null, data: event.data || null, published_at: event.publishedAt, - ttl: event.ttl, + ttl: event.ttl }; }; -exports.default = eventToApi; +exports.default = eventToApi; \ No newline at end of file diff --git a/dist/lib/logger.js b/dist/lib/logger.js index 8d78a751..17958cd0 100644 --- a/dist/lib/logger.js +++ b/dist/lib/logger.js @@ -1,16 +1,16 @@ +"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require("babel-runtime/helpers/createClass"); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -31,33 +31,33 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * * You can download the source here: https://github.com/spark/spark-server * -* +* * */ -const Logger = (function () { +var Logger = function () { function Logger() { (0, _classCallCheck3.default)(this, Logger); } (0, _createClass3.default)(Logger, null, [{ - key: 'log', + key: "log", value: function log() { - let _console; + var _console; // eslint-disable-next-line prefer-rest-params (_console = console).log.apply(_console, arguments); - }, + } }, { - key: 'error', + key: "error", value: function error() { - let _console2; + var _console2; // eslint-disable-next-line prefer-rest-params (_console2 = console).error.apply(_console2, arguments); - }, + } }]); return Logger; -}()); +}(); -exports.default = Logger; +exports.default = Logger; \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index 28a70416..bb0d541d 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,44 +1,44 @@ +'use strict'; +var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); -const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); +var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); -const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); +var _entries = require('babel-runtime/core-js/object/entries'); -const _entries = require('babel-runtime/core-js/object/entries'); +var _entries2 = _interopRequireDefault(_entries); -const _entries2 = _interopRequireDefault(_entries); +var _constitute = require('constitute'); -const _constitute = require('constitute'); +var _os = require('os'); -const _os = require('os'); +var _os2 = _interopRequireDefault(_os); -const _os2 = _interopRequireDefault(_os); +var _arrayFlatten = require('array-flatten'); -const _arrayFlatten = require('array-flatten'); +var _arrayFlatten2 = _interopRequireDefault(_arrayFlatten); -const _arrayFlatten2 = _interopRequireDefault(_arrayFlatten); +var _logger = require('./lib/logger'); -const _logger = require('./lib/logger'); +var _logger2 = _interopRequireDefault(_logger); -const _logger2 = _interopRequireDefault(_logger); +var _app = require('./app'); -const _app = require('./app'); +var _app2 = _interopRequireDefault(_app); -const _app2 = _interopRequireDefault(_app); +var _defaultBindings = require('./defaultBindings'); -const _defaultBindings = require('./defaultBindings'); +var _defaultBindings2 = _interopRequireDefault(_defaultBindings); -const _defaultBindings2 = _interopRequireDefault(_defaultBindings); +var _settings = require('./settings'); -const _settings = require('./settings'); - -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const NODE_PORT = process.env.NODE_PORT || 8080; +var NODE_PORT = process.env.NODE_PORT || 8080; -process.on('uncaughtException', (exception) => { +process.on('uncaughtException', function (exception) { _logger2.default.error('uncaughtException', { message: exception.message, stack: exception.stack }); // logging with MetaData process.exit(1); // exit with failure }); @@ -54,23 +54,31 @@ process.on('uncaughtException', (exception) => { * * See https://github.com/justmoon/constitute for more info */ -const container = new _constitute.Container(); +var container = new _constitute.Container(); (0, _defaultBindings2.default)(container); -const deviceServer = container.constitute('DeviceServer'); +var deviceServer = container.constitute('DeviceServer'); deviceServer.start(); -const app = (0, _app2.default)(container, _settings2.default); +var app = (0, _app2.default)(container, _settings2.default); -app.listen(NODE_PORT, () => console.log(`express server started on port ${NODE_PORT}`)); +app.listen(NODE_PORT, function () { + return console.log('express server started on port ' + NODE_PORT); +}); -const addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map( +var addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map( // eslint-disable-next-line no-unused-vars -(_ref) => { - let _ref2 = (0, _slicedToArray3.default)(_ref, 2), - name = _ref2[0], - nic = _ref2[1]; - - return nic.filter(address => address.family === 'IPv4' && address.address !== '127.0.0.1').map(address => address.address); +function (_ref) { + var _ref2 = (0, _slicedToArray3.default)(_ref, 2), + name = _ref2[0], + nic = _ref2[1]; + + return nic.filter(function (address) { + return address.family === 'IPv4' && address.address !== '127.0.0.1'; + }).map(function (address) { + return address.address; + }); })); -addresses.forEach(address => console.log(`Your device server IP address is: ${address}`)); +addresses.forEach(function (address) { + return console.log('Your device server IP address is: ' + address); +}); \ No newline at end of file diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js index bc9a550a..15a888ea 100644 --- a/dist/managers/DeviceManager.js +++ b/dist/managers/DeviceManager.js @@ -1,53 +1,52 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); +var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); -const _slicedToArray3 = _interopRequireDefault(_slicedToArray2); +var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _ursa = require('ursa'); +var _ursa = require('ursa'); -const _ursa2 = _interopRequireDefault(_ursa); +var _ursa2 = _interopRequireDefault(_ursa); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer) { - const _this = this; +var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer) { + var _this = this; (0, _classCallCheck3.default)(this, DeviceManager); - this.claimDevice = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) { - let deviceAttributes, - attributesToSave; - return _regenerator2.default.wrap((_context) => { + this.claimDevice = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) { + var deviceAttributes, attributesToSave; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -74,7 +73,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 7: attributesToSave = (0, _extends3.default)({}, deviceAttributes, { - ownerID: userID, + ownerID: userID }); _context.next = 10; return _this._deviceAttributeRepository.update(attributesToSave); @@ -93,13 +92,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x, _x2) { return _ref.apply(this, arguments); }; - }()); + }(); - this.unclaimDevice = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID, userID) { - let deviceAttributes, - attributesToSave; - return _regenerator2.default.wrap((_context2) => { + this.unclaimDevice = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID, userID) { + var deviceAttributes, attributesToSave; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -118,7 +116,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 5: attributesToSave = (0, _extends3.default)({}, deviceAttributes, { - ownerID: null, + ownerID: null }); _context2.next = 8; return _this._deviceAttributeRepository.update(attributesToSave); @@ -137,13 +135,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x3, _x4) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.getByID = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, userID) { - let attributes, - device; - return _regenerator2.default.wrap((_context3) => { + this.getByID = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, userID) { + var attributes, device; + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -165,7 +162,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return _context3.abrupt('return', (0, _extends3.default)({}, attributes, { connected: device && device.ping().connected || false, lastFlashedAppName: null, - lastHeard: device && device.ping().lastPing || attributes.lastHeard, + lastHeard: device && device.ping().lastPing || attributes.lastHeard })); case 7: @@ -179,17 +176,13 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x5, _x6) { return _ref3.apply(this, arguments); }; - }()); + }(); - this.getDetailsByID = (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, userID) { - let device, - _ref5, - _ref6, - attributes, - description; + this.getDetailsByID = function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, userID) { + var device, _ref5, _ref6, attributes, description; - return _regenerator2.default.wrap((_context4) => { + return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: @@ -216,7 +209,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi functions: description ? description.state.f : null, lastFlashedAppName: null, lastHeard: device && device.ping().lastPing || attributes.lastHeard, - variables: description ? description.state.v : null, + variables: description ? description.state.v : null })); case 10: @@ -230,13 +223,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x7, _x8) { return _ref4.apply(this, arguments); }; - }()); + }(); - this.getAll = (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(userID) { - let devicesAttributes, - devicePromises; - return _regenerator2.default.wrap((_context6) => { + this.getAll = function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(userID) { + var devicesAttributes, devicePromises; + return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: @@ -246,9 +238,9 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 2: devicesAttributes = _context6.sent; devicePromises = devicesAttributes.map(function () { - const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) { - let device; - return _regenerator2.default.wrap((_context5) => { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) { + var device; + return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: @@ -256,7 +248,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return _context5.abrupt('return', (0, _extends3.default)({}, attributes, { connected: device && device.ping().connected || false, lastFlashedAppName: null, - lastHeard: device && device.ping().lastPing || attributes.lastHeard, + lastHeard: device && device.ping().lastPing || attributes.lastHeard })); case 2: @@ -284,13 +276,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x9) { return _ref7.apply(this, arguments); }; - }()); + }(); - this.callFunction = (function () { - const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, userID, functionName, functionArguments) { - let doesUserHaveAccess, - device; - return _regenerator2.default.wrap((_context7) => { + this.callFunction = function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, userID, functionName, functionArguments) { + var doesUserHaveAccess, device; + return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: @@ -335,13 +326,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x11, _x12, _x13, _x14) { return _ref9.apply(this, arguments); }; - }()); + }(); - this.getVariableValue = (function () { - const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, userID, varName) { - let doesUserHaveAccess, - device; - return _regenerator2.default.wrap((_context8) => { + this.getVariableValue = function () { + var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, userID, varName) { + var doesUserHaveAccess, device; + return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: @@ -386,12 +376,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x15, _x16, _x17) { return _ref10.apply(this, arguments); }; - }()); + }(); - this.flashBinary = (function () { - const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) { - let device; - return _regenerator2.default.wrap((_context9) => { + this.flashBinary = function () { + var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) { + var device; + return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: @@ -422,13 +412,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x18, _x19) { return _ref11.apply(this, arguments); }; - }()); + }(); - this.flashKnownApp = (function () { - const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, userID, appName) { - let knownFirmware, - device; - return _regenerator2.default.wrap((_context10) => { + this.flashKnownApp = function () { + var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, userID, appName) { + var knownFirmware, device; + return _regenerator2.default.wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { case 0: @@ -451,7 +440,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi break; } - throw new _HttpError2.default(`No firmware ${appName} found`, 404); + throw new _HttpError2.default('No firmware ' + appName + ' found', 404); case 7: device = _this._deviceServer.getDevice(deviceID); @@ -481,14 +470,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x20, _x21, _x22) { return _ref12.apply(this, arguments); }; - }()); - - this.provision = (function () { - const _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey) { - let createdKey, - existingAttributes, - attributes; - return _regenerator2.default.wrap((_context11) => { + }(); + + this.provision = function () { + var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey) { + var createdKey, existingAttributes, attributes; + return _regenerator2.default.wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: @@ -508,8 +495,8 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 6: _context11.prev = 6; - _context11.t0 = _context11.catch(0); - throw new _HttpError2.default(`Key error ${_context11.t0}`); + _context11.t0 = _context11['catch'](0); + throw new _HttpError2.default('Key error ' + _context11.t0); case 9: _context11.next = 11; @@ -522,11 +509,11 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 13: existingAttributes = _context11.sent; attributes = (0, _extends3.default)({ - deviceID, + deviceID: deviceID }, existingAttributes, { ownerID: userID, registrar: userID, - timestamp: new Date(), + timestamp: new Date() }); _context11.next = 17; return _this._deviceAttributeRepository.update(attributes); @@ -549,12 +536,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x23, _x24, _x25) { return _ref13.apply(this, arguments); }; - }()); + }(); - this.raiseYourHand = (function () { - const _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, userID, shouldShowSignal) { - let device; - return _regenerator2.default.wrap((_context12) => { + this.raiseYourHand = function () { + var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, userID, shouldShowSignal) { + var device; + return _regenerator2.default.wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { case 0: @@ -597,13 +584,12 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x26, _x27, _x28) { return _ref14.apply(this, arguments); }; - }()); + }(); - this.renameDevice = (function () { - const _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, userID, name) { - let attributes, - attributesToSave; - return _regenerator2.default.wrap((_context13) => { + this.renameDevice = function () { + var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, userID, name) { + var attributes, attributesToSave; + return _regenerator2.default.wrap(function _callee13$(_context13) { while (1) { switch (_context13.prev = _context13.next) { case 0: @@ -622,7 +608,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi case 5: attributesToSave = (0, _extends3.default)({}, attributes, { - name, + name: name }); _context13.next = 8; return _this._deviceAttributeRepository.update(attributesToSave); @@ -641,7 +627,7 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi return function (_x29, _x30, _x31) { return _ref15.apply(this, arguments); }; - }()); + }(); this._deviceAttributeRepository = deviceAttributeRepository; this._deviceFirmwareRepository = deviceFirmwareRepository; @@ -649,4 +635,4 @@ const DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFi this._deviceServer = deviceServer; }; -exports.default = DeviceManager; +exports.default = DeviceManager; \ No newline at end of file diff --git a/dist/managers/EventManager.js b/dist/managers/EventManager.js index 44c33acb..08436883 100644 --- a/dist/managers/EventManager.js +++ b/dist/managers/EventManager.js @@ -1,17 +1,17 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const EventManager = function EventManager(eventPublisher) { - const _this = this; +var EventManager = function EventManager(eventPublisher) { + var _this = this; (0, _classCallCheck3.default)(this, EventManager); @@ -30,4 +30,4 @@ const EventManager = function EventManager(eventPublisher) { this._eventPublisher = eventPublisher; }; -exports.default = EventManager; +exports.default = EventManager; \ No newline at end of file diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js index 1766ca29..d124a01d 100644 --- a/dist/managers/FirmwareCompilationManager.js +++ b/dist/managers/FirmwareCompilationManager.js @@ -1,84 +1,84 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _stringify = require('babel-runtime/core-js/json/stringify'); +var _stringify = require('babel-runtime/core-js/json/stringify'); -const _stringify2 = _interopRequireDefault(_stringify); +var _stringify2 = _interopRequireDefault(_stringify); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _map = require('babel-runtime/core-js/map'); +var _map = require('babel-runtime/core-js/map'); -const _map2 = _interopRequireDefault(_map); +var _map2 = _interopRequireDefault(_map); -const _crypto = require('crypto'); +var _crypto = require('crypto'); -const _crypto2 = _interopRequireDefault(_crypto); +var _crypto2 = _interopRequireDefault(_crypto); -const _fs = require('fs'); +var _fs = require('fs'); -const _fs2 = _interopRequireDefault(_fs); +var _fs2 = _interopRequireDefault(_fs); -const _path = require('path'); +var _path = require('path'); -const _path2 = _interopRequireDefault(_path); +var _path2 = _interopRequireDefault(_path); -const _mkdirp = require('mkdirp'); +var _mkdirp = require('mkdirp'); -const _mkdirp2 = _interopRequireDefault(_mkdirp); +var _mkdirp2 = _interopRequireDefault(_mkdirp); -const _rmfr = require('rmfr'); +var _rmfr = require('rmfr'); -const _rmfr2 = _interopRequireDefault(_rmfr); +var _rmfr2 = _interopRequireDefault(_rmfr); -const _child_process = require('child_process'); +var _child_process = require('child_process'); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _settings = require('../settings'); +var _settings = require('../settings'); -const _settings2 = _interopRequireDefault(_settings); +var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const IS_COMPILATION_ENABLED = _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); +var IS_COMPILATION_ENABLED = _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY); -const USER_APP_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications'); -const BIN_PATH = _path2.default.join(_settings2.default.BUILD_DIRECTORY, 'bin'); -const MAKE_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'main'); +var USER_APP_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications'); +var BIN_PATH = _path2.default.join(_settings2.default.BUILD_DIRECTORY, 'bin'); +var MAKE_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'main'); -const FILE_NAME_BY_KEY = new _map2.default(); +var FILE_NAME_BY_KEY = new _map2.default(); -const getKey = function getKey() { +var getKey = function getKey() { return _crypto2.default.randomBytes(24).toString('hex').substring(0, 24); }; -const getUniqueKey = function getUniqueKey() { - let key = getKey(); +var getUniqueKey = function getUniqueKey() { + var key = getKey(); while (FILE_NAME_BY_KEY.has(key)) { key = getKey(); } return key; }; -const FirmwareCompilationManager = function FirmwareCompilationManager() { +var FirmwareCompilationManager = function FirmwareCompilationManager() { (0, _classCallCheck3.default)(this, FirmwareCompilationManager); }; @@ -91,12 +91,14 @@ FirmwareCompilationManager.getBinaryForID = function (id) { return null; } - const binaryPath = _path2.default.join(BIN_PATH, id); + var binaryPath = _path2.default.join(BIN_PATH, id); if (!_fs2.default.existsSync(binaryPath)) { return null; } - const binFileName = _fs2.default.readdirSync(binaryPath).find(file => file.endsWith('.bin')); + var binFileName = _fs2.default.readdirSync(binaryPath).find(function (file) { + return file.endsWith('.bin'); + }); if (!binFileName) { return null; @@ -105,19 +107,10 @@ FirmwareCompilationManager.getBinaryForID = function (id) { return _fs2.default.readFileSync(_path2.default.join(binaryPath, binFileName)); }; -FirmwareCompilationManager.compileSource = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(platformID, files) { - let platformName, - appFolder, - appPath, - id, - binPath, - makeProcess, - errors, - sizeInfo, - date, - config; - return _regenerator2.default.wrap((_context) => { +FirmwareCompilationManager.compileSource = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(platformID, files) { + var platformName, appFolder, appPath, id, binPath, makeProcess, errors, sizeInfo, date, config; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -141,19 +134,19 @@ FirmwareCompilationManager.compileSource = (function () { case 5: platformName = platformName.toLowerCase(); - appFolder = (`${platformName}_firmware_${new Date().getTime()}`).toLowerCase(); + appFolder = (platformName + '_firmware_' + new Date().getTime()).toLowerCase(); appPath = _path2.default.join(USER_APP_PATH, appFolder); _mkdirp2.default.sync(appPath); - files.forEach((file) => { - const fileName = file.originalname; - const fileExtension = _path2.default.extname(fileName); - let iterator = 0; - let combinedPath = _path2.default.join(appPath, fileName); + files.forEach(function (file) { + var fileName = file.originalname; + var fileExtension = _path2.default.extname(fileName); + var iterator = 0; + var combinedPath = _path2.default.join(appPath, fileName); while (_fs2.default.existsSync(combinedPath)) { - combinedPath = _path2.default.join(appPath, `${_path2.default.basename(fileName, fileExtension)}_${iterator++}${fileExtension}`); + combinedPath = _path2.default.join(appPath, '' + _path2.default.basename(fileName, fileExtension) + ('_' + iterator++ + fileExtension)); } _fs2.default.writeFileSync(combinedPath, file.buffer); @@ -161,18 +154,18 @@ FirmwareCompilationManager.compileSource = (function () { id = getUniqueKey(); binPath = _path2.default.join(BIN_PATH, id); - makeProcess = (0, _child_process.spawn)('make', [`APP=${appFolder}`, `PLATFORM_ID=${platformID}`, `TARGET_DIR=${_path2.default.relative(MAKE_PATH, binPath).replace(/\\/g, '/')}`], { cwd: MAKE_PATH }); + makeProcess = (0, _child_process.spawn)('make', ['APP=' + appFolder, 'PLATFORM_ID=' + platformID, 'TARGET_DIR=' + _path2.default.relative(MAKE_PATH, binPath).replace(/\\/g, '/')], { cwd: MAKE_PATH }); errors = []; - makeProcess.stderr.on('data', (data) => { - console.log(`${data}`); - errors.push(`${data}`); + makeProcess.stderr.on('data', function (data) { + console.log('' + data); + errors.push('' + data); }); sizeInfo = 'not implemented'; - makeProcess.stdout.on('data', (data) => { - const output = `${data}`; + makeProcess.stdout.on('data', function (data) { + var output = '' + data; if (output.includes('text\t')) { sizeInfo = output; @@ -180,8 +173,10 @@ FirmwareCompilationManager.compileSource = (function () { }); _context.next = 19; - return new _promise2.default((resolve) => { - makeProcess.on('exit', () => resolve()); + return new _promise2.default(function (resolve) { + makeProcess.on('exit', function () { + return resolve(); + }); }); case 19: @@ -190,13 +185,13 @@ FirmwareCompilationManager.compileSource = (function () { date.setDate(date.getDate() + 1); config = { binary_id: id, - errors, + errors: errors, // expire in one day expires_at: date, // TODO: this variable has a bunch of extra crap including file names. // we should filter out the string to only show the file sizes - sizeInfo, + sizeInfo: sizeInfo }; @@ -215,16 +210,18 @@ FirmwareCompilationManager.compileSource = (function () { return function (_x, _x2) { return _ref.apply(this, arguments); }; -}()); +}(); FirmwareCompilationManager.addFirmwareCleanupTask = function (appFolderPath, config) { - const configPath = _path2.default.join(appFolderPath, 'config.json'); + var configPath = _path2.default.join(appFolderPath, 'config.json'); if (!_fs2.default.existsSync(configPath)) { _fs2.default.writeFileSync(configPath, (0, _stringify2.default)(config)); } - const currentDate = new Date(); - const difference = new Date(config.expires_at).getTime() - currentDate.getTime(); - setTimeout(() => (0, _rmfr2.default)(appFolderPath), difference); + var currentDate = new Date(); + var difference = new Date(config.expires_at).getTime() - currentDate.getTime(); + setTimeout(function () { + return (0, _rmfr2.default)(appFolderPath); + }, difference); }; if (IS_COMPILATION_ENABLED) { @@ -236,18 +233,18 @@ if (IS_COMPILATION_ENABLED) { _mkdirp2.default.sync(BIN_PATH); } - _fs2.default.readdirSync(USER_APP_PATH).forEach((file) => { - const appFolder = _path2.default.join(USER_APP_PATH, file); - const configPath = _path2.default.join(appFolder, 'config.json'); + _fs2.default.readdirSync(USER_APP_PATH).forEach(function (file) { + var appFolder = _path2.default.join(USER_APP_PATH, file); + var configPath = _path2.default.join(appFolder, 'config.json'); if (!_fs2.default.existsSync(configPath)) { return; } - const configString = _fs2.default.readFileSync(configPath, 'utf8'); + var configString = _fs2.default.readFileSync(configPath, 'utf8'); if (!configString) { return; } - const config = JSON.parse(configString); + var config = JSON.parse(configString); if (config.expires_at < new Date()) { // TODO - clean up artifacts in the firmware folder. Every binary will have // files in firmare/build/target/user & firmware/build/target/user-part @@ -260,4 +257,4 @@ if (IS_COMPILATION_ENABLED) { }); } -exports.default = FirmwareCompilationManager; +exports.default = FirmwareCompilationManager; \ No newline at end of file diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js index b93dbcf0..77ff0346 100644 --- a/dist/managers/WebhookManager.js +++ b/dist/managers/WebhookManager.js @@ -1,68 +1,68 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _promise = require('babel-runtime/core-js/promise'); +var _promise = require('babel-runtime/core-js/promise'); -const _promise2 = _interopRequireDefault(_promise); +var _promise2 = _interopRequireDefault(_promise); -const _typeof2 = require('babel-runtime/helpers/typeof'); +var _typeof2 = require('babel-runtime/helpers/typeof'); -const _typeof3 = _interopRequireDefault(_typeof2); +var _typeof3 = _interopRequireDefault(_typeof2); -const _stringify = require('babel-runtime/core-js/json/stringify'); +var _stringify = require('babel-runtime/core-js/json/stringify'); -const _stringify2 = _interopRequireDefault(_stringify); +var _stringify2 = _interopRequireDefault(_stringify); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _map = require('babel-runtime/core-js/map'); +var _map = require('babel-runtime/core-js/map'); -const _map2 = _interopRequireDefault(_map); +var _map2 = _interopRequireDefault(_map); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _hogan = require('hogan.js'); +var _hogan = require('hogan.js'); -const _hogan2 = _interopRequireDefault(_hogan); +var _hogan2 = _interopRequireDefault(_hogan); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); -const _logger = require('../lib/logger'); +var _logger = require('../lib/logger'); -const _logger2 = _interopRequireDefault(_logger); +var _logger2 = _interopRequireDefault(_logger); -const _nullthrows = require('nullthrows'); +var _nullthrows = require('nullthrows'); -const _nullthrows2 = _interopRequireDefault(_nullthrows); +var _nullthrows2 = _interopRequireDefault(_nullthrows); -const _request = require('request'); +var _request = require('request'); -const _request2 = _interopRequireDefault(_request); +var _request2 = _interopRequireDefault(_request); -const _throttle = require('lodash/throttle'); +var _throttle = require('lodash/throttle'); -const _throttle2 = _interopRequireDefault(_throttle); +var _throttle2 = _interopRequireDefault(_throttle); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const parseEventData = function parseEventData(event) { +var parseEventData = function parseEventData(event) { try { if (event.data) { return JSON.parse(event.data); @@ -73,9 +73,9 @@ const parseEventData = function parseEventData(event) { } }; -const splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { - const chunks = []; - let ii = 0; +var splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) { + var chunks = []; + var ii = 0; while (ii < buffer.length) { chunks.push(buffer.slice(ii, ii += chunkSize)); } @@ -83,22 +83,22 @@ const splitBufferIntoChunks = function splitBufferIntoChunks(buffer, chunkSize) return chunks; }; -const MAX_WEBHOOK_ERRORS_COUNT = 10; -const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; -const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; -const MAX_RESPONSE_MESSAGE_SIZE = 100000; +var MAX_WEBHOOK_ERRORS_COUNT = 10; +var WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min; +var MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512; +var MAX_RESPONSE_MESSAGE_SIZE = 100000; -const WebhookManager = function WebhookManager(webhookRepository, eventPublisher) { - const _this = this; +var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) { + var _this = this; (0, _classCallCheck3.default)(this, WebhookManager); this._subscriptionIDsByWebhookID = new _map2.default(); this._errorsCountByWebhookID = new _map2.default(); - this.create = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) { - let webhook; - return _regenerator2.default.wrap((_context) => { + this.create = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) { + var webhook; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -122,12 +122,12 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x) { return _ref.apply(this, arguments); }; - }()); + }(); - this.deleteByID = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID, userID) { - let webhook; - return _regenerator2.default.wrap((_context2) => { + this.deleteByID = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID, userID) { + var webhook; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -162,11 +162,11 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x2, _x3) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.getAll = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID) { - return _regenerator2.default.wrap((_context3) => { + this.getAll = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID) { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -187,12 +187,12 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x4) { return _ref3.apply(this, arguments); }; - }()); + }(); - this.getByID = (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID, userID) { - let webhook; - return _regenerator2.default.wrap((_context4) => { + this.getByID = function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID, userID) { + var webhook; + return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: @@ -223,11 +223,11 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x5, _x6) { return _ref4.apply(this, arguments); }; - }()); + }(); this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { - let allWebhooks; - return _regenerator2.default.wrap((_context5) => { + var allWebhooks; + return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: @@ -237,7 +237,9 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher case 2: allWebhooks = _context5.sent; - allWebhooks.forEach(webhook => _this._subscribeWebhook(webhook)); + allWebhooks.forEach(function (webhook) { + return _this._subscribeWebhook(webhook); + }); case 4: case 'end': @@ -248,16 +250,16 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher })); this._subscribeWebhook = function (webhook) { - const subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), { + var subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), { deviceID: webhook.deviceID, mydevices: webhook.mydevices, - userID: webhook.ownerID, + userID: webhook.ownerID }); _this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID); }; this._unsubscribeWebhookByID = function (webhookID) { - const subscriptionID = _this._subscriptionIDsByWebhookID.get(webhookID); + var subscriptionID = _this._subscriptionIDsByWebhookID.get(webhookID); if (!subscriptionID) { return; } @@ -269,7 +271,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher this._onNewWebhookEvent = function (webhook) { return function (event) { try { - const webhookErrorCount = _this._errorsCountByWebhookID.get(webhook.id) || 0; + var webhookErrorCount = _this._errorsCountByWebhookID.get(webhook.id) || 0; if (webhookErrorCount < MAX_WEBHOOK_ERRORS_COUNT) { _this.runWebhook(webhook, event); @@ -280,40 +282,28 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher data: 'Too many errors, webhook disabled', isPublic: false, name: _this._compileErrorResponseTopic(webhook, event), - userID: event.userID, + userID: event.userID }); _this.runWebhookThrottled(webhook, event); } catch (error) { - _logger2.default.error(`webhookError: ${error}`); + _logger2.default.error('webhookError: ' + error); } }; }; - this.runWebhook = (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(webhook, event) { - let _ret; + this.runWebhook = function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(webhook, event) { + var _ret; - return _regenerator2.default.wrap((_context7) => { + return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: _context7.prev = 0; return _context7.delegateYield(_regenerator2.default.mark(function _callee6() { - let webhookVariablesObject, - requestJson, - requestFormData, - requestUrl, - requestQuery, - responseTopic, - isJsonRequest, - requestOptions, - responseBody, - isResponseBodyAnObject, - responseTemplate, - responseEventData, - chunks; - return _regenerator2.default.wrap((_context6) => { + var webhookVariablesObject, requestJson, requestFormData, requestUrl, requestQuery, responseTopic, isJsonRequest, requestOptions, responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks; + return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: @@ -333,7 +323,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher method: webhook.requestType, qs: requestQuery, strictSSL: webhook.rejectUnauthorized, - url: (0, _nullthrows2.default)(requestUrl), + url: (0, _nullthrows2.default)(requestUrl) }; _context6.next = 10; return _this._callWebhook(webhook, event, requestOptions); @@ -347,7 +337,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher } return _context6.abrupt('return', { - v: void 0, + v: void 0 }); case 13: @@ -357,14 +347,14 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher chunks = splitBufferIntoChunks(Buffer.from(responseEventData).slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE); - chunks.forEach((chunk, index) => { - const responseEventName = responseTopic && `${responseTopic}/${index}` || `hook-response/${event.name}/${index}`; + chunks.forEach(function (chunk, index) { + var responseEventName = responseTopic && responseTopic + '/' + index || 'hook-response/' + event.name + '/' + index; _this._eventPublisher.publish({ data: chunk, isPublic: false, name: responseEventName, - userID: event.userID, + userID: event.userID }); }); @@ -379,7 +369,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher case 2: _ret = _context7.t0; - if (!((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === 'object')) { + if (!((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === "object")) { _context7.next = 5; break; } @@ -392,9 +382,9 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher case 7: _context7.prev = 7; - _context7.t1 = _context7.catch(0); + _context7.t1 = _context7['catch'](0); - _logger2.default.error(`webhookError: ${_context7.t1}`); + _logger2.default.error('webhookError: ' + _context7.t1); case 10: case 'end': @@ -407,48 +397,50 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return function (_x7, _x8) { return _ref6.apply(this, arguments); }; - }()); + }(); this.runWebhookThrottled = (0, _throttle2.default)(this.runWebhook, WEBHOOK_THROTTLE_TIME, { leading: false, trailing: true }); this._callWebhook = function (webhook, event, requestOptions) { - return new _promise2.default((resolve, reject) => (0, _request2.default)(requestOptions, (error, response, responseBody) => { - const onResponseError = function onResponseError(errorMessage) { - _this._incrementWebhookErrorCounter(webhook.id); + return new _promise2.default(function (resolve, reject) { + return (0, _request2.default)(requestOptions, function (error, response, responseBody) { + var onResponseError = function onResponseError(errorMessage) { + _this._incrementWebhookErrorCounter(webhook.id); + + _this._eventPublisher.publish({ + data: errorMessage, + isPublic: false, + name: _this._compileErrorResponseTopic(webhook, event), + userID: event.userID + }); + + reject(new Error(errorMessage)); + }; + + if (error) { + onResponseError(error.message); + return; + } + if (response.statusCode >= 400) { + onResponseError(response.statusMessage); + return; + } + + _this._resetWebhookErrorCounter(webhook.id); _this._eventPublisher.publish({ - data: errorMessage, isPublic: false, - name: _this._compileErrorResponseTopic(webhook, event), - userID: event.userID, + name: 'hook-sent/' + event.name, + userID: event.userID }); - reject(new Error(errorMessage)); - }; - - if (error) { - onResponseError(error.message); - return; - } - if (response.statusCode >= 400) { - onResponseError(response.statusMessage); - return; - } - - _this._resetWebhookErrorCounter(webhook.id); - - _this._eventPublisher.publish({ - isPublic: false, - name: `hook-sent/${event.name}`, - userID: event.userID, + resolve(responseBody); }); - - resolve(responseBody); - })); + }); }; this._getEventVariables = function (event) { - const defaultWebhookVariables = { + var defaultWebhookVariables = { PARTICLE_DEVICE_ID: event.deviceID, PARTICLE_EVENT_NAME: event.name, PARTICLE_EVENT_VALUE: event.data, @@ -457,20 +449,20 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher SPARK_CORE_ID: event.deviceID, SPARK_EVENT_NAME: event.name, SPARK_EVENT_VALUE: event.data, - SPARK_PUBLISHED_AT: event.publishedAt, + SPARK_PUBLISHED_AT: event.publishedAt }; - const eventDataVariables = parseEventData(event); + var eventDataVariables = parseEventData(event); return (0, _extends3.default)({}, defaultWebhookVariables, eventDataVariables); }; this._getRequestData = function (customData, event, noDefaults) { - const defaultEventData = { + var defaultEventData = { coreid: event.deviceID, data: event.data, event: event.name, - published_at: event.publishedAt, + published_at: event.publishedAt }; return noDefaults ? customData : (0, _extends3.default)({}, defaultEventData, customData || {}); @@ -485,7 +477,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher return undefined; } - const compiledTemplate = _this._compileTemplate((0, _stringify2.default)(template), variables); + var compiledTemplate = _this._compileTemplate((0, _stringify2.default)(template), variables); if (!compiledTemplate) { return undefined; } @@ -494,12 +486,12 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher }; this._compileErrorResponseTopic = function (webhook, event) { - const variables = _this._getEventVariables(event); - return _this._compileTemplate(webhook.errorResponseTopic, variables) || `hook-error/${event.name}`; + var variables = _this._getEventVariables(event); + return _this._compileTemplate(webhook.errorResponseTopic, variables) || 'hook-error/' + event.name; }; this._incrementWebhookErrorCounter = function (webhookID) { - const errorsCount = _this._errorsCountByWebhookID.get(webhookID) || 0; + var errorsCount = _this._errorsCountByWebhookID.get(webhookID) || 0; _this._errorsCountByWebhookID.set(webhookID, errorsCount + 1); }; @@ -511,7 +503,7 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher this._eventPublisher = eventPublisher; (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { - return _regenerator2.default.wrap((_context8) => { + return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: @@ -530,4 +522,4 @@ const WebhookManager = function WebhookManager(webhookRepository, eventPublisher }))(); }; -exports.default = WebhookManager; +exports.default = WebhookManager; \ No newline at end of file diff --git a/dist/repository/DeviceFirmwareFileRepository.js b/dist/repository/DeviceFirmwareFileRepository.js index 7255f1b7..23239fda 100644 --- a/dist/repository/DeviceFirmwareFileRepository.js +++ b/dist/repository/DeviceFirmwareFileRepository.js @@ -1,33 +1,30 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -let _dec, - _desc, - _value, - _class; +var _dec, _desc, _value, _class; -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -37,7 +34,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -52,7 +51,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], { promise: false }), (_class = (function () { +var DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], { promise: false }), (_class = function () { function DeviceFirmwareFileRepository(path) { (0, _classCallCheck3.default)(this, DeviceFirmwareFileRepository); @@ -62,9 +61,9 @@ const DeviceFirmwareFileRepository = (_dec = (0, _sparkProtocol.memoizeGet)([], (0, _createClass3.default)(DeviceFirmwareFileRepository, [{ key: 'getByName', value: function getByName(appName) { - return this._fileManager.getFileBuffer(`${appName}.bin`); - }, + return this._fileManager.getFileBuffer(appName + '.bin'); + } }]); return DeviceFirmwareFileRepository; -}()), (_applyDecoratedDescriptor(_class.prototype, 'getByName', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByName'), _class.prototype)), _class)); -exports.default = DeviceFirmwareFileRepository; +}(), (_applyDecoratedDescriptor(_class.prototype, 'getByName', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByName'), _class.prototype)), _class)); +exports.default = DeviceFirmwareFileRepository; \ No newline at end of file diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js index 33353a83..ff6f94a6 100644 --- a/dist/repository/UserFileRepository.js +++ b/dist/repository/UserFileRepository.js @@ -1,67 +1,58 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); +var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); -const _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); +var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -let _dec, - _dec2, - _dec3, - _dec4, - _dec5, - _dec6, - _dec7, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _desc, _value, _class; -const _uuid = require('uuid'); +var _uuid = require('uuid'); -const _uuid2 = _interopRequireDefault(_uuid); +var _uuid2 = _interopRequireDefault(_uuid); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _PasswordHasher = require('../lib/PasswordHasher'); +var _PasswordHasher = require('../lib/PasswordHasher'); -const _PasswordHasher2 = _interopRequireDefault(_PasswordHasher); +var _PasswordHasher2 = _interopRequireDefault(_PasswordHasher); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -71,7 +62,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -86,20 +79,16 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(['id']), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = (function () { +var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(['id']), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = function () { function UserFileRepository(path) { - const _this = this; + var _this = this; (0, _classCallCheck3.default)(this, UserFileRepository); - this.createWithCredentials = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { - let username, - password, - salt, - passwordHash, - modelToSave; - return _regenerator2.default.wrap((_context) => { + this.createWithCredentials = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) { + var username, password, salt, passwordHash, modelToSave; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -116,9 +105,9 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, passwordHash = _context.sent; modelToSave = { accessTokens: [], - passwordHash, - salt, - username, + passwordHash: passwordHash, + salt: salt, + username: username }; _context.next = 10; return _this.create(modelToSave); @@ -137,13 +126,12 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x) { return _ref.apply(this, arguments); }; - }()); + }(); - this.validateLogin = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { - let user, - hash; - return _regenerator2.default.wrap((_context2) => { + this.validateLogin = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) { + var user, hash; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -180,7 +168,7 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 14: _context2.prev = 14; - _context2.t0 = _context2.catch(0); + _context2.t0 = _context2['catch'](0); throw _context2.t0; case 17: @@ -194,11 +182,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x2, _x3) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.getByAccessToken = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(accessToken) { - return _regenerator2.default.wrap((_context3) => { + this.getByAccessToken = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(accessToken) { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -207,7 +195,9 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 2: _context3.t0 = function (user) { - return user.accessTokens.some(tokenObject => tokenObject.accessToken === accessToken); + return user.accessTokens.some(function (tokenObject) { + return tokenObject.accessToken === accessToken; + }); }; return _context3.abrupt('return', _context3.sent.find(_context3.t0)); @@ -223,13 +213,12 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x4) { return _ref3.apply(this, arguments); }; - }()); + }(); - this.deleteAccessToken = (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, token) { - let user, - userToSave; - return _regenerator2.default.wrap((_context4) => { + this.deleteAccessToken = function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, token) { + var user, userToSave; + return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: @@ -248,7 +237,9 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 5: userToSave = (0, _extends3.default)({}, user, { - accessTokens: user.accessTokens.filter(tokenObject => tokenObject.accessToken !== token), + accessTokens: user.accessTokens.filter(function (tokenObject) { + return tokenObject.accessToken !== token; + }) }); _context4.next = 8; return _this.update(userToSave); @@ -264,13 +255,12 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x5, _x6) { return _ref4.apply(this, arguments); }; - }()); + }(); - this.saveAccessToken = (function () { - const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) { - let user, - userToSave; - return _regenerator2.default.wrap((_context5) => { + this.saveAccessToken = function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) { + var user, userToSave; + return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: @@ -289,7 +279,7 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 5: userToSave = (0, _extends3.default)({}, user, { - accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]), + accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]) }); _context5.next = 8; return _this.update(userToSave); @@ -308,17 +298,16 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, return function (_x7, _x8) { return _ref5.apply(this, arguments); }; - }()); + }(); this._fileManager = new _sparkProtocol.JSONFileManager(path); } (0, _createClass3.default)(UserFileRepository, [{ key: 'create', - value: (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(user) { - let id, - modelToSave; + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(user) { + var id, modelToSave; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { @@ -327,7 +316,7 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, case 1: _context6.next = 3; - return this._fileManager.hasFile(`${id}.json`); + return this._fileManager.hasFile(id + '.json'); case 3: if (!_context6.sent) { @@ -343,11 +332,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, modelToSave = (0, _extends3.default)({}, user, { created_at: new Date(), created_by: null, - id, + id: id }); - this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + this._fileManager.createFile(modelToSave.id + '.json', modelToSave); return _context6.abrupt('return', modelToSave); case 10: @@ -363,16 +352,16 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return create; - }()), + }() }, { key: 'update', - value: (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(model) { + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(model) { return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: - this._fileManager.writeFile(`${model.id}.json`, model); + this._fileManager.writeFile(model.id + '.json', model); return _context7.abrupt('return', model); case 2: @@ -388,11 +377,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return update; - }()), + }() }, { key: 'getAll', - value: (function () { - const _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { + value: function () { + var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() { return _regenerator2.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { @@ -412,16 +401,16 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return getAll; - }()), + }() }, { key: 'getById', - value: (function () { - const _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) { + value: function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) { return _regenerator2.default.wrap(function _callee9$(_context9) { while (1) { switch (_context9.prev = _context9.next) { case 0: - return _context9.abrupt('return', this._fileManager.getFile(`${id}.json`)); + return _context9.abrupt('return', this._fileManager.getFile(id + '.json')); case 1: case 'end': @@ -436,11 +425,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return getById; - }()), + }() }, { key: 'getByUsername', - value: (function () { - const _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(username) { + value: function () { + var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(username) { return _regenerator2.default.wrap(function _callee10$(_context10) { while (1) { switch (_context10.prev = _context10.next) { @@ -468,20 +457,20 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return getByUsername; - }()), + }() // This isn't a good one to memoize as we can't key off user ID and there // isn't a good way to clear the cache. }, { key: 'deleteById', - value: (function () { - const _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) { + value: function () { + var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) { return _regenerator2.default.wrap(function _callee11$(_context11) { while (1) { switch (_context11.prev = _context11.next) { case 0: - this._fileManager.deleteFile(`${id}.json`); + this._fileManager.deleteFile(id + '.json'); case 1: case 'end': @@ -496,11 +485,11 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return deleteById; - }()), + }() }, { key: 'isUserNameInUse', - value: (function () { - const _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) { + value: function () { + var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) { return _regenerator2.default.wrap(function _callee12$(_context12) { while (1) { switch (_context12.prev = _context12.next) { @@ -528,8 +517,8 @@ const UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, } return isUserNameInUse; - }()), + }() }]); return UserFileRepository; -}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class)); -exports.default = UserFileRepository; +}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class)); +exports.default = UserFileRepository; \ No newline at end of file diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js index ed4263e6..c2a9da2c 100644 --- a/dist/repository/WebhookFileRepository.js +++ b/dist/repository/WebhookFileRepository.js @@ -1,56 +1,50 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); +var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor'); -const _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); +var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor); -const _extends2 = require('babel-runtime/helpers/extends'); +var _extends2 = require('babel-runtime/helpers/extends'); -const _extends3 = _interopRequireDefault(_extends2); +var _extends3 = _interopRequireDefault(_extends2); -const _regenerator = require('babel-runtime/regenerator'); +var _regenerator = require('babel-runtime/regenerator'); -const _regenerator2 = _interopRequireDefault(_regenerator); +var _regenerator2 = _interopRequireDefault(_regenerator); -const _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); -const _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); -const _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); -const _classCallCheck3 = _interopRequireDefault(_classCallCheck2); +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); -const _createClass2 = require('babel-runtime/helpers/createClass'); +var _createClass2 = require('babel-runtime/helpers/createClass'); -const _createClass3 = _interopRequireDefault(_createClass2); +var _createClass3 = _interopRequireDefault(_createClass2); -let _dec, - _dec2, - _dec3, - _dec4, - _desc, - _value, - _class; +var _dec, _dec2, _dec3, _dec4, _desc, _value, _class; -const _uuid = require('uuid'); +var _uuid = require('uuid'); -const _uuid2 = _interopRequireDefault(_uuid); +var _uuid2 = _interopRequireDefault(_uuid); -const _sparkProtocol = require('spark-protocol'); +var _sparkProtocol = require('spark-protocol'); -const _HttpError = require('../lib/HttpError'); +var _HttpError = require('../lib/HttpError'); -const _HttpError2 = _interopRequireDefault(_HttpError); +var _HttpError2 = _interopRequireDefault(_HttpError); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { - let desc = {}; - Object['ke' + 'ys'](descriptor).forEach((key) => { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; @@ -60,7 +54,9 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con desc.writable = true; } - desc = decorators.slice().reverse().reduce((desc, decorator) => decorator(target, property, desc) || desc, desc); + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; @@ -75,17 +71,17 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con return desc; } -const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), (_class = (function () { +var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), (_class = function () { function WebhookFileRepository(path) { - const _this = this; + var _this = this; (0, _classCallCheck3.default)(this, WebhookFileRepository); - this.getAll = (function () { - const _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { - const userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; - let allData; - return _regenerator2.default.wrap((_context) => { + this.getAll = function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() { + var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + var allData; + return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: @@ -100,7 +96,9 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = break; } - return _context.abrupt('return', allData.filter(webhook => webhook.ownerID === userID)); + return _context.abrupt('return', allData.filter(function (webhook) { + return webhook.ownerID === userID; + })); case 5: return _context.abrupt('return', allData); @@ -116,13 +114,13 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = return function () { return _ref.apply(this, arguments); }; - }()); + }(); - this.getById = (function () { - const _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) { - const userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; - let webhook; - return _regenerator2.default.wrap((_context2) => { + this.getById = function () { + var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) { + var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + var webhook; + return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: @@ -153,11 +151,11 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = return function (_x2) { return _ref2.apply(this, arguments); }; - }()); + }(); - this.update = (function () { - const _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { - return _regenerator2.default.wrap((_context3) => { + this.update = function () { + var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) { + return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: @@ -174,17 +172,16 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = return function (_x4) { return _ref3.apply(this, arguments); }; - }()); + }(); this._fileManager = new _sparkProtocol.JSONFileManager(path); } (0, _createClass3.default)(WebhookFileRepository, [{ key: 'create', - value: (function () { - const _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) { - let id, - modelToSave; + value: function () { + var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) { + var id, modelToSave; return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { @@ -193,7 +190,7 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = case 1: _context4.next = 3; - return this._fileManager.hasFile(`${id}.json`); + return this._fileManager.hasFile(id + '.json'); case 3: if (!_context4.sent) { @@ -208,11 +205,11 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = case 7: modelToSave = (0, _extends3.default)({}, model, { created_at: new Date(), - id, + id: id }); - this._fileManager.createFile(`${modelToSave.id}.json`, modelToSave); + this._fileManager.createFile(modelToSave.id + '.json', modelToSave); return _context4.abrupt('return', modelToSave); case 10: @@ -228,16 +225,16 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return create; - }()), + }() }, { key: 'deleteById', - value: (function () { - const _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) { + value: function () { + var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) { return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: - this._fileManager.deleteFile(`${id}.json`); + this._fileManager.deleteFile(id + '.json'); case 1: case 'end': @@ -252,14 +249,14 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return deleteById; - }()), + }() // eslint-disable-next-line no-unused-vars }, { key: '_getAll', - value: (function () { - const _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() { + value: function () { + var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() { return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { @@ -279,16 +276,16 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return _getAll; - }()), + }() }, { key: '_getByID', - value: (function () { - const _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) { + value: function () { + var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) { return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: - return _context7.abrupt('return', this._fileManager.getFile(`${id}.json`)); + return _context7.abrupt('return', this._fileManager.getFile(id + '.json')); case 1: case 'end': @@ -303,8 +300,8 @@ const WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = } return _getByID; - }()), + }() }]); return WebhookFileRepository; -}()), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class)); -exports.default = WebhookFileRepository; +}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class)); +exports.default = WebhookFileRepository; \ No newline at end of file diff --git a/dist/settings.js b/dist/settings.js index 9b53767c..fd63b211 100644 --- a/dist/settings.js +++ b/dist/settings.js @@ -1,12 +1,12 @@ +'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true, +Object.defineProperty(exports, "__esModule", { + value: true }); -const _path = require('path'); +var _path = require('path'); -const _path2 = _interopRequireDefault(_path); +var _path2 = _interopRequireDefault(_path); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -28,7 +28,7 @@ exports.default = { LOGIN_ROUTE: '/oauth/token', PORT: 5683, - HOST: 'localhost', + HOST: 'localhost' }; /** * Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. - https://www.spark.io/ * @@ -46,6 +46,6 @@ exports.default = { * * You can download the source here: https://github.com/spark/spark-server * + * * - * - */ + */ \ No newline at end of file diff --git a/dist/types.js b/dist/types.js index 8b137891..a726efc4 100644 --- a/dist/types.js +++ b/dist/types.js @@ -1 +1 @@ - +'use strict'; \ No newline at end of file diff --git a/src/RouteConfig.js b/src/RouteConfig.js index 61e408b1..b3f85d51 100644 --- a/src/RouteConfig.js +++ b/src/RouteConfig.js @@ -172,7 +172,8 @@ export default ( error: Error, request: $Request, response: $Response, - next: NextFunction, // eslint-disable-line no-unused-vars + // eslint-disable-next-line no-unused-vars + next: NextFunction, ) => { response .status(400) From 7641126d84ed45c4f07ebd6f0b2b304a4fc127de Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 18:39:47 -0800 Subject: [PATCH 343/504] Updating bindings to work with external settings. --- dist/defaultBindings.js | 11 ++++++++++- src/defaultBindings.js | 8 +++++++- test/setup/getDefaultContainer.js | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js index 8522b18a..fe3a8f7f 100644 --- a/dist/defaultBindings.js +++ b/dist/defaultBindings.js @@ -4,6 +4,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); +var _keys = require('babel-runtime/core-js/object/keys'); + +var _keys2 = _interopRequireDefault(_keys); + var _sparkProtocol = require('spark-protocol'); var _DeviceClaimsController = require('./controllers/DeviceClaimsController'); @@ -68,7 +72,12 @@ var _settings2 = _interopRequireDefault(_settings); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -exports.default = function (container) { +exports.default = function (container, newSettings) { + // Make sure that the spark-server settings match whatever is passed in + (0, _keys2.default)(newSettings).forEach(function (key) { + _settings2.default[key] = newSettings[key]; + }); + // spark protocol container bindings (0, _sparkProtocol.defaultBindings)(container); diff --git a/src/defaultBindings.js b/src/defaultBindings.js index 9ca61adc..89799bb0 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -1,6 +1,7 @@ // @flow import type { Container } from 'constitute'; +import type { Settings } from './types'; import { defaultBindings } from 'spark-protocol'; import DeviceClaimsController from './controllers/DeviceClaimsController'; @@ -19,7 +20,12 @@ import UserFileRepository from './repository/UserFileRepository'; import WebhookFileRepository from './repository/WebhookFileRepository'; import settings from './settings'; -export default (container: Container) => { +export default (container: Container, newSettings: Settings) => { + // Make sure that the spark-server settings match whatever is passed in + Object.keys(newSettings).forEach((key: string) => { + settings[key] = newSettings[key]; + }); + // spark protocol container bindings defaultBindings(container); diff --git a/test/setup/getDefaultContainer.js b/test/setup/getDefaultContainer.js index d03f5889..8eff6ee6 100644 --- a/test/setup/getDefaultContainer.js +++ b/test/setup/getDefaultContainer.js @@ -9,7 +9,7 @@ import DeviceServerMock from './DeviceServerMock'; const container = new Container(); // TODO - we should be creating different bindings per test so we can mock out // different modules to test -defaultBindings(container); +defaultBindings(container, settings); // settings container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); From 331ed83e4fde6a0364fde5a5595aa21c422674fd Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 19:18:45 -0800 Subject: [PATCH 344/504] Passing settings into spark protocol --- dist/defaultBindings.js | 2 +- src/defaultBindings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js index fe3a8f7f..fe4a0cd6 100644 --- a/dist/defaultBindings.js +++ b/dist/defaultBindings.js @@ -79,7 +79,7 @@ exports.default = function (container, newSettings) { }); // spark protocol container bindings - (0, _sparkProtocol.defaultBindings)(container); + (0, _sparkProtocol.defaultBindings)(container, newSettings); // settings container.bindValue('DEVICE_DIRECTORY', _settings2.default.DEVICE_DIRECTORY); diff --git a/src/defaultBindings.js b/src/defaultBindings.js index 89799bb0..2e5fad82 100644 --- a/src/defaultBindings.js +++ b/src/defaultBindings.js @@ -27,7 +27,7 @@ export default (container: Container, newSettings: Settings) => { }); // spark protocol container bindings - defaultBindings(container); + defaultBindings(container, newSettings); // settings container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY); From ede8f10656a9bb490216655ec2861619516f047b Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sat, 11 Feb 2017 19:29:32 -0800 Subject: [PATCH 345/504] More work with settings --- dist/main.js | 2 +- src/main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/main.js b/dist/main.js index bb0d541d..fa4fa388 100644 --- a/dist/main.js +++ b/dist/main.js @@ -55,7 +55,7 @@ process.on('uncaughtException', function (exception) { * See https://github.com/justmoon/constitute for more info */ var container = new _constitute.Container(); -(0, _defaultBindings2.default)(container); +(0, _defaultBindings2.default)(container, _settings2.default); var deviceServer = container.constitute('DeviceServer'); deviceServer.start(); diff --git a/src/main.js b/src/main.js index bf60f1bf..4bd6e050 100644 --- a/src/main.js +++ b/src/main.js @@ -30,7 +30,7 @@ process.on('uncaughtException', (exception: Error) => { * See https://github.com/justmoon/constitute for more info */ const container = new Container(); -defaultBindings(container); +defaultBindings(container, settings); const deviceServer = container.constitute('DeviceServer'); deviceServer.start(); From ce7bbc558e1dad62590919713e5d57c865f9a89c Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 12 Feb 2017 17:51:48 +0000 Subject: [PATCH 346/504] Update raspberryPi.md --- raspberryPi.md | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/raspberryPi.md b/raspberryPi.md index 763e945f..d70e5930 100644 --- a/raspberryPi.md +++ b/raspberryPi.md @@ -19,19 +19,9 @@ If you're already familiar with the command line, or you are comfortable setting # # Install Node.js - # - sudo apt-get install git htop rng-tools - wget http://node-arm.herokuapp.com/node_latest_armhf.deb - sudo dpkg -i node_latest_armhf.deb - - # - # - # - echo "Enabling hardware random number generator" - sudo modprobe bcm2708-rng - echo "add bcm2708-rng to /etc/modules" - echo "Now you have /dev/hwrng !" - sync + # [You can set up other versions here](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) + curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - + sudo apt-get install -y nodejs # @@ -47,11 +37,15 @@ If you're already familiar with the command line, or you are comfortable setting # - # Install the Spark-CLI + # Install the Particle-CLI # - sudo npm install -g spark-cli + sudo npm install -g particle-cli --unsafe-perm +After this you can follow the normal instructions for setting up the server. I had trouble getting `particle identify` working so I used `ssh` to get my server key and set up the device from my main computer. +https://www.raspberrypi.org/documentation/remote-access/ssh/ +If you want the node server to run whenever the Pi starts up, look into `pm2`: +https://github.com/Unitech/pm2 # # Setup a project folder # From b2478eebc5ac20d0121cd35fc3a44af90ce286d9 Mon Sep 17 00:00:00 2001 From: John Kalberer Date: Sun, 12 Feb 2017 17:52:24 +0000 Subject: [PATCH 347/504] Update raspberryPi.md --- raspberryPi.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/raspberryPi.md b/raspberryPi.md index d70e5930..c33fb60a 100644 --- a/raspberryPi.md +++ b/raspberryPi.md @@ -41,11 +41,6 @@ If you're already familiar with the command line, or you are comfortable setting # sudo npm install -g particle-cli --unsafe-perm -After this you can follow the normal instructions for setting up the server. I had trouble getting `particle identify` working so I used `ssh` to get my server key and set up the device from my main computer. -https://www.raspberrypi.org/documentation/remote-access/ssh/ - -If you want the node server to run whenever the Pi starts up, look into `pm2`: -https://github.com/Unitech/pm2 # # Setup a project folder # @@ -60,3 +55,9 @@ https://github.com/Unitech/pm2 npm install node main.js ``` + +After this you can follow the normal instructions for setting up the server. I had trouble getting `particle identify` working so I used `ssh` to get my server key and set up the device from my main computer. +https://www.raspberrypi.org/documentation/remote-access/ssh/ + +If you want the node server to run whenever the Pi starts up, look into `pm2`: +https://github.com/Unitech/pm2 From 63b3039cabe9916292f203df31ea5fc694b731bf Mon Sep 17 00:00:00 2001 From: David Greaves Date: Tue, 28 Feb 2017 10:20:59 +0000 Subject: [PATCH 348/504] Add Brewskey specific instructions and notes on babel. Signed-off-by: David Greaves --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 744fcfe4..ceb33aeb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ spark-server ============ -An API compatible open source server for interacting with devices speaking the [spark-protocol](https://github.com/spark/spark-protocol) +These instructions have been modified for the Brewskey clone of the local cloud :) + +An API compatible open source server for interacting with devices speaking the [spark-protocol](https://github.com/Brewskey/spark-protocol)
    __  __            __                 __        __                ____
@@ -14,16 +16,23 @@ An API compatible open source server for interacting with devices speaking the [
 
 Quick Install
 ==============
+
 ### You'll need to prepare your system for node-gyp. This is used in the URSA package.
 **https://github.com/nodejs/node-gyp**
 
 ```
-git clone https://github.com/spark/spark-server.git
+git clone https://github.com/Brewsky/spark-server.git
 cd spark-server/
 npm install
-node main.js
+./node_modules/.bin/babel src/ -d lib
+node lib/main.js
 ```
 
+The babel command pre-processes all the src/ to allow modern node
+syntax to be used in older versions of node. The modified code that is
+actually running lives in lib/
+If you change anything in src/ you'll need to rerun babel for changes
+to take effect.
  
 > **Windows Setup**  
 > You'll need to install Python 2.7 and OpenSSL 1.0.2 or older.  
@@ -40,7 +49,7 @@ How do I get started?
 1) Run the server with:
 
 ```
-node main.js
+node lib/main.js
 ```
 
 2) Watch for your IP address, you'll see something like:

From 074437eb333f83ba9b70a724da5bcf111ba84468 Mon Sep 17 00:00:00 2001
From: David Greaves 
Date: Tue, 28 Feb 2017 10:31:16 +0000
Subject: [PATCH 349/504] Additional documentation to help get started with
 using or migrating to the Brewskey clone.

Signed-off-by: David Greaves 
---
 README.md | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/README.md b/README.md
index ceb33aeb..4679ee79 100644
--- a/README.md
+++ b/README.md
@@ -110,9 +110,29 @@ You'll need to run `particle config particle`, `particle keys server cloud_publi
 At this point you should be able to run normal cloud commands and flash binaries.  You can add any webhooks you need, call functions, or get variable values.
 
 
+What's different to the original spark-server?
+==============================================
+
+The way the system stores data has changed.
+
+On first run the server creates the following directories for storing data about your local cloud:
+
+ - `data/`
+  - The cloud keys `default_key.pem` and `default_key.pub.pem` go directly in here. Previously these keys lived in the main directory.
+ - `data/deviceKeys/`
+  - Device keys (.pub.pem) and information (.json) for each device live in here. Previously these were found in `core_keys/`
+ - `data/users/`
+  - User account data (.json) for each user live in here. Previously stored in `users/`
+ - `data/knownApps/`
+  - ???
+ - `data/webhooks/`
+  - ???
+
 What kind of project is this?
 ======================================
 
+This is a refactored clone of the original spark-server because this is what the awesome guys at Particle.io said:
+
 We're open sourcing our core Spark-protocol code to help you build awesome stuff with your Spark Core, and with your
 other projects.  We're also open sourcing an example server with the same easy to use Rest API as the Spark Cloud, so
 your local apps can easily run against both the Spark Cloud, and your local cloud.  We're really excited about this,
@@ -125,6 +145,8 @@ We'll keep improving and adding features, and we hope you'll want to join in too
 What features are currently present
 ====================================
 
+This feature list is not correct wrt the Brewskey clone - there's much more!
+
 The spark-server module aims to provide a HTTP rest interface that is API compatible with the main Spark Cloud.  Ideally any
 programs you write to run against the Spark Cloud should also work on the Local Cloud.  Some features aren't here yet, but may be
 coming down the road, right now the endpoints exposed are:

From e24692f1e0d332af3f4e4e09e2ec318995f028c8 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Wed, 1 Mar 2017 19:48:04 -0800
Subject: [PATCH 350/504] Working on integrating the electron

---
 dist/controllers/ProvisioningController.js |  2 +-
 dist/managers/DeviceManager.js             | 52 +++++++++++++---------
 package.json                               |  4 +-
 src/controllers/ProvisioningController.js  |  8 +++-
 src/managers/DeviceManager.js              |  5 +++
 5 files changed, 46 insertions(+), 25 deletions(-)

diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js
index 88ffdb80..62a3eabd 100644
--- a/dist/controllers/ProvisioningController.js
+++ b/dist/controllers/ProvisioningController.js
@@ -119,7 +119,7 @@ var ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
 
               case 2:
                 _context.next = 4;
-                return this._deviceManager.provision(coreID, this.user.id, postBody.publicKey);
+                return this._deviceManager.provision(coreID, this.user.id, postBody.publicKey, postBody.alogrithm);
 
               case 4:
                 device = _context.sent;
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 15a888ea..ad4ca1d4 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -473,40 +473,48 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.provision = function () {
-    var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey) {
+    var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey, algorithm) {
       var createdKey, existingAttributes, attributes;
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
             case 0:
-              _context11.prev = 0;
+              if (!(algorithm === 'ecc')) {
+                _context11.next = 2;
+                break;
+              }
+
+              return _context11.abrupt('return', null);
+
+            case 2:
+              _context11.prev = 2;
               createdKey = _ursa2.default.createPublicKey(publicKey);
 
               if (_ursa2.default.isPublicKey(createdKey)) {
-                _context11.next = 4;
+                _context11.next = 6;
                 break;
               }
 
               throw new _HttpError2.default('Not a public key');
 
-            case 4:
-              _context11.next = 9;
+            case 6:
+              _context11.next = 11;
               break;
 
-            case 6:
-              _context11.prev = 6;
-              _context11.t0 = _context11['catch'](0);
+            case 8:
+              _context11.prev = 8;
+              _context11.t0 = _context11['catch'](2);
               throw new _HttpError2.default('Key error ' + _context11.t0);
 
-            case 9:
-              _context11.next = 11;
-              return _this._deviceKeyRepository.update(deviceID, publicKey);
-
             case 11:
               _context11.next = 13;
-              return _this._deviceAttributeRepository.getById(deviceID);
+              return _this._deviceKeyRepository.update(deviceID, publicKey);
 
             case 13:
+              _context11.next = 15;
+              return _this._deviceAttributeRepository.getById(deviceID);
+
+            case 15:
               existingAttributes = _context11.sent;
               attributes = (0, _extends3.default)({
                 deviceID: deviceID
@@ -515,25 +523,25 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
                 registrar: userID,
                 timestamp: new Date()
               });
-              _context11.next = 17;
+              _context11.next = 19;
               return _this._deviceAttributeRepository.update(attributes);
 
-            case 17:
-              _context11.next = 19;
+            case 19:
+              _context11.next = 21;
               return _this.getByID(deviceID, userID);
 
-            case 19:
+            case 21:
               return _context11.abrupt('return', _context11.sent);
 
-            case 20:
+            case 22:
             case 'end':
               return _context11.stop();
           }
         }
-      }, _callee11, _this, [[0, 6]]);
+      }, _callee11, _this, [[2, 8]]);
     }));
 
-    return function (_x23, _x24, _x25) {
+    return function (_x23, _x24, _x25, _x26) {
       return _ref13.apply(this, arguments);
     };
   }();
@@ -581,7 +589,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee12, _this);
     }));
 
-    return function (_x26, _x27, _x28) {
+    return function (_x27, _x28, _x29) {
       return _ref14.apply(this, arguments);
     };
   }();
@@ -624,7 +632,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee13, _this);
     }));
 
-    return function (_x29, _x30, _x31) {
+    return function (_x30, _x31, _x32) {
       return _ref15.apply(this, arguments);
     };
   }();
diff --git a/package.json b/package.json
index 4621da23..c76a982c 100644
--- a/package.json
+++ b/package.json
@@ -31,11 +31,13 @@
     "build:watch": "babel ./src --out-dir ./dist --watch",
     "build:clean": "rimraf ./build",
     "lint": "eslint --fix --max-warnings 0 -- .",
+    "postinstall": "npm run update-firmware",
     "prebuild": "npm run build:clean",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
     "start:prod": "npm run build && node ./build/main.js",
     "test": "ava --serial",
-    "test:watch": "ava --watch --serial"
+    "test:watch": "ava --watch --serial",
+    "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries"
   },
   "pre-commit": [
     "lint",
diff --git a/src/controllers/ProvisioningController.js b/src/controllers/ProvisioningController.js
index 4a80bf84..3f4a94c4 100644
--- a/src/controllers/ProvisioningController.js
+++ b/src/controllers/ProvisioningController.js
@@ -21,7 +21,12 @@ class ProvisioningController extends Controller {
   @route('/v1/provisioning/:coreID')
   async provision(
     coreID: string,
-    postBody: { publicKey: string },
+    postBody: {
+      alogrithm: 'ecc' | 'rsa',
+      filename: 'cli',
+      order: string, // not sure what this is used for
+      publicKey: string,
+    },
   ): Promise<*> {
     if (!postBody.publicKey) {
       throw new HttpError('No key provided');
@@ -31,6 +36,7 @@ class ProvisioningController extends Controller {
       coreID,
       this.user.id,
       postBody.publicKey,
+      postBody.alogrithm,
     );
 
     return this.ok(deviceToAPI(device));
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index c3c84b80..2103ee99 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -224,7 +224,12 @@ class DeviceManager {
     deviceID: string,
     userID: string,
     publicKey: string,
+    algorithm: 'ecc' | 'rsa',
   ): Promise<*> => {
+    if (algorithm === 'ecc') {
+      return null;
+    }
+
     try {
       const createdKey = ursa.createPublicKey(publicKey);
       if (!ursa.isPublicKey(createdKey)) {

From 3ac1e528ec3b9277a2a98aa610bf6c58c77a2f07 Mon Sep 17 00:00:00 2001
From: Anton Puko 
Date: Thu, 9 Mar 2017 17:22:19 +0200
Subject: [PATCH 351/504] fix settings. add tcp_server_config:
 ENABLE_SYSTEM_FIRMWARE_AUTOUPDATES

---
 src/main.js            |  2 +-
 src/settings.js        | 10 ++++++++--
 src/types.js           | 10 ++++++++--
 test/setup/settings.js | 10 ++++++++--
 4 files changed, 25 insertions(+), 7 deletions(-)

diff --git a/src/main.js b/src/main.js
index 4bd6e050..26fe9211 100644
--- a/src/main.js
+++ b/src/main.js
@@ -8,7 +8,7 @@ import createApp from './app';
 import defaultBindings from './defaultBindings';
 import settings from './settings';
 
-const NODE_PORT = process.env.NODE_PORT || 8080;
+const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
 
 process.on('uncaughtException', (exception: Error) => {
   logger.error(
diff --git a/src/settings.js b/src/settings.js
index 86d4633e..88cc6ff9 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -38,6 +38,12 @@ export default {
   LOG_REQUESTS: true,
   LOGIN_ROUTE: '/oauth/token',
 
-  PORT: 5683,
-  HOST: 'localhost',
+  EXPRESS_SERVER_CONFIG: {
+    PORT: 8080,
+  },
+  TCP_DEVICE_SERVER_CONFIG: {
+    ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
+    HOST: 'localhost',
+    PORT: 5683,
+  },
 };
diff --git a/src/types.js b/src/types.js
index 06a196ef..cf322eda 100644
--- a/src/types.js
+++ b/src/types.js
@@ -141,14 +141,20 @@ export type Settings = {
   BUILD_DIRECTORY: string,
   CRYPTO_SALT: string,
   DEVICE_DIRECTORY: string,
+  EXPRESS_SERVER_CONFIG: {
+    PORT: number,
+  },
   FIRMWARE_DIRECTORY: string,
   FIRMWARE_REPOSITORY_DIRECTORY: string,
-  HOST: string,
   LOG_REQUESTS: boolean,
   LOGIN_ROUTE: string,
-  PORT: number,
   SERVER_KEY_FILENAME: string,
   SERVER_KEYS_DIRECTORY: string,
+  TCP_DEVICE_SERVER_CONFIG: {
+    ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: boolean,
+    HOST: string,
+    PORT: number,
+  },
   USERS_DIRECTORY: string,
   WEBHOOKS_DIRECTORY: string,
 };
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 5d2a3b60..0beef756 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -20,6 +20,12 @@ export default {
   LOG_REQUESTS: false,
   LOGIN_ROUTE: '/oauth/token',
 
-  PORT: 5683,
-  HOST: 'localhost',
+  EXPRESS_SERVER_CONFIG: {
+    PORT: 8080,
+  },
+  TCP_DEVICE_SERVER_CONFIG: {
+    ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
+    HOST: 'localhost',
+    PORT: 5683,
+  },
 };

From eff5cf9f28b9a23e8040fcd13f8b1694efa69561 Mon Sep 17 00:00:00 2001
From: Anton Puko 
Date: Thu, 9 Mar 2017 18:13:50 +0200
Subject: [PATCH 352/504] move ENABLE_SYSTEM_FIRMWARE_AUTOUPDATES outside tcp
 server config

---
 src/settings.js        | 2 +-
 src/types.js           | 2 +-
 test/setup/settings.js | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/settings.js b/src/settings.js
index 88cc6ff9..1a6b1337 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -25,6 +25,7 @@ import path from 'path';
 export default {
   BUILD_DIRECTORY: path.join(__dirname, '../data/build'),
   DEVICE_DIRECTORY: path.join(__dirname, '../data/deviceKeys'),
+  ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: path.join(__dirname, '../data/knownApps'),
   FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../../spark-firmware'),
   SERVER_KEY_FILENAME: 'default_key.pem',
@@ -42,7 +43,6 @@ export default {
     PORT: 8080,
   },
   TCP_DEVICE_SERVER_CONFIG: {
-    ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
     HOST: 'localhost',
     PORT: 5683,
   },
diff --git a/src/types.js b/src/types.js
index cf322eda..adcbc93a 100644
--- a/src/types.js
+++ b/src/types.js
@@ -141,6 +141,7 @@ export type Settings = {
   BUILD_DIRECTORY: string,
   CRYPTO_SALT: string,
   DEVICE_DIRECTORY: string,
+  ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: boolean,
   EXPRESS_SERVER_CONFIG: {
     PORT: number,
   },
@@ -151,7 +152,6 @@ export type Settings = {
   SERVER_KEY_FILENAME: string,
   SERVER_KEYS_DIRECTORY: string,
   TCP_DEVICE_SERVER_CONFIG: {
-    ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: boolean,
     HOST: string,
     PORT: number,
   },
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 0beef756..32ddd6cb 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -7,6 +7,7 @@ export default {
   BUILD_DIRECTORY: path.join(__dirname, '../__test_data__/build'),
   CUSTOM_FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__'),
   DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'),
+  ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'),
   FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../__test_data__/firmware'),
   SERVER_KEY_FILENAME: 'default_key.pem',
@@ -24,7 +25,6 @@ export default {
     PORT: 8080,
   },
   TCP_DEVICE_SERVER_CONFIG: {
-    ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
     HOST: 'localhost',
     PORT: 5683,
   },

From 1c4fd113591c66029bec904fafece70e827cf094 Mon Sep 17 00:00:00 2001
From: Anton Puko 
Date: Tue, 14 Mar 2017 14:02:17 +0200
Subject: [PATCH 353/504] transform webhookResponse buffer chunks to string

---
 dist/managers/WebhookManager.js |  2 +-
 dist/settings.js                | 10 ++++++++--
 src/managers/WebhookManager.js  |  2 +-
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index d9e4bb9f..ce33333d 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -364,7 +364,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher)
                           var responseEventName = responseTopic && responseTopic + '/' + index || 'hook-response/' + event.name + '/' + index;
 
                           _this._eventPublisher.publish({
-                            data: chunk,
+                            data: chunk.toString(),
                             isPublic: false,
                             name: responseEventName,
                             userID: event.userID
diff --git a/dist/settings.js b/dist/settings.js
index fd63b211..bc69825a 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -14,6 +14,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 exports.default = {
   BUILD_DIRECTORY: _path2.default.join(__dirname, '../data/build'),
   DEVICE_DIRECTORY: _path2.default.join(__dirname, '../data/deviceKeys'),
+  ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: _path2.default.join(__dirname, '../data/knownApps'),
   FIRMWARE_REPOSITORY_DIRECTORY: _path2.default.join(__dirname, '../../spark-firmware'),
   SERVER_KEY_FILENAME: 'default_key.pem',
@@ -27,8 +28,13 @@ exports.default = {
   LOG_REQUESTS: true,
   LOGIN_ROUTE: '/oauth/token',
 
-  PORT: 5683,
-  HOST: 'localhost'
+  EXPRESS_SERVER_CONFIG: {
+    PORT: 8080
+  },
+  TCP_DEVICE_SERVER_CONFIG: {
+    HOST: 'localhost',
+    PORT: 5683
+  }
 }; /**
    *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
    *
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 2cdab72f..98b9b6dd 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -257,7 +257,7 @@ class WebhookManager {
           `hook-response/${event.name}/${index}`;
 
         this._eventPublisher.publish({
-          data: chunk,
+          data: chunk.toString(),
           isPublic: false,
           name: responseEventName,
           userID: event.userID,

From 1bbc311de0c4a14a756f4f480720570297ae1e45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20Suhajda?= 
Date: Sat, 18 Mar 2017 00:53:00 +0100
Subject: [PATCH 354/504] fix typo in install instructions

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 4679ee79..d0ae8931 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Quick Install
 **https://github.com/nodejs/node-gyp**
 
 ```
-git clone https://github.com/Brewsky/spark-server.git
+git clone https://github.com/Brewskey/spark-server.git
 cd spark-server/
 npm install
 ./node_modules/.bin/babel src/ -d lib

From 2b9079b4c94e6b01d6cc5e4ed6111ce221514d03 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 29 Mar 2017 20:50:57 +0200
Subject: [PATCH 355/504] fix route config to prevent sent headers after submit
 error on particle subscribe calls

---
 dist/RouteConfig.js | 48 +++++++++++++++++++++++++++++++--------------
 dist/main.js        |  2 +-
 src/RouteConfig.js  | 23 +++++++++++-----------
 3 files changed, 46 insertions(+), 27 deletions(-)

diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js
index 166e71eb..1ebec26b 100644
--- a/dist/RouteConfig.js
+++ b/dist/RouteConfig.js
@@ -161,47 +161,65 @@ exports.default = function (app, container, controllers, settings) {
                   functionResult = mappedFunction.call.apply(mappedFunction, [controllerInstance].concat((0, _toConsumableArray3.default)(values), [body]));
 
                   if (!functionResult.then) {
+                    _context.next = 24;
+                    break;
+                  }
+
+                  if (serverSentEvents) {
                     _context.next = 17;
                     break;
                   }
 
-                  _context.next = 13;
-                  return _promise2.default.race([functionResult, !serverSentEvents ? new _promise2.default(function (resolve, reject) {
+                  _context.next = 14;
+                  return _promise2.default.race([functionResult, new _promise2.default(function (resolve, reject) {
                     return setTimeout(function () {
                       return reject(new Error('timeout'));
                     }, settings.API_TIMEOUT);
-                  }) : null]);
+                  })]);
+
+                case 14:
+                  _context.t0 = _context.sent;
+                  _context.next = 20;
+                  break;
+
+                case 17:
+                  _context.next = 19;
+                  return functionResult;
+
+                case 19:
+                  _context.t0 = _context.sent;
+
+                case 20:
+                  result = _context.t0;
 
-                case 13:
-                  result = _context.sent;
 
                   response.status((0, _nullthrows2.default)(result).status).json((0, _nullthrows2.default)(result).data);
-                  _context.next = 18;
+                  _context.next = 25;
                   break;
 
-                case 17:
+                case 24:
                   response.status(functionResult.status).json(functionResult.data);
 
-                case 18:
-                  _context.next = 24;
+                case 25:
+                  _context.next = 31;
                   break;
 
-                case 20:
-                  _context.prev = 20;
-                  _context.t0 = _context['catch'](8);
-                  httpError = new _HttpError2.default(_context.t0);
+                case 27:
+                  _context.prev = 27;
+                  _context.t1 = _context['catch'](8);
+                  httpError = new _HttpError2.default(_context.t1);
 
                   response.status(httpError.status).json({
                     error: httpError.message,
                     ok: false
                   });
 
-                case 24:
+                case 31:
                 case 'end':
                   return _context.stop();
               }
             }
-          }, _callee, undefined, [[8, 20]]);
+          }, _callee, undefined, [[8, 27]]);
         }));
 
         return function (_x2, _x3) {
diff --git a/dist/main.js b/dist/main.js
index fa4fa388..23a989b6 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -36,7 +36,7 @@ var _settings2 = _interopRequireDefault(_settings);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var NODE_PORT = process.env.NODE_PORT || 8080;
+var NODE_PORT = process.env.NODE_PORT || _settings2.default.EXPRESS_SERVER_CONFIG.PORT;
 
 process.on('uncaughtException', function (exception) {
   _logger2.default.error('uncaughtException', { message: exception.message, stack: exception.stack }); // logging with MetaData
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index b3f85d51..3d160c5f 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -135,18 +135,19 @@ export default (
             );
 
             if (functionResult.then) {
-              const result = await Promise.race([
+              const result = !serverSentEvents
+              ? await Promise.race([
                 functionResult,
-                !serverSentEvents
-                  ? new Promise(
-                    (resolve: () => void, reject: () => void): number =>
-                      setTimeout(
-                        (): void => reject(new Error('timeout')),
-                        settings.API_TIMEOUT,
-                      ),
-                  )
-                  : null,
-              ]);
+                new Promise(
+                  (resolve: () => void, reject: () => void): number =>
+                    setTimeout(
+                      (): void => reject(new Error('timeout')),
+                      settings.API_TIMEOUT,
+                    ),
+                ),
+              ])
+              : await functionResult;
+
               response
                 .status(nullthrows(result).status)
                 .json(nullthrows(result).data);

From b4dbea7e3acdea6b377ec4f8762a6a1727ccc78d Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 29 Mar 2017 20:56:38 +0200
Subject: [PATCH 356/504] fix tab

---
 src/RouteConfig.js | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 3d160c5f..6364407f 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -136,17 +136,17 @@ export default (
 
             if (functionResult.then) {
               const result = !serverSentEvents
-              ? await Promise.race([
-                functionResult,
-                new Promise(
-                  (resolve: () => void, reject: () => void): number =>
-                    setTimeout(
-                      (): void => reject(new Error('timeout')),
-                      settings.API_TIMEOUT,
-                    ),
-                ),
-              ])
-              : await functionResult;
+                ? await Promise.race([
+                  functionResult,
+                  new Promise(
+                    (resolve: () => void, reject: () => void): number =>
+                      setTimeout(
+                        (): void => reject(new Error('timeout')),
+                        settings.API_TIMEOUT,
+                      ),
+                  ),
+                ])
+                : await functionResult;
 
               response
                 .status(nullthrows(result).status)

From 3aa23c506eeef8cbdfec8fc87886e1bd45b22785 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Tue, 4 Apr 2017 08:04:21 -0700
Subject: [PATCH 357/504] Updated start:prod to use the correct folder.

---
 package.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index c76a982c..8fbf4dd1 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "postinstall": "npm run update-firmware",
     "prebuild": "npm run build:clean",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
-    "start:prod": "npm run build && node ./build/main.js",
+    "start:prod": "npm run build && node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries"
@@ -100,7 +100,7 @@
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-sorting": "^0.3.0",
     "flow-bin": "^0.37.0",
-    "nodemon": "^1.11.0",
+    "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
     "rimraf": "^2.5.4",
     "sinon": "^1.17.7",

From 76efd9a158db01896f9e9c9c3875a19556f4abde Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Mon, 1 May 2017 08:46:03 -0700
Subject: [PATCH 358/504] Fixed #134

---
 dist/managers/WebhookManager.js | 180 ++++++++++++++------------------
 src/managers/WebhookManager.js  |  15 ++-
 test/WebhookManager.test.js     |  30 ++----
 3 files changed, 93 insertions(+), 132 deletions(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index ce33333d..e8217282 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -4,18 +4,10 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _extends2 = require('babel-runtime/helpers/extends');
-
-var _extends3 = _interopRequireDefault(_extends2);
-
 var _promise = require('babel-runtime/core-js/promise');
 
 var _promise2 = _interopRequireDefault(_promise);
 
-var _typeof2 = require('babel-runtime/helpers/typeof');
-
-var _typeof3 = _interopRequireDefault(_typeof2);
-
 var _stringify = require('babel-runtime/core-js/json/stringify');
 
 var _stringify2 = _interopRequireDefault(_stringify);
@@ -24,6 +16,10 @@ var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
@@ -98,6 +94,10 @@ var WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min;
 var MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512;
 var MAX_RESPONSE_MESSAGE_SIZE = 100000;
 
+var WEBHOOK_DEFAULTS = {
+  rejectUnauthorized: true
+};
+
 var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) {
   var _this = this;
 
@@ -113,7 +113,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher)
           switch (_context.prev = _context.next) {
             case 0:
               _context.next = 2;
-              return _this._webhookRepository.create(model);
+              return _this._webhookRepository.create((0, _extends3.default)({}, WEBHOOK_DEFAULTS, model));
 
             case 2:
               webhook = _context.sent;
@@ -303,108 +303,80 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher)
   };
 
   this.runWebhook = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(webhook, event) {
-      var _ret;
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(webhook, event) {
+      var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, isJsonRequest, requestOptions, _responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks;
 
-      return _regenerator2.default.wrap(function _callee7$(_context7) {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
-          switch (_context7.prev = _context7.next) {
+          switch (_context6.prev = _context6.next) {
             case 0:
-              _context7.prev = 0;
-              return _context7.delegateYield(_regenerator2.default.mark(function _callee6() {
-                var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, isJsonRequest, requestOptions, responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks;
-                return _regenerator2.default.wrap(function _callee6$(_context6) {
-                  while (1) {
-                    switch (_context6.prev = _context6.next) {
-                      case 0:
-                        webhookVariablesObject = _this._getEventVariables(event);
-                        requestAuth = _this._compileJsonTemplate(webhook.auth, webhookVariablesObject);
-                        requestJson = _this._compileJsonTemplate(webhook.json, webhookVariablesObject);
-                        requestFormData = _this._compileJsonTemplate(webhook.form, webhookVariablesObject);
-                        requestHeaders = _this._compileJsonTemplate(webhook.headers, webhookVariablesObject);
-                        requestUrl = _this._compileTemplate(webhook.url, webhookVariablesObject);
-                        requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject);
-                        responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject);
-                        requestType = _this._compileTemplate(webhook.requestType, webhookVariablesObject);
-                        isJsonRequest = !!requestJson;
-                        requestOptions = {
-                          auth: requestAuth,
-                          body: isJsonRequest ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
-                          form: !isJsonRequest ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined,
-                          headers: requestHeaders,
-                          json: true,
-                          method: validateRequestType((0, _nullthrows2.default)(requestType)),
-                          qs: requestQuery,
-                          strictSSL: webhook.rejectUnauthorized,
-                          url: (0, _nullthrows2.default)(requestUrl)
-                        };
-                        _context6.next = 13;
-                        return _this._callWebhook(webhook, event, requestOptions);
-
-                      case 13:
-                        responseBody = _context6.sent;
-
-                        if (responseBody) {
-                          _context6.next = 16;
-                          break;
-                        }
-
-                        return _context6.abrupt('return', {
-                          v: void 0
-                        });
-
-                      case 16:
-                        isResponseBodyAnObject = responseBody === Object(responseBody);
-                        responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(responseBody);
-                        responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(responseBody) : responseBody);
-                        chunks = splitBufferIntoChunks(Buffer.from(responseEventData).slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE);
-
-
-                        chunks.forEach(function (chunk, index) {
-                          var responseEventName = responseTopic && responseTopic + '/' + index || 'hook-response/' + event.name + '/' + index;
-
-                          _this._eventPublisher.publish({
-                            data: chunk.toString(),
-                            isPublic: false,
-                            name: responseEventName,
-                            userID: event.userID
-                          });
-                        });
-
-                      case 21:
-                      case 'end':
-                        return _context6.stop();
-                    }
-                  }
-                }, _callee6, _this);
-              })(), 't0', 2);
-
-            case 2:
-              _ret = _context7.t0;
-
-              if (!((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === "object")) {
-                _context7.next = 5;
+              _context6.prev = 0;
+              webhookVariablesObject = _this._getEventVariables(event);
+              requestAuth = _this._compileJsonTemplate(webhook.auth, webhookVariablesObject);
+              requestJson = _this._compileJsonTemplate(webhook.json, webhookVariablesObject);
+              requestFormData = _this._compileJsonTemplate(webhook.form, webhookVariablesObject);
+              requestHeaders = _this._compileJsonTemplate(webhook.headers, webhookVariablesObject);
+              requestUrl = _this._compileTemplate(webhook.url, webhookVariablesObject);
+              requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject);
+              responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject);
+              requestType = _this._compileTemplate(webhook.requestType, webhookVariablesObject);
+              isJsonRequest = !!requestJson;
+              requestOptions = {
+                auth: requestAuth,
+                body: isJsonRequest && requestJson ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
+                form: !isJsonRequest && requestFormData ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined,
+                headers: requestHeaders,
+                json: true,
+                method: validateRequestType((0, _nullthrows2.default)(requestType)),
+                qs: requestQuery,
+                strictSSL: webhook.rejectUnauthorized,
+                url: (0, _nullthrows2.default)(requestUrl)
+              };
+              _context6.next = 14;
+              return _this._callWebhook(webhook, event, requestOptions);
+
+            case 14:
+              _responseBody = _context6.sent;
+
+              if (_responseBody) {
+                _context6.next = 17;
                 break;
               }
 
-              return _context7.abrupt('return', _ret.v);
+              return _context6.abrupt('return');
 
-            case 5:
-              _context7.next = 10;
+            case 17:
+              isResponseBodyAnObject = _responseBody === Object(_responseBody);
+              responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(_responseBody);
+              responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(_responseBody) : _responseBody);
+              chunks = splitBufferIntoChunks(Buffer.from(responseEventData).slice(0, MAX_RESPONSE_MESSAGE_SIZE), MAX_RESPONSE_MESSAGE_CHUNK_SIZE);
+
+
+              chunks.forEach(function (chunk, index) {
+                var responseEventName = responseTopic && responseTopic + '/' + index || 'hook-response/' + event.name + '/' + index;
+
+                _this._eventPublisher.publish({
+                  data: chunk.toString(),
+                  isPublic: false,
+                  name: responseEventName,
+                  userID: event.userID
+                });
+              });
+              _context6.next = 27;
               break;
 
-            case 7:
-              _context7.prev = 7;
-              _context7.t1 = _context7['catch'](0);
+            case 24:
+              _context6.prev = 24;
+              _context6.t0 = _context6['catch'](0);
 
-              _logger2.default.error('webhookError: ' + _context7.t1);
+              _logger2.default.error('webhookError: ' + _context6.t0);
 
-            case 10:
+            case 27:
             case 'end':
-              return _context7.stop();
+              return _context6.stop();
           }
         }
-      }, _callee7, _this, [[0, 7]]);
+      }, _callee6, _this, [[0, 24]]);
     }));
 
     return function (_x7, _x8) {
@@ -515,23 +487,23 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher)
   this._webhookRepository = webhookRepository;
   this._eventPublisher = eventPublisher;
 
-  (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() {
-    return _regenerator2.default.wrap(function _callee8$(_context8) {
+  (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
+    return _regenerator2.default.wrap(function _callee7$(_context7) {
       while (1) {
-        switch (_context8.prev = _context8.next) {
+        switch (_context7.prev = _context7.next) {
           case 0:
-            _context8.next = 2;
+            _context7.next = 2;
             return _this._init();
 
           case 2:
-            return _context8.abrupt('return', _context8.sent);
+            return _context7.abrupt('return', _context7.sent);
 
           case 3:
           case 'end':
-            return _context8.stop();
+            return _context7.stop();
         }
       }
-    }, _callee8, _this);
+    }, _callee7, _this);
   }))();
 };
 
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 98b9b6dd..1316fe8d 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -58,6 +58,10 @@ const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min;
 const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512;
 const MAX_RESPONSE_MESSAGE_SIZE = 100000;
 
+const WEBHOOK_DEFAULTS = {
+  rejectUnauthorized: true,
+};
+
 class WebhookManager {
   _eventPublisher: EventPublisher;
   _subscriptionIDsByWebhookID: Map = new Map();
@@ -75,7 +79,10 @@ class WebhookManager {
   }
 
   create = async (model: WebhookMutator): Promise => {
-    const webhook = await this._webhookRepository.create(model);
+    const webhook = await this._webhookRepository.create({
+      ...WEBHOOK_DEFAULTS,
+      ...model,
+    });
     this._subscribeWebhook(webhook);
     return webhook;
   };
@@ -206,13 +213,13 @@ class WebhookManager {
         webhookVariablesObject,
       );
 
-      const isJsonRequest = !!requestJson;
+      const isJsonRequest = !!requestJson || !requestFormData;
       const requestOptions = {
         auth: (requestAuth: any),
-        body: isJsonRequest
+        body: isJsonRequest && requestJson
           ? this._getRequestData(requestJson, event, webhook.noDefaults)
           : undefined,
-        form: !isJsonRequest
+        form: !isJsonRequest && requestFormData
           ? this._getRequestData(requestFormData, event, webhook.noDefaults)
           : undefined,
         headers: requestHeaders,
diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js
index 3470570b..e6561cd2 100644
--- a/test/WebhookManager.test.js
+++ b/test/WebhookManager.test.js
@@ -57,10 +57,7 @@ test(
     ) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(
-        JSON.stringify(requestOptions.form),
-        JSON.stringify(defaultRequestData),
-      );
+      t.is(requestOptions.form, undefined);
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
@@ -207,10 +204,7 @@ test(
         }),
       );
       t.is(requestOptions.body, undefined);
-      t.is(
-        JSON.stringify(requestOptions.form),
-        JSON.stringify(defaultRequestData),
-      );
+      t.is(requestOptions.form, undefined);
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
@@ -243,10 +237,7 @@ test(
     ) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(
-        JSON.stringify(requestOptions.form),
-        JSON.stringify(defaultRequestData),
-      );
+      t.is(requestOptions.form, undefined);
       t.is(
         JSON.stringify(requestOptions.headers),
         JSON.stringify({
@@ -282,10 +273,7 @@ test(
     ) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(
-        JSON.stringify(requestOptions.form),
-        JSON.stringify(defaultRequestData),
-      );
+      t.is(requestOptions.form, undefined);
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
@@ -319,10 +307,7 @@ test(
     ) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(
-        JSON.stringify(requestOptions.form),
-        JSON.stringify(defaultRequestData),
-      );
+      t.is(requestOptions.form, undefined);
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(
@@ -356,10 +341,7 @@ test(
     ) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(
-        JSON.stringify(requestOptions.form),
-        JSON.stringify(defaultRequestData),
-      );
+      t.is(requestOptions.form, undefined);
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, 'POST');
       t.is(requestOptions.url, WEBHOOK_BASE.url);

From 1974581caac41fa8448f26da6d6d1fc0966eb865 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 5 May 2017 01:43:17 +0200
Subject: [PATCH 359/504] add ssl support

---
 src/main.js            | 35 ++++++++++++++++++++++++++++-------
 src/settings.js        |  5 +++--
 src/types.js           |  3 +++
 test/setup/settings.js |  3 +++
 4 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/src/main.js b/src/main.js
index 26fe9211..25b65c45 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,12 +1,16 @@
 // @flow
 
-import { Container } from 'constitute';
-import os from 'os';
 import arrayFlatten from 'array-flatten';
-import logger from './lib/logger';
 import createApp from './app';
+import nulltrhows from 'nullthrows';
+import fs from 'fs';
+import http from 'http';
+import https from 'https';
+import os from 'os';
 import defaultBindings from './defaultBindings';
+import logger from './lib/logger';
 import settings from './settings';
+import { Container } from 'constitute';
 
 const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
 
@@ -37,10 +41,27 @@ deviceServer.start();
 
 const app = createApp(container, settings);
 
-app.listen(
-  NODE_PORT,
-  (): void => console.log(`express server started on port ${NODE_PORT}`),
-);
+const onServerStartListen = (): void =>
+  console.log(`express server started on port ${NODE_PORT}`);
+
+if (settings.EXPRESS_SERVER_CONFIG.USE_SSL) {
+  const options = {
+    cert: fs.readFileSync(
+      nulltrhows(settings.EXPRESS_SERVER_CONFIG.SSL_CERTIFICATE_FILEPATH),
+    ),
+    key: fs.readFileSync(
+      nulltrhows(settings.EXPRESS_SERVER_CONFIG.SSL_PRIVATE_KEY_FILEPATH),
+    ),
+  };
+
+  https
+    .createServer(options, (app: any))
+    .listen(NODE_PORT, onServerStartListen);
+} else {
+  http
+    .createServer((app: any))
+    .listen(NODE_PORT, onServerStartListen);
+}
 
 const addresses = arrayFlatten(
   Object.entries(os.networkInterfaces()).map(
diff --git a/src/settings.js b/src/settings.js
index 1a6b1337..721b6ac1 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -32,15 +32,16 @@ export default {
   SERVER_KEYS_DIRECTORY: path.join(__dirname, '../data'),
   USERS_DIRECTORY: path.join(__dirname, '../data/users'),
   WEBHOOKS_DIRECTORY: path.join(__dirname, '../data/webhooks'),
-
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_SALT: 'aes-128-cbc',
   LOG_REQUESTS: true,
   LOGIN_ROUTE: '/oauth/token',
-
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,
+    SSL_CERTIFICATE_FILEPATH: null,
+    SSL_PRIVATE_KEY_FILEPATH: null,
+    USE_SSL: false,
   },
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
diff --git a/src/types.js b/src/types.js
index adcbc93a..ca59c21d 100644
--- a/src/types.js
+++ b/src/types.js
@@ -144,6 +144,9 @@ export type Settings = {
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: boolean,
   EXPRESS_SERVER_CONFIG: {
     PORT: number,
+    SSL_CERTIFICATE_FILEPATH: ?string,
+    SSL_PRIVATE_KEY_FILEPATH: ?string,
+    USE_SSL: boolean,
   },
   FIRMWARE_DIRECTORY: string,
   FIRMWARE_REPOSITORY_DIRECTORY: string,
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 32ddd6cb..713dc1a9 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -23,6 +23,9 @@ export default {
 
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,
+    SSL_CERTIFICATE_FILEPATH: null,
+    SSL_PRIVATE_KEY_FILEPATH: null,
+    USE_SSL: false,
   },
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',

From 6f382471811df08db082621c44627047d21d960a Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 19 May 2017 20:12:41 +0200
Subject: [PATCH 360/504] add tingodb, implement database repositories

---
 dist/defaultBindings.js                      |  24 +-
 dist/lib/promisify.js                        |  60 ++++
 dist/main.js                                 |  49 ++-
 dist/managers/WebhookManager.js              |   2 +-
 dist/repository/TingoDb.js                   |  45 +++
 dist/repository/UserDatabaseRepository.js    | 297 +++++++++++++++++++
 dist/repository/WebhookDatabaseRepository.js | 163 ++++++++++
 dist/settings.js                             |  17 +-
 package.json                                 |   3 +-
 src/defaultBindings.js                       |  23 +-
 src/lib/promisify.js                         |  31 ++
 src/repository/TingoDb.js                    |  25 ++
 src/repository/UserDatabaseRepository.js     |  94 ++++++
 src/repository/WebhookDatabaseRepository.js  |  49 +++
 src/settings.js                              |  10 +
 src/types.js                                 |   8 +
 test/setup/settings.js                       |  10 +
 17 files changed, 879 insertions(+), 31 deletions(-)
 create mode 100644 dist/lib/promisify.js
 create mode 100644 dist/repository/TingoDb.js
 create mode 100644 dist/repository/UserDatabaseRepository.js
 create mode 100644 dist/repository/WebhookDatabaseRepository.js
 create mode 100644 src/lib/promisify.js
 create mode 100644 src/repository/TingoDb.js
 create mode 100644 src/repository/UserDatabaseRepository.js
 create mode 100644 src/repository/WebhookDatabaseRepository.js

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index fe4a0cd6..1a73acb7 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -42,6 +42,10 @@ var _WebhooksController = require('./controllers/WebhooksController');
 
 var _WebhooksController2 = _interopRequireDefault(_WebhooksController);
 
+var _DeviceManager = require('./managers/DeviceManager');
+
+var _DeviceManager2 = _interopRequireDefault(_DeviceManager);
+
 var _WebhookManager = require('./managers/WebhookManager');
 
 var _WebhookManager2 = _interopRequireDefault(_WebhookManager);
@@ -54,17 +58,17 @@ var _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepo
 
 var _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository);
 
-var _DeviceManager = require('./managers/DeviceManager');
+var _TingoDb = require('./repository/TingoDb');
 
-var _DeviceManager2 = _interopRequireDefault(_DeviceManager);
+var _TingoDb2 = _interopRequireDefault(_TingoDb);
 
-var _UserFileRepository = require('./repository/UserFileRepository');
+var _UserDatabaseRepository = require('./repository/UserDatabaseRepository');
 
-var _UserFileRepository2 = _interopRequireDefault(_UserFileRepository);
+var _UserDatabaseRepository2 = _interopRequireDefault(_UserDatabaseRepository);
 
-var _WebhookFileRepository = require('./repository/WebhookFileRepository');
+var _WebhookDatabaseRepository = require('./repository/WebhookDatabaseRepository');
 
-var _WebhookFileRepository2 = _interopRequireDefault(_WebhookFileRepository);
+var _WebhookDatabaseRepository2 = _interopRequireDefault(_WebhookDatabaseRepository);
 
 var _settings = require('./settings');
 
@@ -82,6 +86,8 @@ exports.default = function (container, newSettings) {
   (0, _sparkProtocol.defaultBindings)(container, newSettings);
 
   // settings
+  container.bindValue('DATABASE_PATH', _settings2.default.DB_CONFIG.PATH);
+  container.bindValue('DATABASE_OPTIONS', _settings2.default.DB_CONFIG.OPTIONS);
   container.bindValue('DEVICE_DIRECTORY', _settings2.default.DEVICE_DIRECTORY);
   container.bindValue('FIRMWARE_DIRECTORY', _settings2.default.FIRMWARE_DIRECTORY);
   container.bindValue('SERVER_KEY_FILENAME', _settings2.default.SERVER_KEY_FILENAME);
@@ -89,6 +95,8 @@ exports.default = function (container, newSettings) {
   container.bindValue('USERS_DIRECTORY', _settings2.default.USERS_DIRECTORY);
   container.bindValue('WEBHOOKS_DIRECTORY', _settings2.default.WEBHOOKS_DIRECTORY);
 
+  container.bindClass('Database', _TingoDb2.default, ['DATABASE_PATH', 'DATABASE_OPTIONS']);
+
   // controllers
   container.bindClass('DeviceClaimsController', _DeviceClaimsController2.default, ['DeviceManager', 'ClaimCodeManager']);
   container.bindClass('DevicesController', _DevicesController2.default, ['DeviceManager']);
@@ -106,6 +114,6 @@ exports.default = function (container, newSettings) {
 
   // Repositories
   container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']);
-  container.bindClass('UserRepository', _UserFileRepository2.default, ['USERS_DIRECTORY']);
-  container.bindClass('WebhookRepository', _WebhookFileRepository2.default, ['WEBHOOKS_DIRECTORY']);
+  container.bindClass('UserRepository', _UserDatabaseRepository2.default, ['Database']);
+  container.bindClass('WebhookRepository', _WebhookDatabaseRepository2.default, ['Database']);
 };
\ No newline at end of file
diff --git a/dist/lib/promisify.js b/dist/lib/promisify.js
new file mode 100644
index 00000000..36bd227e
--- /dev/null
+++ b/dist/lib/promisify.js
@@ -0,0 +1,60 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.promisifyByPrototype = exports.promisify = undefined;
+
+var _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names');
+
+var _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _promise = require('babel-runtime/core-js/promise');
+
+var _promise2 = _interopRequireDefault(_promise);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var promisify = exports.promisify = function promisify(func) {
+  for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+    args[_key - 1] = arguments[_key];
+  }
+
+  return new _promise2.default(function (resolve, reject) {
+    return func.apply(undefined, args.concat([function (error, result) {
+      if (error) {
+        reject(error);
+        return;
+      }
+      resolve(result);
+    }]));
+  });
+};
+
+var promisifyByPrototype = exports.promisifyByPrototype = function promisifyByPrototype(object) {
+  var prototype = (0, _getPrototypeOf2.default)(object);
+
+  var fnNames = (0, _getOwnPropertyNames2.default)(prototype).filter(function (propName) {
+    return typeof prototype[propName] === 'function';
+  });
+
+  var resultObject = {};
+
+  fnNames.forEach(function (fnName) {
+    resultObject[fnName] = function () {
+      var _object$fnName;
+
+      for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+        args[_key2] = arguments[_key2];
+      }
+
+      return promisify((_object$fnName = object[fnName]).bind.apply(_object$fnName, [object].concat(args)));
+    };
+  });
+
+  return resultObject;
+};
\ No newline at end of file
diff --git a/dist/main.js b/dist/main.js
index 23a989b6..1479b054 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -8,32 +8,48 @@ var _entries = require('babel-runtime/core-js/object/entries');
 
 var _entries2 = _interopRequireDefault(_entries);
 
-var _constitute = require('constitute');
+var _arrayFlatten = require('array-flatten');
 
-var _os = require('os');
+var _arrayFlatten2 = _interopRequireDefault(_arrayFlatten);
 
-var _os2 = _interopRequireDefault(_os);
+var _app = require('./app');
 
-var _arrayFlatten = require('array-flatten');
+var _app2 = _interopRequireDefault(_app);
 
-var _arrayFlatten2 = _interopRequireDefault(_arrayFlatten);
+var _nullthrows = require('nullthrows');
 
-var _logger = require('./lib/logger');
+var _nullthrows2 = _interopRequireDefault(_nullthrows);
 
-var _logger2 = _interopRequireDefault(_logger);
+var _fs = require('fs');
 
-var _app = require('./app');
+var _fs2 = _interopRequireDefault(_fs);
 
-var _app2 = _interopRequireDefault(_app);
+var _http = require('http');
+
+var _http2 = _interopRequireDefault(_http);
+
+var _https = require('https');
+
+var _https2 = _interopRequireDefault(_https);
+
+var _os = require('os');
+
+var _os2 = _interopRequireDefault(_os);
 
 var _defaultBindings = require('./defaultBindings');
 
 var _defaultBindings2 = _interopRequireDefault(_defaultBindings);
 
+var _logger = require('./lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
+var _constitute = require('constitute');
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var NODE_PORT = process.env.NODE_PORT || _settings2.default.EXPRESS_SERVER_CONFIG.PORT;
@@ -62,9 +78,20 @@ deviceServer.start();
 
 var app = (0, _app2.default)(container, _settings2.default);
 
-app.listen(NODE_PORT, function () {
+var onServerStartListen = function onServerStartListen() {
   return console.log('express server started on port ' + NODE_PORT);
-});
+};
+
+if (_settings2.default.EXPRESS_SERVER_CONFIG.USE_SSL) {
+  var options = {
+    cert: _fs2.default.readFileSync((0, _nullthrows2.default)(_settings2.default.EXPRESS_SERVER_CONFIG.SSL_CERTIFICATE_FILEPATH)),
+    key: _fs2.default.readFileSync((0, _nullthrows2.default)(_settings2.default.EXPRESS_SERVER_CONFIG.SSL_PRIVATE_KEY_FILEPATH))
+  };
+
+  _https2.default.createServer(options, app).listen(NODE_PORT, onServerStartListen);
+} else {
+  _http2.default.createServer(app).listen(NODE_PORT, onServerStartListen);
+}
 
 var addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map(
 // eslint-disable-next-line no-unused-vars
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index e8217282..c5be0fd0 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -320,7 +320,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher)
               requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject);
               responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject);
               requestType = _this._compileTemplate(webhook.requestType, webhookVariablesObject);
-              isJsonRequest = !!requestJson;
+              isJsonRequest = !!requestJson || !requestFormData;
               requestOptions = {
                 auth: requestAuth,
                 body: isJsonRequest && requestJson ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
diff --git a/dist/repository/TingoDb.js b/dist/repository/TingoDb.js
new file mode 100644
index 00000000..eea6cd93
--- /dev/null
+++ b/dist/repository/TingoDb.js
@@ -0,0 +1,45 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _fs = require('fs');
+
+var _fs2 = _interopRequireDefault(_fs);
+
+var _mkdirp = require('mkdirp');
+
+var _mkdirp2 = _interopRequireDefault(_mkdirp);
+
+var _tingodb = require('tingodb');
+
+var _tingodb2 = _interopRequireDefault(_tingodb);
+
+var _promisify = require('../lib/promisify');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var TingoDb = function TingoDb(path, options) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, TingoDb);
+
+  this.getCollection = function (collectionName) {
+    return (0, _promisify.promisifyByPrototype)(_this._database.collection(collectionName));
+  };
+
+  var Db = (0, _tingodb2.default)(options).Db;
+
+  if (!_fs2.default.existsSync(path)) {
+    _mkdirp2.default.sync(path);
+  }
+
+  this._database = new Db(path, {});
+};
+
+exports.default = TingoDb;
\ No newline at end of file
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
new file mode 100644
index 00000000..0b0bf915
--- /dev/null
+++ b/dist/repository/UserDatabaseRepository.js
@@ -0,0 +1,297 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _PasswordHasher = require('../lib/PasswordHasher');
+
+var _PasswordHasher2 = _interopRequireDefault(_PasswordHasher);
+
+var _HttpError = require('../lib/HttpError');
+
+var _HttpError2 = _interopRequireDefault(_HttpError);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var UserDatabaseRepository = function UserDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, UserDatabaseRepository);
+
+  this.createWithCredentials = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) {
+      var username, password, salt, passwordHash, modelToSave, user;
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              username = userCredentials.username, password = userCredentials.password;
+              _context.next = 3;
+              return _PasswordHasher2.default.generateSalt();
+
+            case 3:
+              salt = _context.sent;
+              _context.next = 6;
+              return _PasswordHasher2.default.hash(password, salt);
+
+            case 6:
+              passwordHash = _context.sent;
+              modelToSave = {
+                accessTokens: [],
+                created_at: new Date(),
+                created_by: null,
+                passwordHash: passwordHash,
+                salt: salt,
+                username: username
+              };
+              _context.next = 10;
+              return _this._collection.insert(modelToSave, { fullResult: true });
+
+            case 10:
+              user = _context.sent[0];
+              return _context.abrupt('return', (0, _extends3.default)({}, user, { id: user._id.toString() }));
+
+            case 12:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.deleteAccessToken = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userID, accessToken) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this._collection.findAndModify({ _id: userID }, null, { $pull: { accessTokens: { accessToken: accessToken } } }, { new: true });
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x2, _x3) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.deleteById = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(id) {
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              _context3.next = 2;
+              return _this._collection.remove({ _id: id });
+
+            case 2:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 3:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function (_x4) {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.getByAccessToken = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(accessToken) {
+      var user;
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this._collection.findOne({ 'accessTokens.accessToken': accessToken });
+
+            case 2:
+              user = _context4.sent;
+              return _context4.abrupt('return', user ? (0, _extends3.default)({}, user, { id: user._id.toString() }) : null);
+
+            case 4:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x5) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.getByUsername = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(username) {
+      var user;
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              _context5.next = 2;
+              return _this._collection.findOne({ username: username });
+
+            case 2:
+              user = _context5.sent;
+              return _context5.abrupt('return', user ? (0, _extends3.default)({}, user, { id: user._id.toString() }) : null);
+
+            case 4:
+            case 'end':
+              return _context5.stop();
+          }
+        }
+      }, _callee5, _this);
+    }));
+
+    return function (_x6) {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.isUserNameInUse = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(username) {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
+        while (1) {
+          switch (_context6.prev = _context6.next) {
+            case 0:
+              _context6.next = 2;
+              return _this.getByUsername(username);
+
+            case 2:
+              return _context6.abrupt('return', !!_context6.sent);
+
+            case 3:
+            case 'end':
+              return _context6.stop();
+          }
+        }
+      }, _callee6, _this);
+    }));
+
+    return function (_x7) {
+      return _ref6.apply(this, arguments);
+    };
+  }();
+
+  this.saveAccessToken = function () {
+    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(userID, tokenObject) {
+      return _regenerator2.default.wrap(function _callee7$(_context7) {
+        while (1) {
+          switch (_context7.prev = _context7.next) {
+            case 0:
+              _context7.next = 2;
+              return _this._collection.findAndModify({ _id: userID }, null, { $push: { accessTokens: tokenObject } }, { new: true });
+
+            case 2:
+              return _context7.abrupt('return', _context7.sent);
+
+            case 3:
+            case 'end':
+              return _context7.stop();
+          }
+        }
+      }, _callee7, _this);
+    }));
+
+    return function (_x8, _x9) {
+      return _ref7.apply(this, arguments);
+    };
+  }();
+
+  this.validateLogin = function () {
+    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(username, password) {
+      var user, hash;
+      return _regenerator2.default.wrap(function _callee8$(_context8) {
+        while (1) {
+          switch (_context8.prev = _context8.next) {
+            case 0:
+              _context8.prev = 0;
+              _context8.next = 3;
+              return _this._collection.findOne({ username: username });
+
+            case 3:
+              user = _context8.sent;
+
+              if (user) {
+                _context8.next = 6;
+                break;
+              }
+
+              throw new _HttpError2.default('User doesn\'t exist', 404);
+
+            case 6:
+              _context8.next = 8;
+              return _PasswordHasher2.default.hash(password, user.salt);
+
+            case 8:
+              hash = _context8.sent;
+
+              if (!(hash !== user.passwordHash)) {
+                _context8.next = 11;
+                break;
+              }
+
+              throw new _HttpError2.default('Wrong password');
+
+            case 11:
+              return _context8.abrupt('return', (0, _extends3.default)({}, user, { id: user._id.toString() }));
+
+            case 14:
+              _context8.prev = 14;
+              _context8.t0 = _context8['catch'](0);
+              throw _context8.t0;
+
+            case 17:
+            case 'end':
+              return _context8.stop();
+          }
+        }
+      }, _callee8, _this, [[0, 14]]);
+    }));
+
+    return function (_x10, _x11) {
+      return _ref8.apply(this, arguments);
+    };
+  }();
+
+  this._collection = database.getCollection('users');
+};
+
+exports.default = UserDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
new file mode 100644
index 00000000..5dc04ca9
--- /dev/null
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -0,0 +1,163 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _promisify = require('../lib/promisify');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, WebhookDatabaseRepository);
+
+  this.create = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var webhook;
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return _this._collection.insert((0, _extends3.default)({}, model, {
+                created_at: new Date()
+              }), { fullResult: true });
+
+            case 2:
+              webhook = _context.sent[0];
+              return _context.abrupt('return', (0, _extends3.default)({}, webhook, { id: webhook._id.toString() }));
+
+            case 4:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.deleteById = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              return _context2.abrupt('return', _this._collection.remove({ _id: id }));
+
+            case 1:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x2) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.getAll = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              query = userID ? { ownerID: userID } : {};
+              _context3.t0 = _promisify.promisifyByPrototype;
+              _context3.next = 4;
+              return _this._collection.find(query);
+
+            case 4:
+              _context3.t1 = _context3.sent;
+              _context3.next = 7;
+              return (0, _context3.t0)(_context3.t1).toArray();
+
+            case 7:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 8:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function () {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.getById = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+      var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+      var query, webhook;
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              query = userID ? { _id: id, ownerID: userID } : { _id: id };
+              _context4.next = 3;
+              return _this._collection.findOne(query);
+
+            case 3:
+              webhook = _context4.sent;
+              return _context4.abrupt('return', webhook ? (0, _extends3.default)({}, webhook, { id: webhook._id.toString() }) : null);
+
+            case 5:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x4) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.update = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+    return _regenerator2.default.wrap(function _callee5$(_context5) {
+      while (1) {
+        switch (_context5.prev = _context5.next) {
+          case 0:
+            throw new Error('The method is not implemented');
+
+          case 1:
+          case 'end':
+            return _context5.stop();
+        }
+      }
+    }, _callee5, _this);
+  }));
+
+  this._collection = database.getCollection('webhooks');
+};
+
+exports.default = WebhookDatabaseRepository;
\ No newline at end of file
diff --git a/dist/settings.js b/dist/settings.js
index bc69825a..55e561db 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -21,15 +21,26 @@ exports.default = {
   SERVER_KEYS_DIRECTORY: _path2.default.join(__dirname, '../data'),
   USERS_DIRECTORY: _path2.default.join(__dirname, '../data/users'),
   WEBHOOKS_DIRECTORY: _path2.default.join(__dirname, '../data/webhooks'),
-
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_SALT: 'aes-128-cbc',
   LOG_REQUESTS: true,
   LOGIN_ROUTE: '/oauth/token',
-
   EXPRESS_SERVER_CONFIG: {
-    PORT: 8080
+    PORT: 8080,
+    SSL_CERTIFICATE_FILEPATH: null,
+    SSL_PRIVATE_KEY_FILEPATH: null,
+    USE_SSL: false
+  },
+  DB_CONFIG: {
+    OPTIONS: {
+      cacheMaxObjSize: 1024,
+      cacheSize: 1000,
+      memStore: false,
+      nativeObjectID: true,
+      searchInArray: true
+    },
+    PATH: _path2.default.join(__dirname, '../data/db')
   },
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
diff --git a/package.json b/package.json
index 8fbf4dd1..a6620767 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
     "rimraf": "^2.5.4",
     "rmfr": "^1.0.1",
     "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev",
+    "tingodb": "git+https://github.com/Brewskey/tingodb",
     "ursa": "^0.9.4",
     "uuid": "^3.0.1"
   },
@@ -100,7 +101,7 @@
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-sorting": "^0.3.0",
     "flow-bin": "^0.37.0",
-    "nodemon": "^1.11.0",
+    "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
     "rimraf": "^2.5.4",
     "sinon": "^1.17.7",
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 2e5fad82..8239e462 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -12,12 +12,13 @@ import ProductsController from './controllers/ProductsController';
 import ProvisioningController from './controllers/ProvisioningController';
 import UsersController from './controllers/UsersController';
 import WebhooksController from './controllers/WebhooksController';
+import DeviceManager from './managers/DeviceManager';
 import WebhookManager from './managers/WebhookManager';
 import EventManager from './managers/EventManager';
 import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository';
-import DeviceManager from './managers/DeviceManager';
-import UserFileRepository from './repository/UserFileRepository';
-import WebhookFileRepository from './repository/WebhookFileRepository';
+import TingoDb from './repository/TingoDb';
+import UserDatabaseRepository from './repository/UserDatabaseRepository';
+import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
 import settings from './settings';
 
 export default (container: Container, newSettings: Settings) => {
@@ -30,6 +31,8 @@ export default (container: Container, newSettings: Settings) => {
   defaultBindings(container, newSettings);
 
   // settings
+  container.bindValue('DATABASE_PATH', settings.DB_CONFIG.PATH);
+  container.bindValue('DATABASE_OPTIONS', settings.DB_CONFIG.OPTIONS);
   container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY);
   container.bindValue('FIRMWARE_DIRECTORY', settings.FIRMWARE_DIRECTORY);
   container.bindValue('SERVER_KEY_FILENAME', settings.SERVER_KEY_FILENAME);
@@ -37,6 +40,12 @@ export default (container: Container, newSettings: Settings) => {
   container.bindValue('USERS_DIRECTORY', settings.USERS_DIRECTORY);
   container.bindValue('WEBHOOKS_DIRECTORY', settings.WEBHOOKS_DIRECTORY);
 
+  container.bindClass(
+    'Database',
+    TingoDb,
+    ['DATABASE_PATH', 'DATABASE_OPTIONS'],
+  );
+
   // controllers
   container.bindClass(
     'DeviceClaimsController',
@@ -112,12 +121,12 @@ export default (container: Container, newSettings: Settings) => {
   );
   container.bindClass(
     'UserRepository',
-    UserFileRepository,
-    ['USERS_DIRECTORY'],
+    UserDatabaseRepository,
+    ['Database'],
   );
   container.bindClass(
     'WebhookRepository',
-    WebhookFileRepository,
-    ['WEBHOOKS_DIRECTORY'],
+    WebhookDatabaseRepository,
+    ['Database'],
   );
 };
diff --git a/src/lib/promisify.js b/src/lib/promisify.js
new file mode 100644
index 00000000..f6fce6be
--- /dev/null
+++ b/src/lib/promisify.js
@@ -0,0 +1,31 @@
+// @flow
+
+export const promisify = (func: Function, ...args: Array): Promise<*> =>
+  new Promise((
+    resolve: (result: any) => void,
+    reject: (error: Error) => void,
+  ): void => func(...args, (error: Error, result: any) => {
+    if (error) {
+      reject(error);
+      return;
+    }
+    resolve(result);
+  }),
+);
+
+export const promisifyByPrototype = (object: Object): Object => {
+  const prototype = Object.getPrototypeOf(object);
+
+  const fnNames = Object.getOwnPropertyNames(prototype).filter(
+    (propName: string): boolean => typeof prototype[propName] === 'function',
+  );
+
+  const resultObject = {};
+
+  fnNames.forEach((fnName: string) => {
+    resultObject[fnName] = (...args: Array): Promise<*> =>
+      promisify(object[fnName].bind(object, ...args));
+  });
+
+  return resultObject;
+};
diff --git a/src/repository/TingoDb.js b/src/repository/TingoDb.js
new file mode 100644
index 00000000..ccc7beac
--- /dev/null
+++ b/src/repository/TingoDb.js
@@ -0,0 +1,25 @@
+// @flow
+
+import fs from 'fs';
+import mkdirp from 'mkdirp';
+import tingoDb from 'tingodb';
+import { promisifyByPrototype } from '../lib/promisify';
+
+class TingoDb {
+  _database: Object;
+
+  constructor(path: string, options: Object) {
+    const Db = tingoDb(options).Db;
+
+    if (!fs.existsSync(path)) {
+      mkdirp.sync(path);
+    }
+
+    this._database = new Db(path, {});
+  }
+
+  getCollection = (collectionName: string): Object =>
+    promisifyByPrototype(this._database.collection(collectionName));
+}
+
+export default TingoDb;
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
new file mode 100644
index 00000000..ac0261a9
--- /dev/null
+++ b/src/repository/UserDatabaseRepository.js
@@ -0,0 +1,94 @@
+// @flow
+
+import type { Database, TokenObject, User, UserCredentials } from '../types';
+
+import PasswordHasher from '../lib/PasswordHasher';
+import HttpError from '../lib/HttpError';
+
+class UserDatabaseRepository {
+  _collection: Object;
+
+  constructor(database: Database) {
+    this._collection = database.getCollection('users');
+  }
+
+  createWithCredentials = async (userCredentials: UserCredentials): Promise => {
+    const { username, password } = userCredentials;
+
+    const salt = await PasswordHasher.generateSalt();
+    const passwordHash = await PasswordHasher.hash(password, salt);
+    const modelToSave = {
+      accessTokens: [],
+      created_at: new Date(),
+      created_by: null,
+      passwordHash,
+      salt,
+      username,
+    };
+
+    const user = (await this._collection.insert(
+      modelToSave,
+      { fullResult: true },
+    ))[0];
+    return { ...user, id: user._id.toString() };
+  };
+
+  deleteAccessToken = async (userID: string, accessToken: string): Promise =>
+    await this._collection.findAndModify(
+      { _id: userID },
+      null,
+      { $pull: { accessTokens: { accessToken } } },
+      { new: true },
+    );
+
+  deleteById = async (id: string): Promise =>
+    await this._collection.remove({ _id: id });
+
+  getByAccessToken = async (accessToken: string): Promise => {
+    const user = await this._collection.findOne(
+      { 'accessTokens.accessToken': accessToken },
+    );
+
+    return user ? { ...user, id: user._id.toString() } : null;
+  };
+
+  getByUsername = async (username: string): Promise => {
+    const user = await this._collection.findOne({ username });
+
+    return user ? { ...user, id: user._id.toString() } : null;
+  };
+
+  isUserNameInUse = async (username: string): Promise =>
+    !!(await this.getByUsername(username));
+
+  saveAccessToken = async (
+    userID: string,
+    tokenObject: TokenObject,
+  ): Promise<*> => await this._collection.findAndModify(
+    { _id: userID },
+    null,
+    { $push: { accessTokens: tokenObject } },
+    { new: true },
+  );
+
+  validateLogin = async (username: string, password: string): Promise => {
+    try {
+      const user = await this._collection.findOne({ username });
+
+      if (!user) {
+        throw new HttpError('User doesn\'t exist', 404);
+      }
+
+      const hash = await PasswordHasher.hash(password, user.salt);
+      if (hash !== user.passwordHash) {
+        throw new HttpError('Wrong password');
+      }
+
+      return { ...user, id: user._id.toString() };
+    } catch (error) {
+      throw error;
+    }
+  };
+}
+
+export default UserDatabaseRepository;
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
new file mode 100644
index 00000000..7f200f6b
--- /dev/null
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -0,0 +1,49 @@
+// @flow
+
+import type { Database, Webhook } from '../types';
+
+import { promisifyByPrototype } from '../lib/promisify';
+
+class WebhookDatabaseRepository {
+  _collection: Object;
+
+  constructor(database: Database) {
+    this._collection = database.getCollection('webhooks');
+  }
+
+  create = async (model: $Shape): Promise => {
+    const webhook = (await this._collection.insert(
+      {
+        ...model,
+        created_at: new Date(),
+      },
+      { fullResult: true },
+    ))[0];
+
+    return { ...webhook, id: webhook._id.toString() };
+  };
+
+  deleteById = async (id: string): Promise =>
+    this._collection.remove({ _id: id });
+
+  getAll = async (userID: ?string = null): Promise> => {
+    const query = userID ? { ownerID: userID } : {};
+    return await (promisifyByPrototype(
+      await this._collection.find(query),
+    ).toArray());
+  };
+
+  getById = async (id: string, userID: ?string = null): Promise => {
+    const query = userID ? { _id: id, ownerID: userID } : { _id: id };
+    const webhook = await this._collection.findOne(query);
+
+    return webhook ? { ...webhook, id: webhook._id.toString() } : null;
+  };
+
+  update = async (): Promise => {
+    throw new Error('The method is not implemented');
+  };
+}
+
+export default WebhookDatabaseRepository;
+
diff --git a/src/settings.js b/src/settings.js
index 721b6ac1..d8051b93 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -43,6 +43,16 @@ export default {
     SSL_PRIVATE_KEY_FILEPATH: null,
     USE_SSL: false,
   },
+  DB_CONFIG: {
+    OPTIONS: {
+      cacheMaxObjSize: 1024,
+      cacheSize: 1000,
+      memStore: false,
+      nativeObjectID: true,
+      searchInArray: true,
+    },
+    PATH: path.join(__dirname, '../data/db'),
+  },
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
     PORT: 5683,
diff --git a/src/types.js b/src/types.js
index ca59c21d..6d8ec0e2 100644
--- a/src/types.js
+++ b/src/types.js
@@ -2,6 +2,10 @@
 
 import type { File } from 'express';
 
+export type Database = {
+  getCollection(collectionName: string): Object,
+};
+
 export type Webhook = {
   auth?: { password: string, username: string },
   created_at: Date,
@@ -140,6 +144,10 @@ export type Settings = {
   API_TIMEOUT: number,
   BUILD_DIRECTORY: string,
   CRYPTO_SALT: string,
+  DB_CONFIG: {
+    OPTIONS: Object,
+    PATH: string,
+  },
   DEVICE_DIRECTORY: string,
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: boolean,
   EXPRESS_SERVER_CONFIG: {
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 713dc1a9..4e6b213d 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -31,4 +31,14 @@ export default {
     HOST: 'localhost',
     PORT: 5683,
   },
+  DB_CONFIG: {
+    OPTIONS: {
+      cacheMaxObjSize: 1024,
+      cacheSize: 1000,
+      memStore: true,
+      nativeObjectID: true,
+      searchInArray: true,
+    },
+    PATH: path.join(__dirname, '../__test_data__/db'),
+  },
 };

From ac99672817a276925c142489c4bb2b56f047c942 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 19 May 2017 22:02:00 +0200
Subject: [PATCH 361/504] implement deviceAttributeDatabase repo

---
 dist/defaultBindings.js                       |   5 +
 .../DeviceAttributeDatabaseRepository.js      | 186 ++++++++++++++++++
 src/defaultBindings.js                        |   7 +
 .../DeviceAttributeDatabaseRepository.js      |  50 +++++
 4 files changed, 248 insertions(+)
 create mode 100644 dist/repository/DeviceAttributeDatabaseRepository.js
 create mode 100644 src/repository/DeviceAttributeDatabaseRepository.js

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 1a73acb7..90d4095b 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -62,6 +62,10 @@ var _TingoDb = require('./repository/TingoDb');
 
 var _TingoDb2 = _interopRequireDefault(_TingoDb);
 
+var _DeviceAttributeDatabaseRepository = require('./repository/DeviceAttributeDatabaseRepository');
+
+var _DeviceAttributeDatabaseRepository2 = _interopRequireDefault(_DeviceAttributeDatabaseRepository);
+
 var _UserDatabaseRepository = require('./repository/UserDatabaseRepository');
 
 var _UserDatabaseRepository2 = _interopRequireDefault(_UserDatabaseRepository);
@@ -113,6 +117,7 @@ exports.default = function (container, newSettings) {
   container.bindClass('WebhookManager', _WebhookManager2.default, ['WebhookRepository', 'EventPublisher']);
 
   // Repositories
+  container.bindClass('DeviceAttributeRepository', _DeviceAttributeDatabaseRepository2.default, ['Database']);
   container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']);
   container.bindClass('UserRepository', _UserDatabaseRepository2.default, ['Database']);
   container.bindClass('WebhookRepository', _WebhookDatabaseRepository2.default, ['Database']);
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
new file mode 100644
index 00000000..711a45ed
--- /dev/null
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -0,0 +1,186 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _promisify = require('../lib/promisify');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, DeviceAttributeDatabaseRepository);
+  this.create = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+    return _regenerator2.default.wrap(function _callee$(_context) {
+      while (1) {
+        switch (_context.prev = _context.next) {
+          case 0:
+            throw new Error('The method is not implemented');
+
+          case 1:
+          case 'end':
+            return _context.stop();
+        }
+      }
+    }, _callee, _this);
+  }));
+
+  this.update = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(model) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this._collection.findAndModify({ _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.deleteById = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(id) {
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              _context3.next = 2;
+              return _this._collection.remove({ _id: id });
+
+            case 2:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 3:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function (_x2) {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.doesUserHaveAccess = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id, userID) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this._collection.findOne({ _id: id, ownerID: userID });
+
+            case 2:
+              return _context4.abrupt('return', !!_context4.sent);
+
+            case 3:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x3, _x4) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.getAll = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              query = userID ? { ownerID: userID } : {};
+              _context5.t0 = _promisify.promisifyByPrototype;
+              _context5.next = 4;
+              return _this._collection.find(query);
+
+            case 4:
+              _context5.t1 = _context5.sent;
+              _context5.next = 7;
+              return (0, _context5.t0)(_context5.t1).toArray();
+
+            case 7:
+              return _context5.abrupt('return', _context5.sent);
+
+            case 8:
+            case 'end':
+              return _context5.stop();
+          }
+        }
+      }, _callee5, _this);
+    }));
+
+    return function () {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.getById = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(id) {
+      var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+      var query;
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
+        while (1) {
+          switch (_context6.prev = _context6.next) {
+            case 0:
+              query = userID ? { _id: id, ownerID: userID } : { _id: id };
+              _context6.next = 3;
+              return _this._collection.findOne(query);
+
+            case 3:
+              return _context6.abrupt('return', _context6.sent);
+
+            case 4:
+            case 'end':
+              return _context6.stop();
+          }
+        }
+      }, _callee6, _this);
+    }));
+
+    return function (_x6) {
+      return _ref6.apply(this, arguments);
+    };
+  }();
+
+  this._collection = database.getCollection('deviceAttributes');
+};
+
+exports.default = DeviceAttributeDatabaseRepository;
\ No newline at end of file
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 8239e462..6baf9c87 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -17,6 +17,8 @@ import WebhookManager from './managers/WebhookManager';
 import EventManager from './managers/EventManager';
 import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository';
 import TingoDb from './repository/TingoDb';
+import DeviceAttributeDatabaseRepository from
+  './repository/DeviceAttributeDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
 import settings from './settings';
@@ -114,6 +116,11 @@ export default (container: Container, newSettings: Settings) => {
   );
 
   // Repositories
+  container.bindClass(
+    'DeviceAttributeRepository',
+    DeviceAttributeDatabaseRepository,
+    ['Database'],
+  );
   container.bindClass(
     'DeviceFirmwareRepository',
     DeviceFirmwareFileRepository,
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
new file mode 100644
index 00000000..f24b81cc
--- /dev/null
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -0,0 +1,50 @@
+// @flow
+
+import type { Database, DeviceAttributes } from '../types';
+
+import { promisifyByPrototype } from '../lib/promisify';
+
+class DeviceAttributeDatabaseRepository {
+  _collection: Object;
+
+  constructor(database: Database) {
+    this._collection = database.getCollection('deviceAttributes');
+  }
+
+  create = async (): Promise => {
+    throw new Error('The method is not implemented');
+  };
+
+  update = async (model: DeviceAttributes): Promise =>
+    await this._collection.findAndModify(
+      { _id: model.deviceID },
+      null,
+      { $set: { ...model, _id: model.deviceID, timeStamp: new Date() } },
+      { new: true, upsert: true },
+    );
+
+  deleteById = async (id: string): Promise =>
+    await this._collection.remove({ _id: id });
+
+  doesUserHaveAccess = async (id: string, userID: string): Promise =>
+    !!(await this._collection.findOne({ _id: id, ownerID: userID }));
+
+  getAll = async (userID: ?string = null): Promise> => {
+    const query = userID ? { ownerID: userID } : {};
+
+    return await (promisifyByPrototype(
+      await this._collection.find(query),
+    ).toArray());
+  };
+
+  getById = async (
+    id: string,
+    userID: ?string = null,
+  ): Promise => {
+    const query = userID ? { _id: id, ownerID: userID } : { _id: id };
+
+    return await this._collection.findOne(query);
+  }
+}
+
+export default DeviceAttributeDatabaseRepository;

From 7b953bbcfd386e86ddc4a2565231734647ef21d1 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 20 May 2017 11:47:54 -0700
Subject: [PATCH 362/504] Removing postinstall as it breaks when you consume
 spark-server in other projects.

---
 package.json | 2 --
 1 file changed, 2 deletions(-)

diff --git a/package.json b/package.json
index a6620767..80ef2ce3 100644
--- a/package.json
+++ b/package.json
@@ -31,13 +31,11 @@
     "build:watch": "babel ./src --out-dir ./dist --watch",
     "build:clean": "rimraf ./build",
     "lint": "eslint --fix --max-warnings 0 -- .",
-    "postinstall": "npm run update-firmware",
     "prebuild": "npm run build:clean",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
     "start:prod": "npm run build && node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
-    "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries"
   },
   "pre-commit": [
     "lint",

From 37e369469f325c61ae8db68a0a5896bd30b68b76 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 20 May 2017 11:54:34 -0700
Subject: [PATCH 363/504] Fixing package.json

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index 80ef2ce3..75001512 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
     "start:prod": "npm run build && node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
+    "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries"
   },
   "pre-commit": [
     "lint",

From 754cf5c88f2f94984c63bb5cbc29bf7565c72e53 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 20 May 2017 12:32:38 -0700
Subject: [PATCH 364/504] Updating exports so I can use the promisify code.

---
 dist/exports.js | 5 ++++-
 src/exports.js  | 2 ++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/dist/exports.js b/dist/exports.js
index a7506238..f4eee3d9 100644
--- a/dist/exports.js
+++ b/dist/exports.js
@@ -3,7 +3,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.settings = exports.logger = exports.defaultBindings = exports.createApp = undefined;
+exports.settings = exports.promisifyByPrototype = exports.logger = exports.defaultBindings = exports.createApp = undefined;
 
 var _logger = require('./lib/logger');
 
@@ -21,9 +21,12 @@ var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
+var _promisify = require('./lib/promisify');
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 exports.createApp = _app2.default;
 exports.defaultBindings = _defaultBindings2.default;
 exports.logger = _logger2.default;
+exports.promisifyByPrototype = _promisify.promisifyByPrototype;
 exports.settings = _settings2.default;
\ No newline at end of file
diff --git a/src/exports.js b/src/exports.js
index 008a4897..b2a58137 100644
--- a/src/exports.js
+++ b/src/exports.js
@@ -4,10 +4,12 @@ import logger from './lib/logger';
 import createApp from './app';
 import defaultBindings from './defaultBindings';
 import settings from './settings';
+import { promisifyByPrototype } from './lib/promisify';
 
 export {
   createApp,
   defaultBindings,
   logger,
+  promisifyByPrototype,
   settings,
 };

From eb71f97095b4bcaec2e3c9462296e6922bb000f4 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 20 May 2017 15:07:13 -0700
Subject: [PATCH 365/504] Changed the interface for using mongo

---
 dist/exports.js                               |   5 +-
 dist/lib/promisify.js                         |   8 +-
 dist/repository/BaseMongoRepository.js        | 187 ++++++++++++++++++
 .../DeviceAttributeDatabaseRepository.js      |  33 ++--
 dist/repository/TingoDb.js                    |  72 +++++--
 dist/repository/UserDatabaseRepository.js     |  19 +-
 dist/repository/WebhookDatabaseRepository.js  |  29 +--
 src/exports.js                                |   2 -
 src/lib/promisify.js                          |   8 +-
 src/repository/BaseMongoRepository.js         |  65 ++++++
 .../DeviceAttributeDatabaseRepository.js      |  23 +--
 src/repository/TingoDb.js                     |  12 +-
 src/repository/UserDatabaseRepository.js      |  32 +--
 src/repository/WebhookDatabaseRepository.js   |  25 ++-
 src/types.js                                  |   5 +-
 15 files changed, 410 insertions(+), 115 deletions(-)
 create mode 100644 dist/repository/BaseMongoRepository.js
 create mode 100644 src/repository/BaseMongoRepository.js

diff --git a/dist/exports.js b/dist/exports.js
index f4eee3d9..a7506238 100644
--- a/dist/exports.js
+++ b/dist/exports.js
@@ -3,7 +3,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.settings = exports.promisifyByPrototype = exports.logger = exports.defaultBindings = exports.createApp = undefined;
+exports.settings = exports.logger = exports.defaultBindings = exports.createApp = undefined;
 
 var _logger = require('./lib/logger');
 
@@ -21,12 +21,9 @@ var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
-var _promisify = require('./lib/promisify');
-
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 exports.createApp = _app2.default;
 exports.defaultBindings = _defaultBindings2.default;
 exports.logger = _logger2.default;
-exports.promisifyByPrototype = _promisify.promisifyByPrototype;
 exports.settings = _settings2.default;
\ No newline at end of file
diff --git a/dist/lib/promisify.js b/dist/lib/promisify.js
index 36bd227e..aead0c0c 100644
--- a/dist/lib/promisify.js
+++ b/dist/lib/promisify.js
@@ -19,13 +19,13 @@ var _promise2 = _interopRequireDefault(_promise);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var promisify = exports.promisify = function promisify(func) {
-  for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
-    args[_key - 1] = arguments[_key];
+var promisify = exports.promisify = function promisify(object, fnName) {
+  for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+    args[_key - 2] = arguments[_key];
   }
 
   return new _promise2.default(function (resolve, reject) {
-    return func.apply(undefined, args.concat([function (error, result) {
+    return object[fnName].apply(object, args.concat([function (error, result) {
       if (error) {
         reject(error);
         return;
diff --git a/dist/repository/BaseMongoRepository.js b/dist/repository/BaseMongoRepository.js
new file mode 100644
index 00000000..71cae4fd
--- /dev/null
+++ b/dist/repository/BaseMongoRepository.js
@@ -0,0 +1,187 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _promisify = require('../lib/promisify');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var BaseMongoRepository = function BaseMongoRepository() {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, BaseMongoRepository);
+
+  this.insert = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collectionName, entity) {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              return _context.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
+                return (0, _promisify.promisify)(collection, 'insert', entity, { fullResult: true }).then(function (results) {
+                  return results[0];
+                });
+              }));
+
+            case 1:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x, _x2) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.find = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName) {
+      for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+        args[_key - 1] = arguments[_key];
+      }
+
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              return _context2.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
+                return (0, _promisify.promisify)(collection.find.apply(collection, args), 'toArray');
+              }).then(function (items) {
+                return items.map(function (item) {
+                  return (0, _extends3.default)({}, item, { id: item._id });
+                });
+              }));
+
+            case 1:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x3) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.findOne = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collectionName) {
+      for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+        args[_key2 - 1] = arguments[_key2];
+      }
+
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              return _context3.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
+                return _promisify.promisify.apply(undefined, [collection, 'findOne'].concat(args));
+              }));
+
+            case 1:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function (_x4) {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.findAndModify = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName) {
+      for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
+        args[_key3 - 1] = arguments[_key3];
+      }
+
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              return _context4.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
+                return _promisify.promisify.apply(undefined, [collection, 'findAndModify'].concat(args));
+              }));
+
+            case 1:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x5) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.remove = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collectionName, id) {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              return _context5.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
+                return (0, _promisify.promisify)(collection, 'remove', { _id: id });
+              }));
+
+            case 1:
+            case 'end':
+              return _context5.stop();
+          }
+        }
+      }, _callee5, _this);
+    }));
+
+    return function (_x6, _x7) {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.__runForCollection = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, callback) {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
+        while (1) {
+          switch (_context6.prev = _context6.next) {
+            case 0:
+              throw new Error('Not implemented ' + callback.toString());
+
+            case 1:
+            case 'end':
+              return _context6.stop();
+          }
+        }
+      }, _callee6, _this);
+    }));
+
+    return function (_x8, _x9) {
+      return _ref6.apply(this, arguments);
+    };
+  }();
+};
+
+exports.default = BaseMongoRepository;
\ No newline at end of file
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 711a45ed..76ae5b67 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -20,14 +20,13 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
-var _promisify = require('../lib/promisify');
-
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseRepository(database) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, DeviceAttributeDatabaseRepository);
+  this._collectionName = 'deviceAttributes';
   this.create = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
     return _regenerator2.default.wrap(function _callee$(_context) {
       while (1) {
@@ -50,7 +49,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._collection.findAndModify({ _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
@@ -75,7 +74,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context3.prev = _context3.next) {
             case 0:
               _context3.next = 2;
-              return _this._collection.remove({ _id: id });
+              return _this._database.remove(_this._collectionName, id);
 
             case 2:
               return _context3.abrupt('return', _context3.sent);
@@ -100,7 +99,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _this._collection.findOne({ _id: id, ownerID: userID });
+              return _this._database.findOne(_this._collectionName, { _id: id, ownerID: userID });
 
             case 2:
               return _context4.abrupt('return', !!_context4.sent);
@@ -127,19 +126,13 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context5.prev = _context5.next) {
             case 0:
               query = userID ? { ownerID: userID } : {};
-              _context5.t0 = _promisify.promisifyByPrototype;
-              _context5.next = 4;
-              return _this._collection.find(query);
-
-            case 4:
-              _context5.t1 = _context5.sent;
-              _context5.next = 7;
-              return (0, _context5.t0)(_context5.t1).toArray();
+              _context5.next = 3;
+              return _this._database.find(_this._collectionName, query);
 
-            case 7:
+            case 3:
               return _context5.abrupt('return', _context5.sent);
 
-            case 8:
+            case 4:
             case 'end':
               return _context5.stop();
           }
@@ -161,13 +154,9 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context6.prev = _context6.next) {
             case 0:
               query = userID ? { _id: id, ownerID: userID } : { _id: id };
-              _context6.next = 3;
-              return _this._collection.findOne(query);
+              return _context6.abrupt('return', _this._database.findOne(_this._collectionName, query));
 
-            case 3:
-              return _context6.abrupt('return', _context6.sent);
-
-            case 4:
+            case 2:
             case 'end':
               return _context6.stop();
           }
@@ -180,7 +169,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     };
   }();
 
-  this._collection = database.getCollection('deviceAttributes');
+  this._database = database;
 };
 
 exports.default = DeviceAttributeDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/TingoDb.js b/dist/repository/TingoDb.js
index eea6cd93..d004ce09 100644
--- a/dist/repository/TingoDb.js
+++ b/dist/repository/TingoDb.js
@@ -4,10 +4,30 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
 var _fs = require('fs');
 
 var _fs2 = _interopRequireDefault(_fs);
@@ -20,26 +40,54 @@ var _tingodb = require('tingodb');
 
 var _tingodb2 = _interopRequireDefault(_tingodb);
 
-var _promisify = require('../lib/promisify');
+var _BaseMongoRepository2 = require('./BaseMongoRepository');
+
+var _BaseMongoRepository3 = _interopRequireDefault(_BaseMongoRepository2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var TingoDb = function TingoDb(path, options) {
-  var _this = this;
+var TingoDb = function (_BaseMongoRepository) {
+  (0, _inherits3.default)(TingoDb, _BaseMongoRepository);
+
+  function TingoDb(path, options) {
+    var _this2 = this;
+
+    (0, _classCallCheck3.default)(this, TingoDb);
+
+    var _this = (0, _possibleConstructorReturn3.default)(this, (TingoDb.__proto__ || (0, _getPrototypeOf2.default)(TingoDb)).call(this));
+
+    _this.__runForCollection = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collectionName, callback) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                return _context.abrupt('return', callback(_this._database.collection(collectionName)));
+
+              case 1:
+              case 'end':
+                return _context.stop();
+            }
+          }
+        }, _callee, _this2);
+      }));
 
-  (0, _classCallCheck3.default)(this, TingoDb);
+      return function (_x, _x2) {
+        return _ref.apply(this, arguments);
+      };
+    }();
 
-  this.getCollection = function (collectionName) {
-    return (0, _promisify.promisifyByPrototype)(_this._database.collection(collectionName));
-  };
+    var Db = (0, _tingodb2.default)(options).Db;
 
-  var Db = (0, _tingodb2.default)(options).Db;
+    if (!_fs2.default.existsSync(path)) {
+      _mkdirp2.default.sync(path);
+    }
 
-  if (!_fs2.default.existsSync(path)) {
-    _mkdirp2.default.sync(path);
+    _this._database = new Db(path, {});
+    return _this;
   }
 
-  this._database = new Db(path, {});
-};
+  return TingoDb;
+}(_BaseMongoRepository3.default);
 
 exports.default = TingoDb;
\ No newline at end of file
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 0b0bf915..fb565068 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -34,6 +34,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, UserDatabaseRepository);
+  this._collectionName = 'users';
 
   this.createWithCredentials = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) {
@@ -62,10 +63,10 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
                 username: username
               };
               _context.next = 10;
-              return _this._collection.insert(modelToSave, { fullResult: true });
+              return _this._database.insert(_this._collectionName, modelToSave);
 
             case 10:
-              user = _context.sent[0];
+              user = _context.sent;
               return _context.abrupt('return', (0, _extends3.default)({}, user, { id: user._id.toString() }));
 
             case 12:
@@ -88,7 +89,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._collection.findAndModify({ _id: userID }, null, { $pull: { accessTokens: { accessToken: accessToken } } }, { new: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: userID }, null, { $pull: { accessTokens: { accessToken: accessToken } } }, { new: true });
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
@@ -113,7 +114,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context3.prev = _context3.next) {
             case 0:
               _context3.next = 2;
-              return _this._collection.remove({ _id: id });
+              return _this._database.remove(_this._collectionName, id);
 
             case 2:
               return _context3.abrupt('return', _context3.sent);
@@ -139,7 +140,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _this._collection.findOne({ 'accessTokens.accessToken': accessToken });
+              return _this._database.findOne(_this._collectionName, { 'accessTokens.accessToken': accessToken });
 
             case 2:
               user = _context4.sent;
@@ -166,7 +167,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
-              return _this._collection.findOne({ username: username });
+              return _this._database.findOne(_this._collectionName, { username: username });
 
             case 2:
               user = _context5.sent;
@@ -217,7 +218,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context7.prev = _context7.next) {
             case 0:
               _context7.next = 2;
-              return _this._collection.findAndModify({ _id: userID }, null, { $push: { accessTokens: tokenObject } }, { new: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: userID }, null, { $push: { accessTokens: tokenObject } }, { new: true });
 
             case 2:
               return _context7.abrupt('return', _context7.sent);
@@ -244,7 +245,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
             case 0:
               _context8.prev = 0;
               _context8.next = 3;
-              return _this._collection.findOne({ username: username });
+              return _this._database.findOne(_this._collectionName, { username: username });
 
             case 3:
               user = _context8.sent;
@@ -291,7 +292,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
     };
   }();
 
-  this._collection = database.getCollection('users');
+  this._database = database;
 };
 
 exports.default = UserDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index 5dc04ca9..b1a76321 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -20,14 +20,13 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
-var _promisify = require('../lib/promisify');
-
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, WebhookDatabaseRepository);
+  this._collectionName = 'webhooks';
 
   this.create = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
@@ -37,12 +36,12 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
           switch (_context.prev = _context.next) {
             case 0:
               _context.next = 2;
-              return _this._collection.insert((0, _extends3.default)({}, model, {
+              return _this._database.insert(_this._collectionName, (0, _extends3.default)({}, model, {
                 created_at: new Date()
-              }), { fullResult: true });
+              }));
 
             case 2:
-              webhook = _context.sent[0];
+              webhook = _context.sent;
               return _context.abrupt('return', (0, _extends3.default)({}, webhook, { id: webhook._id.toString() }));
 
             case 4:
@@ -64,7 +63,7 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
-              return _context2.abrupt('return', _this._collection.remove({ _id: id }));
+              return _context2.abrupt('return', _this._database.remove(_this._collectionName, id));
 
             case 1:
             case 'end':
@@ -88,19 +87,9 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
           switch (_context3.prev = _context3.next) {
             case 0:
               query = userID ? { ownerID: userID } : {};
-              _context3.t0 = _promisify.promisifyByPrototype;
-              _context3.next = 4;
-              return _this._collection.find(query);
-
-            case 4:
-              _context3.t1 = _context3.sent;
-              _context3.next = 7;
-              return (0, _context3.t0)(_context3.t1).toArray();
+              return _context3.abrupt('return', _this._database.find(_this._collectionName, query));
 
-            case 7:
-              return _context3.abrupt('return', _context3.sent);
-
-            case 8:
+            case 2:
             case 'end':
               return _context3.stop();
           }
@@ -123,7 +112,7 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
             case 0:
               query = userID ? { _id: id, ownerID: userID } : { _id: id };
               _context4.next = 3;
-              return _this._collection.findOne(query);
+              return _this._database.findOne(_this._collectionName, query);
 
             case 3:
               webhook = _context4.sent;
@@ -157,7 +146,7 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
     }, _callee5, _this);
   }));
 
-  this._collection = database.getCollection('webhooks');
+  this._database = database;
 };
 
 exports.default = WebhookDatabaseRepository;
\ No newline at end of file
diff --git a/src/exports.js b/src/exports.js
index b2a58137..008a4897 100644
--- a/src/exports.js
+++ b/src/exports.js
@@ -4,12 +4,10 @@ import logger from './lib/logger';
 import createApp from './app';
 import defaultBindings from './defaultBindings';
 import settings from './settings';
-import { promisifyByPrototype } from './lib/promisify';
 
 export {
   createApp,
   defaultBindings,
   logger,
-  promisifyByPrototype,
   settings,
 };
diff --git a/src/lib/promisify.js b/src/lib/promisify.js
index f6fce6be..53bf0d37 100644
--- a/src/lib/promisify.js
+++ b/src/lib/promisify.js
@@ -1,10 +1,14 @@
 // @flow
 
-export const promisify = (func: Function, ...args: Array): Promise<*> =>
+export const promisify = (
+  object: Object,
+  fnName: string,
+  ...args: Array
+): Promise<*> =>
   new Promise((
     resolve: (result: any) => void,
     reject: (error: Error) => void,
-  ): void => func(...args, (error: Error, result: any) => {
+  ): void => object[fnName](...args, (error: Error, result: any) => {
     if (error) {
       reject(error);
       return;
diff --git a/src/repository/BaseMongoRepository.js b/src/repository/BaseMongoRepository.js
new file mode 100644
index 00000000..36d9877c
--- /dev/null
+++ b/src/repository/BaseMongoRepository.js
@@ -0,0 +1,65 @@
+// @flow
+
+import { promisify } from '../lib/promisify';
+
+class BaseMongoRepository {
+  insert = async (
+    collectionName: string,
+    entity: Object,
+  ): Promise<*> => this.__runForCollection(
+      collectionName,
+      (collection: Object): Promise<*> => promisify(
+        collection,
+        'insert',
+        entity,
+        { fullResult: true },
+     ).then((results: Array<*>): Object => results[0]),
+    );
+
+  find = async (
+    collectionName: string,
+    ...args: Array
+  ): Promise<*> => this.__runForCollection(
+    collectionName,
+    (collection: Object): Promise<*> =>
+      promisify(collection.find(...args), 'toArray'),
+  ).then((items: Array<*>): Array<*> =>
+    items.map((item: Object): Object => ({ ...item, id: item._id })),
+  );
+
+  findOne = async (
+    collectionName: string,
+    ...args: Array
+  ): Promise<*> => this.__runForCollection(
+      collectionName,
+      (collection: Object): Promise<*> =>
+        promisify(collection, 'findOne', ...args),
+    );
+
+  findAndModify = async (
+    collectionName: string,
+    ...args: Array
+  ): Promise<*> => this.__runForCollection(
+      collectionName,
+      (collection: Object): Promise<*> =>
+        promisify(collection, 'findAndModify', ...args),
+    )
+
+  remove = async (
+    collectionName: string,
+    id: string,
+  ): Promise<*> => this.__runForCollection(
+      collectionName,
+      (collection: Object): Promise<*> =>
+        promisify(collection, 'remove', { _id: id }),
+    );
+
+  __runForCollection = async (
+    collectionName: string,
+    callback: (collection: Object) => Promise<*>,
+  ): Promise<*> => {
+    throw new Error(`Not implemented ${callback.toString()}`);
+  };
+}
+
+export default BaseMongoRepository;
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index f24b81cc..cf4eb26c 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -2,13 +2,12 @@
 
 import type { Database, DeviceAttributes } from '../types';
 
-import { promisifyByPrototype } from '../lib/promisify';
-
 class DeviceAttributeDatabaseRepository {
-  _collection: Object;
+  _database: Object;
+  _collectionName: string = 'deviceAttributes';
 
   constructor(database: Database) {
-    this._collection = database.getCollection('deviceAttributes');
+    this._database = database;
   }
 
   create = async (): Promise => {
@@ -16,7 +15,8 @@ class DeviceAttributeDatabaseRepository {
   };
 
   update = async (model: DeviceAttributes): Promise =>
-    await this._collection.findAndModify(
+    await this._database.findAndModify(
+      this._collectionName,
       { _id: model.deviceID },
       null,
       { $set: { ...model, _id: model.deviceID, timeStamp: new Date() } },
@@ -24,17 +24,18 @@ class DeviceAttributeDatabaseRepository {
     );
 
   deleteById = async (id: string): Promise =>
-    await this._collection.remove({ _id: id });
+    await this._database.remove(this._collectionName, id);
 
   doesUserHaveAccess = async (id: string, userID: string): Promise =>
-    !!(await this._collection.findOne({ _id: id, ownerID: userID }));
+    !!(await this._database.findOne(
+      this._collectionName,
+      { _id: id, ownerID: userID },
+    ));
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
 
-    return await (promisifyByPrototype(
-      await this._collection.find(query),
-    ).toArray());
+    return await this._database.find(this._collectionName, query);
   };
 
   getById = async (
@@ -43,7 +44,7 @@ class DeviceAttributeDatabaseRepository {
   ): Promise => {
     const query = userID ? { _id: id, ownerID: userID } : { _id: id };
 
-    return await this._collection.findOne(query);
+    return this._database.findOne(this._collectionName, query);
   }
 }
 
diff --git a/src/repository/TingoDb.js b/src/repository/TingoDb.js
index ccc7beac..4896f47b 100644
--- a/src/repository/TingoDb.js
+++ b/src/repository/TingoDb.js
@@ -3,12 +3,14 @@
 import fs from 'fs';
 import mkdirp from 'mkdirp';
 import tingoDb from 'tingodb';
-import { promisifyByPrototype } from '../lib/promisify';
+import BaseMongoRepository from './BaseMongoRepository';
 
-class TingoDb {
+class TingoDb extends BaseMongoRepository {
   _database: Object;
 
   constructor(path: string, options: Object) {
+    super();
+
     const Db = tingoDb(options).Db;
 
     if (!fs.existsSync(path)) {
@@ -18,8 +20,10 @@ class TingoDb {
     this._database = new Db(path, {});
   }
 
-  getCollection = (collectionName: string): Object =>
-    promisifyByPrototype(this._database.collection(collectionName));
+  __runForCollection = async (
+    collectionName: string,
+    callback: (collection: Object) => Promise<*>,
+  ): Promise<*> => callback(this._database.collection(collectionName));
 }
 
 export default TingoDb;
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index ac0261a9..d358462f 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -6,10 +6,11 @@ import PasswordHasher from '../lib/PasswordHasher';
 import HttpError from '../lib/HttpError';
 
 class UserDatabaseRepository {
-  _collection: Object;
+  _database: Object;
+  _collectionName: string = 'users';
 
   constructor(database: Database) {
-    this._collection = database.getCollection('users');
+    this._database = database;
   }
 
   createWithCredentials = async (userCredentials: UserCredentials): Promise => {
@@ -26,15 +27,16 @@ class UserDatabaseRepository {
       username,
     };
 
-    const user = (await this._collection.insert(
+    const user = await this._database.insert(
+      this._collectionName,
       modelToSave,
-      { fullResult: true },
-    ))[0];
+    );
     return { ...user, id: user._id.toString() };
   };
 
   deleteAccessToken = async (userID: string, accessToken: string): Promise =>
-    await this._collection.findAndModify(
+    await this._database.findAndModify(
+      this._collectionName,
       { _id: userID },
       null,
       { $pull: { accessTokens: { accessToken } } },
@@ -42,10 +44,11 @@ class UserDatabaseRepository {
     );
 
   deleteById = async (id: string): Promise =>
-    await this._collection.remove({ _id: id });
+    await this._database.remove(this._collectionName, id);
 
   getByAccessToken = async (accessToken: string): Promise => {
-    const user = await this._collection.findOne(
+    const user = await this._database.findOne(
+      this._collectionName,
       { 'accessTokens.accessToken': accessToken },
     );
 
@@ -53,7 +56,10 @@ class UserDatabaseRepository {
   };
 
   getByUsername = async (username: string): Promise => {
-    const user = await this._collection.findOne({ username });
+    const user = await this._database.findOne(
+      this._collectionName,
+      { username },
+    );
 
     return user ? { ...user, id: user._id.toString() } : null;
   };
@@ -64,7 +70,8 @@ class UserDatabaseRepository {
   saveAccessToken = async (
     userID: string,
     tokenObject: TokenObject,
-  ): Promise<*> => await this._collection.findAndModify(
+  ): Promise<*> => await this._database.findAndModify(
+    this._collectionName,
     { _id: userID },
     null,
     { $push: { accessTokens: tokenObject } },
@@ -73,7 +80,10 @@ class UserDatabaseRepository {
 
   validateLogin = async (username: string, password: string): Promise => {
     try {
-      const user = await this._collection.findOne({ username });
+      const user = await this._database.findOne(
+        this._collectionName,
+        { username },
+      );
 
       if (!user) {
         throw new HttpError('User doesn\'t exist', 404);
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index 7f200f6b..b6e4f595 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -2,40 +2,40 @@
 
 import type { Database, Webhook } from '../types';
 
-import { promisifyByPrototype } from '../lib/promisify';
-
 class WebhookDatabaseRepository {
-  _collection: Object;
+  _database: Object;
+  _collectionName: string = 'webhooks';
 
   constructor(database: Database) {
-    this._collection = database.getCollection('webhooks');
+    this._database = database;
   }
 
   create = async (model: $Shape): Promise => {
-    const webhook = (await this._collection.insert(
+    const webhook = await this._database.insert(
+      this._collectionName,
       {
         ...model,
         created_at: new Date(),
       },
-      { fullResult: true },
-    ))[0];
+    );
 
     return { ...webhook, id: webhook._id.toString() };
   };
 
   deleteById = async (id: string): Promise =>
-    this._collection.remove({ _id: id });
+    this._database.remove(this._collectionName, id);
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-    return await (promisifyByPrototype(
-      await this._collection.find(query),
-    ).toArray());
+    return this._database.find(this._collectionName, query);
   };
 
   getById = async (id: string, userID: ?string = null): Promise => {
     const query = userID ? { _id: id, ownerID: userID } : { _id: id };
-    const webhook = await this._collection.findOne(query);
+    const webhook = await this._database.findOne(
+      this._collectionName,
+      query,
+    );
 
     return webhook ? { ...webhook, id: webhook._id.toString() } : null;
   };
@@ -46,4 +46,3 @@ class WebhookDatabaseRepository {
 }
 
 export default WebhookDatabaseRepository;
-
diff --git a/src/types.js b/src/types.js
index 6d8ec0e2..fc3be4a3 100644
--- a/src/types.js
+++ b/src/types.js
@@ -3,7 +3,10 @@
 import type { File } from 'express';
 
 export type Database = {
-  getCollection(collectionName: string): Object,
+  find: (collectionName: string, ...args: Array) => Promise<*>,
+  findOne: (collectionName: string, ...args: Array) => Promise<*>,
+  findAndModify: (collectionName: string, ...args: Array) => Promise<*>,
+  remove: (collectionName: string, id: string) => Promise<*>,
 };
 
 export type Webhook = {

From 2055ce1c785b3e0f074c5a14e32c1d4dbe58ed88 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 20 May 2017 17:41:34 -0700
Subject: [PATCH 366/504] Added a mongo connector. Did some cleanup

---
 dist/exports.js                              | 12 +++-
 dist/repository/BaseMongoRepository.js       |  6 +-
 dist/repository/MongoDb.js                   | 76 ++++++++++++++++++++
 dist/repository/UserDatabaseRepository.js    | 31 ++++----
 dist/repository/WebhookDatabaseRepository.js | 11 +--
 package.json                                 |  3 +-
 src/exports.js                               |  5 ++
 src/repository/BaseMongoRepository.js        |  7 +-
 src/repository/MongoDb.js                    | 24 +++++++
 src/repository/UserDatabaseRepository.js     | 23 +++---
 src/repository/WebhookDatabaseRepository.js  |  4 +-
 11 files changed, 164 insertions(+), 38 deletions(-)
 create mode 100644 dist/repository/MongoDb.js
 create mode 100644 src/repository/MongoDb.js

diff --git a/dist/exports.js b/dist/exports.js
index a7506238..b343774a 100644
--- a/dist/exports.js
+++ b/dist/exports.js
@@ -3,7 +3,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.settings = exports.logger = exports.defaultBindings = exports.createApp = undefined;
+exports.settings = exports.logger = exports.defaultBindings = exports.createApp = exports.MongoDb = exports.BaseMongoRepository = undefined;
 
 var _logger = require('./lib/logger');
 
@@ -21,8 +21,18 @@ var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
+var _BaseMongoRepository = require('./repository/BaseMongoRepository');
+
+var _BaseMongoRepository2 = _interopRequireDefault(_BaseMongoRepository);
+
+var _MongoDb = require('./repository/MongoDb');
+
+var _MongoDb2 = _interopRequireDefault(_MongoDb);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+exports.BaseMongoRepository = _BaseMongoRepository2.default;
+exports.MongoDb = _MongoDb2.default;
 exports.createApp = _app2.default;
 exports.defaultBindings = _defaultBindings2.default;
 exports.logger = _logger2.default;
diff --git a/dist/repository/BaseMongoRepository.js b/dist/repository/BaseMongoRepository.js
index 71cae4fd..7a5b24a5 100644
--- a/dist/repository/BaseMongoRepository.js
+++ b/dist/repository/BaseMongoRepository.js
@@ -68,7 +68,7 @@ var BaseMongoRepository = function BaseMongoRepository() {
                 return (0, _promisify.promisify)(collection.find.apply(collection, args), 'toArray');
               }).then(function (items) {
                 return items.map(function (item) {
-                  return (0, _extends3.default)({}, item, { id: item._id });
+                  return item ? (0, _extends3.default)({}, item, { id: item._id }) : null;
                 });
               }));
 
@@ -97,6 +97,8 @@ var BaseMongoRepository = function BaseMongoRepository() {
             case 0:
               return _context3.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
                 return _promisify.promisify.apply(undefined, [collection, 'findOne'].concat(args));
+              }).then(function (item) {
+                return item ? (0, _extends3.default)({}, item, { id: item._id }) : null;
               }));
 
             case 1:
@@ -124,6 +126,8 @@ var BaseMongoRepository = function BaseMongoRepository() {
             case 0:
               return _context4.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
                 return _promisify.promisify.apply(undefined, [collection, 'findAndModify'].concat(args));
+              }).then(function (item) {
+                return (0, _extends3.default)({}, item, { id: item._id });
               }));
 
             case 1:
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
new file mode 100644
index 00000000..ba1af556
--- /dev/null
+++ b/dist/repository/MongoDb.js
@@ -0,0 +1,76 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _BaseMongoRepository2 = require('./BaseMongoRepository');
+
+var _BaseMongoRepository3 = _interopRequireDefault(_BaseMongoRepository2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var MongoDb = function (_BaseMongoRepository) {
+  (0, _inherits3.default)(MongoDb, _BaseMongoRepository);
+
+  function MongoDb(database) {
+    var _this2 = this;
+
+    (0, _classCallCheck3.default)(this, MongoDb);
+
+    var _this = (0, _possibleConstructorReturn3.default)(this, (MongoDb.__proto__ || (0, _getPrototypeOf2.default)(MongoDb)).call(this));
+
+    _this.__runForCollection = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collectionName, callback) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                console.log(_this._database.collection(collectionName) !== null);
+                return _context.abrupt('return', callback(_this._database.collection(collectionName)));
+
+              case 2:
+              case 'end':
+                return _context.stop();
+            }
+          }
+        }, _callee, _this2);
+      }));
+
+      return function (_x, _x2) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this._database = database;
+    return _this;
+  }
+
+  return MongoDb;
+}(_BaseMongoRepository3.default);
+
+exports.default = MongoDb;
\ No newline at end of file
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index fb565068..e8e82fe8 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -140,13 +140,26 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, { 'accessTokens.accessToken': accessToken });
+              return _this._database.findOne(_this._collectionName, { accessTokens: { $elemMatch: { accessToken: accessToken } } });
 
             case 2:
               user = _context4.sent;
-              return _context4.abrupt('return', user ? (0, _extends3.default)({}, user, { id: user._id.toString() }) : null);
 
-            case 4:
+              if (user) {
+                _context4.next = 7;
+                break;
+              }
+
+              _context4.next = 6;
+              return _this._database.findOne(_this._collectionName, { 'accessTokens.accessToken': accessToken });
+
+            case 6:
+              user = _context4.sent;
+
+            case 7:
+              return _context4.abrupt('return', user);
+
+            case 8:
             case 'end':
               return _context4.stop();
           }
@@ -161,19 +174,13 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
 
   this.getByUsername = function () {
     var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(username) {
-      var user;
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
-              _context5.next = 2;
-              return _this._database.findOne(_this._collectionName, { username: username });
-
-            case 2:
-              user = _context5.sent;
-              return _context5.abrupt('return', user ? (0, _extends3.default)({}, user, { id: user._id.toString() }) : null);
+              return _context5.abrupt('return', _this._database.findOne(_this._collectionName, { username: username }));
 
-            case 4:
+            case 1:
             case 'end':
               return _context5.stop();
           }
@@ -272,7 +279,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
               throw new _HttpError2.default('Wrong password');
 
             case 11:
-              return _context8.abrupt('return', (0, _extends3.default)({}, user, { id: user._id.toString() }));
+              return _context8.abrupt('return', user);
 
             case 14:
               _context8.prev = 14;
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index b1a76321..e4dd6d05 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -105,20 +105,15 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
   this.getById = function () {
     var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
       var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-      var query, webhook;
+      var query;
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
               query = userID ? { _id: id, ownerID: userID } : { _id: id };
-              _context4.next = 3;
-              return _this._database.findOne(_this._collectionName, query);
-
-            case 3:
-              webhook = _context4.sent;
-              return _context4.abrupt('return', webhook ? (0, _extends3.default)({}, webhook, { id: webhook._id.toString() }) : null);
+              return _context4.abrupt('return', _this._database.findOne(_this._collectionName, query));
 
-            case 5:
+            case 2:
             case 'end':
               return _context4.stop();
           }
diff --git a/package.json b/package.json
index 75001512..42357fe7 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,8 @@
     "start:prod": "npm run build && node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
-    "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries"
+    "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",
+    "watch": "babel ./src --out-dir ./dist --watch"
   },
   "pre-commit": [
     "lint",
diff --git a/src/exports.js b/src/exports.js
index 008a4897..95668014 100644
--- a/src/exports.js
+++ b/src/exports.js
@@ -5,7 +5,12 @@ import createApp from './app';
 import defaultBindings from './defaultBindings';
 import settings from './settings';
 
+import BaseMongoRepository from './repository/BaseMongoRepository';
+import MongoDb from './repository/MongoDb';
+
 export {
+  BaseMongoRepository,
+  MongoDb,
   createApp,
   defaultBindings,
   logger,
diff --git a/src/repository/BaseMongoRepository.js b/src/repository/BaseMongoRepository.js
index 36d9877c..48e6e02c 100644
--- a/src/repository/BaseMongoRepository.js
+++ b/src/repository/BaseMongoRepository.js
@@ -24,7 +24,8 @@ class BaseMongoRepository {
     (collection: Object): Promise<*> =>
       promisify(collection.find(...args), 'toArray'),
   ).then((items: Array<*>): Array<*> =>
-    items.map((item: Object): Object => ({ ...item, id: item._id })),
+    items.map((item: Object): Object =>
+      (item ? { ...item, id: item._id } : null)),
   );
 
   findOne = async (
@@ -34,7 +35,7 @@ class BaseMongoRepository {
       collectionName,
       (collection: Object): Promise<*> =>
         promisify(collection, 'findOne', ...args),
-    );
+    ).then((item: Object): Object => (item ? { ...item, id: item._id } : null));
 
   findAndModify = async (
     collectionName: string,
@@ -43,7 +44,7 @@ class BaseMongoRepository {
       collectionName,
       (collection: Object): Promise<*> =>
         promisify(collection, 'findAndModify', ...args),
-    )
+    ).then((item: Object): Object => ({ ...item, id: item._id }));
 
   remove = async (
     collectionName: string,
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
new file mode 100644
index 00000000..661fc352
--- /dev/null
+++ b/src/repository/MongoDb.js
@@ -0,0 +1,24 @@
+// @flow
+
+import BaseMongoRepository from './BaseMongoRepository';
+
+class MongoDb extends BaseMongoRepository {
+  _databasePromise: Promise;
+  _database: Object;
+
+  constructor(database: Object) {
+    super();
+
+    this._database = database;
+  }
+
+  __runForCollection = async (
+    collectionName: string,
+    callback: (collection: Object) => Promise<*>,
+  ): Promise<*> => {
+    console.log(this._database.collection(collectionName) !== null);
+    return callback(this._database.collection(collectionName));
+  }
+}
+
+export default MongoDb;
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index d358462f..2f112d8f 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -47,23 +47,28 @@ class UserDatabaseRepository {
     await this._database.remove(this._collectionName, id);
 
   getByAccessToken = async (accessToken: string): Promise => {
-    const user = await this._database.findOne(
+    let user = await this._database.findOne(
       this._collectionName,
-      { 'accessTokens.accessToken': accessToken },
+      { accessTokens: { $elemMatch: { accessToken } } },
     );
 
-    return user ? { ...user, id: user._id.toString() } : null;
+    if (!user) {
+      // The newer query only works on mongo so we run this for tingo.
+      user = await this._database.findOne(
+        this._collectionName,
+        { 'accessTokens.accessToken': accessToken },
+      );
+    }
+
+    return user;
   };
 
-  getByUsername = async (username: string): Promise => {
-    const user = await this._database.findOne(
+  getByUsername = async (username: string): Promise =>
+    this._database.findOne(
       this._collectionName,
       { username },
     );
 
-    return user ? { ...user, id: user._id.toString() } : null;
-  };
-
   isUserNameInUse = async (username: string): Promise =>
     !!(await this.getByUsername(username));
 
@@ -94,7 +99,7 @@ class UserDatabaseRepository {
         throw new HttpError('Wrong password');
       }
 
-      return { ...user, id: user._id.toString() };
+      return user;
     } catch (error) {
       throw error;
     }
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index b6e4f595..58a4cfdd 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -32,12 +32,10 @@ class WebhookDatabaseRepository {
 
   getById = async (id: string, userID: ?string = null): Promise => {
     const query = userID ? { _id: id, ownerID: userID } : { _id: id };
-    const webhook = await this._database.findOne(
+    return this._database.findOne(
       this._collectionName,
       query,
     );
-
-    return webhook ? { ...webhook, id: webhook._id.toString() } : null;
   };
 
   update = async (): Promise => {

From 99533a77f22f3c7ac544835a604ba9d1e39cfecb Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 21 May 2017 13:35:47 +0200
Subject: [PATCH 367/504] made it work for tingo and mongo

---
 dist/repository/BaseMongoRepository.js        | 328 +++++++++++++-----
 .../DeviceAttributeDatabaseRepository.js      |   8 +-
 dist/repository/MongoDb.js                    |   3 +-
 dist/repository/TingoDb.js                    | 278 ++++++++++++++-
 dist/repository/UserDatabaseRepository.js     |  21 +-
 dist/repository/WebhookDatabaseRepository.js  |  32 +-
 package.json                                  |   1 +
 src/repository/BaseMongoRepository.js         | 109 ++++--
 .../DeviceAttributeDatabaseRepository.js      |   4 +-
 src/repository/MongoDb.js                     |   6 +-
 src/repository/TingoDb.js                     |  60 ++++
 src/repository/UserDatabaseRepository.js      |   5 +-
 src/repository/WebhookDatabaseRepository.js   |  16 +-
 13 files changed, 696 insertions(+), 175 deletions(-)

diff --git a/dist/repository/BaseMongoRepository.js b/dist/repository/BaseMongoRepository.js
index 7a5b24a5..32f47006 100644
--- a/dist/repository/BaseMongoRepository.js
+++ b/dist/repository/BaseMongoRepository.js
@@ -8,6 +8,10 @@ var _extends2 = require('babel-runtime/helpers/extends');
 
 var _extends3 = _interopRequireDefault(_extends2);
 
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -20,33 +24,75 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
-var _promisify = require('../lib/promisify');
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _mongodb = require('mongodb');
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var deepToObjectIdCast = function deepToObjectIdCast(node) {
+  (0, _keys2.default)(node).forEach(function (key) {
+    if (node[key] === Object(node[key])) {
+      deepToObjectIdCast(node[key]);
+    }
+    if (key === '_id') {
+      // eslint-disable-next-line
+      node[key] = new _mongodb.ObjectId(node[key]);
+    }
+  });
+  return node;
+};
+
 var BaseMongoRepository = function BaseMongoRepository() {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, BaseMongoRepository);
 
-  this.insert = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collectionName, entity) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
+  this.insertOne = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
-          switch (_context.prev = _context.next) {
+          switch (_context2.prev = _context2.next) {
             case 0:
-              return _context.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
-                return (0, _promisify.promisify)(collection, 'insert', entity, { fullResult: true }).then(function (results) {
-                  return results[0];
-                });
-              }));
-
-            case 1:
+              _context2.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
+                  var insertResult;
+                  return _regenerator2.default.wrap(function _callee$(_context) {
+                    while (1) {
+                      switch (_context.prev = _context.next) {
+                        case 0:
+                          _context.next = 2;
+                          return collection.insertOne(entity);
+
+                        case 2:
+                          insertResult = _context.sent;
+                          return _context.abrupt('return', _this.__translateResultItem(insertResult.ops[0]));
+
+                        case 4:
+                        case 'end':
+                          return _context.stop();
+                      }
+                    }
+                  }, _callee, _this);
+                }));
+
+                return function (_x3) {
+                  return _ref2.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
             case 'end':
-              return _context.stop();
+              return _context2.stop();
           }
         }
-      }, _callee, _this);
+      }, _callee2, _this);
     }));
 
     return function (_x, _x2) {
@@ -55,137 +101,249 @@ var BaseMongoRepository = function BaseMongoRepository() {
   }();
 
   this.find = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName) {
-      for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
-        args[_key - 1] = arguments[_key];
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
+      for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+        args[_key - 2] = arguments[_key];
       }
 
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
-          switch (_context2.prev = _context2.next) {
+          switch (_context4.prev = _context4.next) {
             case 0:
-              return _context2.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
-                return (0, _promisify.promisify)(collection.find.apply(collection, args), 'toArray');
-              }).then(function (items) {
-                return items.map(function (item) {
-                  return item ? (0, _extends3.default)({}, item, { id: item._id }) : null;
-                });
-              }));
-
-            case 1:
+              _context4.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
+                  var resultItems;
+                  return _regenerator2.default.wrap(function _callee3$(_context3) {
+                    while (1) {
+                      switch (_context3.prev = _context3.next) {
+                        case 0:
+                          _context3.next = 2;
+                          return collection.find.apply(collection, [_this.__translateQuery(query)].concat(args)).toArray();
+
+                        case 2:
+                          resultItems = _context3.sent;
+                          return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
+
+                        case 4:
+                        case 'end':
+                          return _context3.stop();
+                      }
+                    }
+                  }, _callee3, _this);
+                }));
+
+                return function (_x6) {
+                  return _ref4.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 3:
             case 'end':
-              return _context2.stop();
+              return _context4.stop();
           }
         }
-      }, _callee2, _this);
+      }, _callee4, _this);
     }));
 
-    return function (_x3) {
-      return _ref2.apply(this, arguments);
+    return function (_x4, _x5) {
+      return _ref3.apply(this, arguments);
     };
   }();
 
-  this.findOne = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collectionName) {
-      for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
-        args[_key2 - 1] = arguments[_key2];
+  this.findAndModify = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, sort, updateQuery) {
+      for (var _len2 = arguments.length, args = Array(_len2 > 4 ? _len2 - 4 : 0), _key2 = 4; _key2 < _len2; _key2++) {
+        args[_key2 - 4] = arguments[_key2];
       }
 
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
-          switch (_context3.prev = _context3.next) {
+          switch (_context6.prev = _context6.next) {
             case 0:
-              return _context3.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
-                return _promisify.promisify.apply(undefined, [collection, 'findOne'].concat(args));
-              }).then(function (item) {
-                return item ? (0, _extends3.default)({}, item, { id: item._id }) : null;
-              }));
-
-            case 1:
+              _context6.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
+                  var modifyResult;
+                  return _regenerator2.default.wrap(function _callee5$(_context5) {
+                    while (1) {
+                      switch (_context5.prev = _context5.next) {
+                        case 0:
+                          _context5.next = 2;
+                          return collection.findAndModify.apply(collection, [_this.__translateQuery(query), sort, _this.__translateQuery(updateQuery)].concat(args));
+
+                        case 2:
+                          modifyResult = _context5.sent;
+                          return _context5.abrupt('return', _this.__translateResultItem(modifyResult.value));
+
+                        case 4:
+                        case 'end':
+                          return _context5.stop();
+                      }
+                    }
+                  }, _callee5, _this);
+                }));
+
+                return function (_x11) {
+                  return _ref6.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context6.abrupt('return', _context6.sent);
+
+            case 3:
             case 'end':
-              return _context3.stop();
+              return _context6.stop();
           }
         }
-      }, _callee3, _this);
+      }, _callee6, _this);
     }));
 
-    return function (_x4) {
-      return _ref3.apply(this, arguments);
+    return function (_x7, _x8, _x9, _x10) {
+      return _ref5.apply(this, arguments);
     };
   }();
 
-  this.findAndModify = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName) {
-      for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
-        args[_key3 - 1] = arguments[_key3];
+  this.findOne = function () {
+    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
+      for (var _len3 = arguments.length, args = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
+        args[_key3 - 2] = arguments[_key3];
       }
 
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
+      return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
-          switch (_context4.prev = _context4.next) {
+          switch (_context8.prev = _context8.next) {
             case 0:
-              return _context4.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
-                return _promisify.promisify.apply(undefined, [collection, 'findAndModify'].concat(args));
-              }).then(function (item) {
-                return (0, _extends3.default)({}, item, { id: item._id });
-              }));
-
-            case 1:
+              _context8.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
+                  var resultItem;
+                  return _regenerator2.default.wrap(function _callee7$(_context7) {
+                    while (1) {
+                      switch (_context7.prev = _context7.next) {
+                        case 0:
+                          _context7.next = 2;
+                          return collection.findOne.apply(collection, [_this.__translateQuery(query)].concat(args));
+
+                        case 2:
+                          resultItem = _context7.sent;
+                          return _context7.abrupt('return', _this.__translateResultItem(resultItem));
+
+                        case 4:
+                        case 'end':
+                          return _context7.stop();
+                      }
+                    }
+                  }, _callee7, _this);
+                }));
+
+                return function (_x14) {
+                  return _ref8.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context8.abrupt('return', _context8.sent);
+
+            case 3:
             case 'end':
-              return _context4.stop();
+              return _context8.stop();
           }
         }
-      }, _callee4, _this);
+      }, _callee8, _this);
     }));
 
-    return function (_x5) {
-      return _ref4.apply(this, arguments);
+    return function (_x12, _x13) {
+      return _ref7.apply(this, arguments);
     };
   }();
 
   this.remove = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collectionName, id) {
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
+    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, id) {
+      return _regenerator2.default.wrap(function _callee10$(_context10) {
         while (1) {
-          switch (_context5.prev = _context5.next) {
+          switch (_context10.prev = _context10.next) {
             case 0:
-              return _context5.abrupt('return', _this.__runForCollection(collectionName, function (collection) {
-                return (0, _promisify.promisify)(collection, 'remove', { _id: id });
-              }));
-
-            case 1:
+              _context10.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
+                  return _regenerator2.default.wrap(function _callee9$(_context9) {
+                    while (1) {
+                      switch (_context9.prev = _context9.next) {
+                        case 0:
+                          _context9.next = 2;
+                          return collection.remove(_this.__translateQuery({ _id: id }));
+
+                        case 2:
+                          return _context9.abrupt('return', _context9.sent);
+
+                        case 3:
+                        case 'end':
+                          return _context9.stop();
+                      }
+                    }
+                  }, _callee9, _this);
+                }));
+
+                return function (_x17) {
+                  return _ref10.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context10.abrupt('return', _context10.sent);
+
+            case 3:
             case 'end':
-              return _context5.stop();
+              return _context10.stop();
           }
         }
-      }, _callee5, _this);
+      }, _callee10, _this);
     }));
 
-    return function (_x6, _x7) {
-      return _ref5.apply(this, arguments);
+    return function (_x15, _x16) {
+      return _ref9.apply(this, arguments);
     };
   }();
 
   this.__runForCollection = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, callback) {
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
+    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
+      return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
-          switch (_context6.prev = _context6.next) {
+          switch (_context11.prev = _context11.next) {
             case 0:
               throw new Error('Not implemented ' + callback.toString());
 
             case 1:
             case 'end':
-              return _context6.stop();
+              return _context11.stop();
           }
         }
-      }, _callee6, _this);
+      }, _callee11, _this);
     }));
 
-    return function (_x8, _x9) {
-      return _ref6.apply(this, arguments);
+    return function (_x18, _x19) {
+      return _ref11.apply(this, arguments);
     };
   }();
+
+  this.__translateQuery = function (query) {
+    return deepToObjectIdCast(query);
+  };
+
+  this.__translateResultItem = function (item) {
+    if (!item) {
+      return null;
+    }
+    var _id = item._id,
+        otherProps = (0, _objectWithoutProperties3.default)(item, ['_id']);
+
+    return (0, _extends3.default)({}, otherProps, { id: _id.toString() });
+  };
 };
 
 exports.default = BaseMongoRepository;
\ No newline at end of file
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 76ae5b67..ddfc1dc7 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -154,9 +154,13 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context6.prev = _context6.next) {
             case 0:
               query = userID ? { _id: id, ownerID: userID } : { _id: id };
-              return _context6.abrupt('return', _this._database.findOne(_this._collectionName, query));
+              _context6.next = 3;
+              return _this._database.findOne(_this._collectionName, query);
 
-            case 2:
+            case 3:
+              return _context6.abrupt('return', _context6.sent);
+
+            case 4:
             case 'end':
               return _context6.stop();
           }
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index ba1af556..a256a111 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -50,10 +50,9 @@ var MongoDb = function (_BaseMongoRepository) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                console.log(_this._database.collection(collectionName) !== null);
                 return _context.abrupt('return', callback(_this._database.collection(collectionName)));
 
-              case 2:
+              case 1:
               case 'end':
                 return _context.stop();
             }
diff --git a/dist/repository/TingoDb.js b/dist/repository/TingoDb.js
index d004ce09..bf7121f0 100644
--- a/dist/repository/TingoDb.js
+++ b/dist/repository/TingoDb.js
@@ -40,6 +40,8 @@ var _tingodb = require('tingodb');
 
 var _tingodb2 = _interopRequireDefault(_tingodb);
 
+var _promisify = require('../lib/promisify');
+
 var _BaseMongoRepository2 = require('./BaseMongoRepository');
 
 var _BaseMongoRepository3 = _interopRequireDefault(_BaseMongoRepository2);
@@ -56,24 +58,284 @@ var TingoDb = function (_BaseMongoRepository) {
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (TingoDb.__proto__ || (0, _getPrototypeOf2.default)(TingoDb)).call(this));
 
+    _this.insertOne = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
+                    var insertResults;
+                    return _regenerator2.default.wrap(function _callee$(_context) {
+                      while (1) {
+                        switch (_context.prev = _context.next) {
+                          case 0:
+                            _context.next = 2;
+                            return (0, _promisify.promisify)(collection, 'insert', entity, { fullResult: true });
+
+                          case 2:
+                            insertResults = _context.sent;
+                            return _context.abrupt('return', _this.__translateResultItem(insertResults[0]));
+
+                          case 4:
+                          case 'end':
+                            return _context.stop();
+                        }
+                      }
+                    }, _callee, _this2);
+                  }));
+
+                  return function (_x3) {
+                    return _ref2.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x, _x2) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.find = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName) {
+        for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+          args[_key - 1] = arguments[_key];
+        }
+
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
+                    var resultItems;
+                    return _regenerator2.default.wrap(function _callee3$(_context3) {
+                      while (1) {
+                        switch (_context3.prev = _context3.next) {
+                          case 0:
+                            _context3.next = 2;
+                            return (0, _promisify.promisify)(collection.find.apply(collection, args), 'toArray');
+
+                          case 2:
+                            resultItems = _context3.sent;
+                            return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
+
+                          case 4:
+                          case 'end':
+                            return _context3.stop();
+                        }
+                      }
+                    }, _callee3, _this2);
+                  }));
+
+                  return function (_x5) {
+                    return _ref4.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4) {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.findAndModify = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName) {
+        for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+          args[_key2 - 1] = arguments[_key2];
+        }
+
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                _context6.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
+                    var modifiedItem;
+                    return _regenerator2.default.wrap(function _callee5$(_context5) {
+                      while (1) {
+                        switch (_context5.prev = _context5.next) {
+                          case 0:
+                            _context5.next = 2;
+                            return _promisify.promisify.apply(undefined, [collection, 'findAndModify'].concat(args));
+
+                          case 2:
+                            modifiedItem = _context5.sent;
+                            return _context5.abrupt('return', _this.__translateResultItem(modifiedItem));
+
+                          case 4:
+                          case 'end':
+                            return _context5.stop();
+                        }
+                      }
+                    }, _callee5, _this2);
+                  }));
+
+                  return function (_x7) {
+                    return _ref6.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context6.abrupt('return', _context6.sent);
+
+              case 3:
+              case 'end':
+                return _context6.stop();
+            }
+          }
+        }, _callee6, _this2);
+      }));
+
+      return function (_x6) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
+
+    _this.findOne = function () {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName) {
+        for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
+          args[_key3 - 1] = arguments[_key3];
+        }
+
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
+          while (1) {
+            switch (_context8.prev = _context8.next) {
+              case 0:
+                _context8.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
+                    var resultItem;
+                    return _regenerator2.default.wrap(function _callee7$(_context7) {
+                      while (1) {
+                        switch (_context7.prev = _context7.next) {
+                          case 0:
+                            _context7.next = 2;
+                            return _promisify.promisify.apply(undefined, [collection, 'findOne'].concat(args));
+
+                          case 2:
+                            resultItem = _context7.sent;
+                            return _context7.abrupt('return', _this.__translateResultItem(resultItem));
+
+                          case 4:
+                          case 'end':
+                            return _context7.stop();
+                        }
+                      }
+                    }, _callee7, _this2);
+                  }));
+
+                  return function (_x9) {
+                    return _ref8.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context8.abrupt('return', _context8.sent);
+
+              case 3:
+              case 'end':
+                return _context8.stop();
+            }
+          }
+        }, _callee8, _this2);
+      }));
+
+      return function (_x8) {
+        return _ref7.apply(this, arguments);
+      };
+    }();
+
+    _this.remove = function () {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, id) {
+        return _regenerator2.default.wrap(function _callee10$(_context10) {
+          while (1) {
+            switch (_context10.prev = _context10.next) {
+              case 0:
+                _context10.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
+                    return _regenerator2.default.wrap(function _callee9$(_context9) {
+                      while (1) {
+                        switch (_context9.prev = _context9.next) {
+                          case 0:
+                            _context9.next = 2;
+                            return (0, _promisify.promisify)(collection, 'remove', { _id: id });
+
+                          case 2:
+                            return _context9.abrupt('return', _context9.sent);
+
+                          case 3:
+                          case 'end':
+                            return _context9.stop();
+                        }
+                      }
+                    }, _callee9, _this2);
+                  }));
+
+                  return function (_x12) {
+                    return _ref10.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context10.abrupt('return', _context10.sent);
+
+              case 3:
+              case 'end':
+                return _context10.stop();
+            }
+          }
+        }, _callee10, _this2);
+      }));
+
+      return function (_x10, _x11) {
+        return _ref9.apply(this, arguments);
+      };
+    }();
+
     _this.__runForCollection = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collectionName, callback) {
-        return _regenerator2.default.wrap(function _callee$(_context) {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
+        return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
-            switch (_context.prev = _context.next) {
+            switch (_context11.prev = _context11.next) {
               case 0:
-                return _context.abrupt('return', callback(_this._database.collection(collectionName)));
+                return _context11.abrupt('return', callback(_this._database.collection(collectionName)));
 
               case 1:
               case 'end':
-                return _context.stop();
+                return _context11.stop();
             }
           }
-        }, _callee, _this2);
+        }, _callee11, _this2);
       }));
 
-      return function (_x, _x2) {
-        return _ref.apply(this, arguments);
+      return function (_x13, _x14) {
+        return _ref11.apply(this, arguments);
       };
     }();
 
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index e8e82fe8..0c0592ea 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -8,10 +8,6 @@ var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
 
-var _extends2 = require('babel-runtime/helpers/extends');
-
-var _extends3 = _interopRequireDefault(_extends2);
-
 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
@@ -38,7 +34,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
 
   this.createWithCredentials = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) {
-      var username, password, salt, passwordHash, modelToSave, user;
+      var username, password, salt, passwordHash, modelToSave;
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -63,13 +59,12 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
                 username: username
               };
               _context.next = 10;
-              return _this._database.insert(_this._collectionName, modelToSave);
+              return _this._database.insertOne(_this._collectionName, modelToSave);
 
             case 10:
-              user = _context.sent;
-              return _context.abrupt('return', (0, _extends3.default)({}, user, { id: user._id.toString() }));
+              return _context.abrupt('return', _context.sent);
 
-            case 12:
+            case 11:
             case 'end':
               return _context.stop();
           }
@@ -178,9 +173,13 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
-              return _context5.abrupt('return', _this._database.findOne(_this._collectionName, { username: username }));
+              _context5.next = 2;
+              return _this._database.findOne(_this._collectionName, { username: username });
 
-            case 1:
+            case 2:
+              return _context5.abrupt('return', _context5.sent);
+
+            case 3:
             case 'end':
               return _context5.stop();
           }
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index e4dd6d05..dae0a545 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -30,21 +30,19 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
 
   this.create = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
-      var webhook;
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
             case 0:
               _context.next = 2;
-              return _this._database.insert(_this._collectionName, (0, _extends3.default)({}, model, {
+              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
                 created_at: new Date()
               }));
 
             case 2:
-              webhook = _context.sent;
-              return _context.abrupt('return', (0, _extends3.default)({}, webhook, { id: webhook._id.toString() }));
+              return _context.abrupt('return', _context.sent);
 
-            case 4:
+            case 3:
             case 'end':
               return _context.stop();
           }
@@ -63,9 +61,13 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
-              return _context2.abrupt('return', _this._database.remove(_this._collectionName, id));
+              _context2.next = 2;
+              return _this._database.remove(_this._collectionName, id);
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
 
-            case 1:
+            case 3:
             case 'end':
               return _context2.stop();
           }
@@ -87,9 +89,13 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
           switch (_context3.prev = _context3.next) {
             case 0:
               query = userID ? { ownerID: userID } : {};
-              return _context3.abrupt('return', _this._database.find(_this._collectionName, query));
+              _context3.next = 3;
+              return _this._database.find(_this._collectionName, query);
 
-            case 2:
+            case 3:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 4:
             case 'end':
               return _context3.stop();
           }
@@ -111,9 +117,13 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
           switch (_context4.prev = _context4.next) {
             case 0:
               query = userID ? { _id: id, ownerID: userID } : { _id: id };
-              return _context4.abrupt('return', _this._database.findOne(_this._collectionName, query));
+              _context4.next = 3;
+              return _this._database.findOne(_this._collectionName, query);
 
-            case 2:
+            case 3:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 4:
             case 'end':
               return _context4.stop();
           }
diff --git a/package.json b/package.json
index 42357fe7..7e0fa2a5 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
     "lodash": "^4.17.4",
     "mkdirp": "^0.5.1",
     "moment": "*",
+    "mongodb": "^2.2.26",
     "morgan": "^1.7.0",
     "multer": "^1.2.1",
     "nullthrows": "^1.0.0",
diff --git a/src/repository/BaseMongoRepository.js b/src/repository/BaseMongoRepository.js
index 48e6e02c..89730864 100644
--- a/src/repository/BaseMongoRepository.js
+++ b/src/repository/BaseMongoRepository.js
@@ -1,59 +1,90 @@
 // @flow
 
-import { promisify } from '../lib/promisify';
+import { ObjectId } from 'mongodb';
+
+const deepToObjectIdCast = (node: any): any => {
+  Object.keys(node).forEach((key: string) => {
+    if (node[key] === Object(node[key])) {
+      deepToObjectIdCast(node[key]);
+    }
+    if (key === '_id') {
+      // eslint-disable-next-line
+      node[key] = new ObjectId(node[key]);
+    }
+  });
+  return node;
+};
 
 class BaseMongoRepository {
-  insert = async (
+  insertOne = async (
     collectionName: string,
     entity: Object,
-  ): Promise<*> => this.__runForCollection(
-      collectionName,
-      (collection: Object): Promise<*> => promisify(
-        collection,
-        'insert',
-        entity,
-        { fullResult: true },
-     ).then((results: Array<*>): Object => results[0]),
-    );
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const insertResult = await collection.insertOne(entity);
+      return this.__translateResultItem(insertResult.ops[0]);
+    },
+  );
 
   find = async (
     collectionName: string,
+    query: Object,
     ...args: Array
-  ): Promise<*> => this.__runForCollection(
+  ): Promise<*> => await this.__runForCollection(
     collectionName,
-    (collection: Object): Promise<*> =>
-      promisify(collection.find(...args), 'toArray'),
-  ).then((items: Array<*>): Array<*> =>
-    items.map((item: Object): Object =>
-      (item ? { ...item, id: item._id } : null)),
+    async (collection: Object): Promise<*> => {
+      const resultItems = await collection.find(
+        this.__translateQuery(query),
+        ...args,
+      ).toArray();
+
+      return resultItems.map(this.__translateResultItem);
+    },
   );
 
-  findOne = async (
+  findAndModify = async (
     collectionName: string,
+    query: Object,
+    sort: ?Array,
+    updateQuery: Object,
     ...args: Array
-  ): Promise<*> => this.__runForCollection(
-      collectionName,
-      (collection: Object): Promise<*> =>
-        promisify(collection, 'findOne', ...args),
-    ).then((item: Object): Object => (item ? { ...item, id: item._id } : null));
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const modifyResult = await collection.findAndModify(
+        this.__translateQuery(query),
+        sort,
+        this.__translateQuery(updateQuery),
+        ...args,
+      );
+      return this.__translateResultItem(modifyResult.value);
+    },
+  );
 
-  findAndModify = async (
+  findOne = async (
     collectionName: string,
+    query: Object,
     ...args: Array
-  ): Promise<*> => this.__runForCollection(
-      collectionName,
-      (collection: Object): Promise<*> =>
-        promisify(collection, 'findAndModify', ...args),
-    ).then((item: Object): Object => ({ ...item, id: item._id }));
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const resultItem = await collection.findOne(
+        this.__translateQuery(query),
+        ...args,
+      );
+      return this.__translateResultItem(resultItem);
+    },
+  );
 
   remove = async (
     collectionName: string,
     id: string,
-  ): Promise<*> => this.__runForCollection(
-      collectionName,
-      (collection: Object): Promise<*> =>
-        promisify(collection, 'remove', { _id: id }),
-    );
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> =>
+      await collection.remove(this.__translateQuery({ _id: id })),
+  );
 
   __runForCollection = async (
     collectionName: string,
@@ -61,6 +92,16 @@ class BaseMongoRepository {
   ): Promise<*> => {
     throw new Error(`Not implemented ${callback.toString()}`);
   };
+
+  __translateQuery = (query: Object): Object => deepToObjectIdCast(query);
+
+  __translateResultItem = (item: ?Object): ?Object => {
+    if (!item) {
+      return null;
+    }
+    const { _id, ...otherProps } = item;
+    return { ...otherProps, id: _id.toString() };
+  };
 }
 
 export default BaseMongoRepository;
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index cf4eb26c..6e092fa0 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -34,7 +34,6 @@ class DeviceAttributeDatabaseRepository {
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-
     return await this._database.find(this._collectionName, query);
   };
 
@@ -43,8 +42,7 @@ class DeviceAttributeDatabaseRepository {
     userID: ?string = null,
   ): Promise => {
     const query = userID ? { _id: id, ownerID: userID } : { _id: id };
-
-    return this._database.findOne(this._collectionName, query);
+    return await this._database.findOne(this._collectionName, query);
   }
 }
 
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index 661fc352..67bf1b0e 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -3,7 +3,6 @@
 import BaseMongoRepository from './BaseMongoRepository';
 
 class MongoDb extends BaseMongoRepository {
-  _databasePromise: Promise;
   _database: Object;
 
   constructor(database: Object) {
@@ -15,10 +14,7 @@ class MongoDb extends BaseMongoRepository {
   __runForCollection = async (
     collectionName: string,
     callback: (collection: Object) => Promise<*>,
-  ): Promise<*> => {
-    console.log(this._database.collection(collectionName) !== null);
-    return callback(this._database.collection(collectionName));
-  }
+  ): Promise<*> => callback(this._database.collection(collectionName));
 }
 
 export default MongoDb;
diff --git a/src/repository/TingoDb.js b/src/repository/TingoDb.js
index 4896f47b..17e1aef2 100644
--- a/src/repository/TingoDb.js
+++ b/src/repository/TingoDb.js
@@ -3,6 +3,7 @@
 import fs from 'fs';
 import mkdirp from 'mkdirp';
 import tingoDb from 'tingodb';
+import { promisify } from '../lib/promisify';
 import BaseMongoRepository from './BaseMongoRepository';
 
 class TingoDb extends BaseMongoRepository {
@@ -20,6 +21,65 @@ class TingoDb extends BaseMongoRepository {
     this._database = new Db(path, {});
   }
 
+  insertOne = async (
+    collectionName: string,
+    entity: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const insertResults = await promisify(
+        collection,
+        'insert',
+        entity,
+        { fullResult: true },
+      );
+
+      return this.__translateResultItem(insertResults[0]);
+    },
+  );
+
+  find = async (
+    collectionName: string,
+    ...args: Array
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const resultItems = await promisify(collection.find(...args), 'toArray');
+      return resultItems.map(this.__translateResultItem);
+    },
+  );
+
+  findAndModify = async (
+    collectionName: string,
+    ...args: Array
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const modifiedItem = await promisify(collection, 'findAndModify', ...args);
+      return this.__translateResultItem(modifiedItem);
+    },
+  );
+
+  findOne = async (
+    collectionName: string,
+    ...args: Array
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const resultItem = await promisify(collection, 'findOne', ...args);
+      return this.__translateResultItem(resultItem);
+    },
+  );
+
+  remove = async (
+    collectionName: string,
+    id: string,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> =>
+      await promisify(collection, 'remove', { _id: id }),
+  );
+
   __runForCollection = async (
     collectionName: string,
     callback: (collection: Object) => Promise<*>,
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index 2f112d8f..82d5c52a 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -27,11 +27,10 @@ class UserDatabaseRepository {
       username,
     };
 
-    const user = await this._database.insert(
+    return await this._database.insertOne(
       this._collectionName,
       modelToSave,
     );
-    return { ...user, id: user._id.toString() };
   };
 
   deleteAccessToken = async (userID: string, accessToken: string): Promise =>
@@ -64,7 +63,7 @@ class UserDatabaseRepository {
   };
 
   getByUsername = async (username: string): Promise =>
-    this._database.findOne(
+    await this._database.findOne(
       this._collectionName,
       { username },
     );
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index 58a4cfdd..34ba9764 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -10,8 +10,8 @@ class WebhookDatabaseRepository {
     this._database = database;
   }
 
-  create = async (model: $Shape): Promise => {
-    const webhook = await this._database.insert(
+  create = async (model: $Shape): Promise =>
+    await this._database.insertOne(
       this._collectionName,
       {
         ...model,
@@ -19,23 +19,17 @@ class WebhookDatabaseRepository {
       },
     );
 
-    return { ...webhook, id: webhook._id.toString() };
-  };
-
   deleteById = async (id: string): Promise =>
-    this._database.remove(this._collectionName, id);
+    await this._database.remove(this._collectionName, id);
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-    return this._database.find(this._collectionName, query);
+    return await this._database.find(this._collectionName, query);
   };
 
   getById = async (id: string, userID: ?string = null): Promise => {
     const query = userID ? { _id: id, ownerID: userID } : { _id: id };
-    return this._database.findOne(
-      this._collectionName,
-      query,
-    );
+    return await this._database.findOne(this._collectionName, query);
   };
 
   update = async (): Promise => {

From db77fc4c28fb8434c782da32a7361d9b7e01b207 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 21 May 2017 22:15:00 -0700
Subject: [PATCH 368/504] Allow users to override express config.

---
 dist/main.js | 22 ++++++++++++++++------
 src/main.js  | 21 ++++++++++++++-------
 2 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/dist/main.js b/dist/main.js
index 1479b054..314b2bb1 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -8,6 +8,10 @@ var _entries = require('babel-runtime/core-js/object/entries');
 
 var _entries2 = _interopRequireDefault(_entries);
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _arrayFlatten = require('array-flatten');
 
 var _arrayFlatten2 = _interopRequireDefault(_arrayFlatten);
@@ -82,15 +86,21 @@ var onServerStartListen = function onServerStartListen() {
   return console.log('express server started on port ' + NODE_PORT);
 };
 
-if (_settings2.default.EXPRESS_SERVER_CONFIG.USE_SSL) {
-  var options = {
-    cert: _fs2.default.readFileSync((0, _nullthrows2.default)(_settings2.default.EXPRESS_SERVER_CONFIG.SSL_CERTIFICATE_FILEPATH)),
-    key: _fs2.default.readFileSync((0, _nullthrows2.default)(_settings2.default.EXPRESS_SERVER_CONFIG.SSL_PRIVATE_KEY_FILEPATH))
-  };
+var _settings$EXPRESS_SER = _settings2.default.EXPRESS_SERVER_CONFIG,
+    expressConfig = _settings$EXPRESS_SER.CONFIG,
+    privateKeyFilePath = _settings$EXPRESS_SER.SSL_PRIVATE_KEY_FILEPATH,
+    certificateFilePath = _settings$EXPRESS_SER.SSL_CERTIFICATE_FILEPATH,
+    useSSL = _settings$EXPRESS_SER.USE_SSL;
+
 
+if (useSSL) {
+  var options = (0, _extends3.default)({
+    cert: certificateFilePath && _fs2.default.readFileSync((0, _nullthrows2.default)(certificateFilePath)),
+    key: privateKeyFilePath && _fs2.default.readFileSync((0, _nullthrows2.default)(privateKeyFilePath))
+  }, expressConfig);
   _https2.default.createServer(options, app).listen(NODE_PORT, onServerStartListen);
 } else {
-  _http2.default.createServer(app).listen(NODE_PORT, onServerStartListen);
+  _http2.default.createServer((0, _extends3.default)({}, expressConfig), app).listen(NODE_PORT, onServerStartListen);
 }
 
 var addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map(
diff --git a/src/main.js b/src/main.js
index 25b65c45..cae9b4f4 100644
--- a/src/main.js
+++ b/src/main.js
@@ -44,22 +44,29 @@ const app = createApp(container, settings);
 const onServerStartListen = (): void =>
   console.log(`express server started on port ${NODE_PORT}`);
 
-if (settings.EXPRESS_SERVER_CONFIG.USE_SSL) {
+const {
+  CONFIG: expressConfig,
+  SSL_PRIVATE_KEY_FILEPATH: privateKeyFilePath,
+  SSL_CERTIFICATE_FILEPATH: certificateFilePath,
+  USE_SSL: useSSL
+} = settings.EXPRESS_SERVER_CONFIG;
+
+if (useSSL) {
   const options = {
-    cert: fs.readFileSync(
-      nulltrhows(settings.EXPRESS_SERVER_CONFIG.SSL_CERTIFICATE_FILEPATH),
+    cert: certificateFilePath && fs.readFileSync(
+      nulltrhows(certificateFilePath),
     ),
-    key: fs.readFileSync(
-      nulltrhows(settings.EXPRESS_SERVER_CONFIG.SSL_PRIVATE_KEY_FILEPATH),
+    key: privateKeyFilePath && fs.readFileSync(
+      nulltrhows(privateKeyFilePath),
     ),
+    ...expressConfig,
   };
-
   https
     .createServer(options, (app: any))
     .listen(NODE_PORT, onServerStartListen);
 } else {
   http
-    .createServer((app: any))
+    .createServer({...expressConfig}, (app: any))
     .listen(NODE_PORT, onServerStartListen);
 }
 

From 343b87a7f6f3bd95bb2539582c701617a9a82038 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Mon, 22 May 2017 07:04:10 -0700
Subject: [PATCH 369/504] nits

---
 src/main.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main.js b/src/main.js
index cae9b4f4..f2df9b7b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -48,7 +48,7 @@ const {
   CONFIG: expressConfig,
   SSL_PRIVATE_KEY_FILEPATH: privateKeyFilePath,
   SSL_CERTIFICATE_FILEPATH: certificateFilePath,
-  USE_SSL: useSSL
+  USE_SSL: useSSL,
 } = settings.EXPRESS_SERVER_CONFIG;
 
 if (useSSL) {
@@ -66,7 +66,7 @@ if (useSSL) {
     .listen(NODE_PORT, onServerStartListen);
 } else {
   http
-    .createServer({...expressConfig}, (app: any))
+    .createServer({ ...expressConfig }, (app: any))
     .listen(NODE_PORT, onServerStartListen);
 }
 

From cc5943d2e72322e580ab28917758b7c56dedffda Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Mon, 22 May 2017 23:43:07 +0200
Subject: [PATCH 370/504] add migrateScript

---
 dist/scripts/migrateFilesToDatabase.js | 254 +++++++++++++++++++++++++
 dist/settings.js                       |   3 +-
 package.json                           |   4 +-
 src/scripts/migrateFilesToDatabase.js  | 102 ++++++++++
 src/settings.js                        |   1 +
 src/types.js                           |   3 +-
 test/setup/settings.js                 |   1 +
 7 files changed, 365 insertions(+), 3 deletions(-)
 create mode 100644 dist/scripts/migrateFilesToDatabase.js
 create mode 100644 src/scripts/migrateFilesToDatabase.js

diff --git a/dist/scripts/migrateFilesToDatabase.js b/dist/scripts/migrateFilesToDatabase.js
new file mode 100644
index 00000000..8a814ea3
--- /dev/null
+++ b/dist/scripts/migrateFilesToDatabase.js
@@ -0,0 +1,254 @@
+'use strict';
+
+var _promise = require('babel-runtime/core-js/promise');
+
+var _promise2 = _interopRequireDefault(_promise);
+
+var _map = require('babel-runtime/core-js/map');
+
+var _map2 = _interopRequireDefault(_map);
+
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _fs = require('fs');
+
+var _fs2 = _interopRequireDefault(_fs);
+
+var _settings = require('../settings');
+
+var _settings2 = _interopRequireDefault(_settings);
+
+var _mongodb = require('mongodb');
+
+var _TingoDb = require('../repository/TingoDb');
+
+var _TingoDb2 = _interopRequireDefault(_TingoDb);
+
+var _MongoDb = require('../repository/MongoDb');
+
+var _MongoDb2 = _interopRequireDefault(_MongoDb);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var DATABASE_TYPE = process.argv[2];
+
+var setupDatabase = function () {
+  var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+    var mongoConnection;
+    return _regenerator2.default.wrap(function _callee$(_context) {
+      while (1) {
+        switch (_context.prev = _context.next) {
+          case 0:
+            if (!(DATABASE_TYPE === 'tingo')) {
+              _context.next = 2;
+              break;
+            }
+
+            return _context.abrupt('return', new _TingoDb2.default(_settings2.default.DB_CONFIG.PATH, _settings2.default.DB_CONFIG.OPTIONS));
+
+          case 2:
+            if (!(DATABASE_TYPE === 'mongo')) {
+              _context.next = 7;
+              break;
+            }
+
+            _context.next = 5;
+            return _mongodb.MongoClient.connect(_settings2.default.DB_CONFIG.URL);
+
+          case 5:
+            mongoConnection = _context.sent;
+            return _context.abrupt('return', new _MongoDb2.default(mongoConnection));
+
+          case 7:
+            throw new Error('Wrong database type');
+
+          case 8:
+          case 'end':
+            return _context.stop();
+        }
+      }
+    }, _callee, undefined);
+  }));
+
+  return function setupDatabase() {
+    return _ref.apply(this, arguments);
+  };
+}();
+
+var getFiles = function getFiles(directoryPath) {
+  var fileNames = _fs2.default.readdirSync(directoryPath).filter(function (fileName) {
+    return fileName.endsWith('.json');
+  });
+
+  return fileNames.map(function (fileName) {
+    return _fs2.default.readFileSync(directoryPath + '/' + fileName);
+  });
+};
+
+var parseFile = function parseFile(file) {
+  return JSON.parse(file.toString());
+};
+
+var mapOwnerID = function mapOwnerID(userIDsMap) {
+  return function (item) {
+    return (0, _extends3.default)({}, item, { ownerID: userIDsMap.get(item.ownerID) || null
+    });
+  };
+};
+
+var translateDeviceID = function translateDeviceID(item) {
+  return (0, _extends3.default)({}, item, { _id: new _mongodb.ObjectId(item.deviceID), id: item.deviceID });
+};
+
+// eslint-disable-next-line no-unused-vars
+var filterID = function filterID(_ref2) {
+  var id = _ref2.id,
+      otherProps = (0, _objectWithoutProperties3.default)(_ref2, ['id']);
+  return (0, _extends3.default)({}, otherProps);
+};
+
+var insertItem = function insertItem(database, collectionName) {
+  return function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(item) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return database.insertOne(collectionName, item);
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, undefined);
+    }));
+
+    return function (_x) {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+};
+
+var insertUsers = function () {
+  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(database, users) {
+    var userIDsMap;
+    return _regenerator2.default.wrap(function _callee4$(_context4) {
+      while (1) {
+        switch (_context4.prev = _context4.next) {
+          case 0:
+            userIDsMap = new _map2.default();
+            _context4.next = 3;
+            return _promise2.default.all(users.map(function () {
+              var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(user) {
+                var insertedUser;
+                return _regenerator2.default.wrap(function _callee3$(_context3) {
+                  while (1) {
+                    switch (_context3.prev = _context3.next) {
+                      case 0:
+                        _context3.next = 2;
+                        return database.insertOne('users', filterID(user));
+
+                      case 2:
+                        insertedUser = _context3.sent;
+
+                        userIDsMap.set(user.id, insertedUser.id);
+
+                      case 4:
+                      case 'end':
+                        return _context3.stop();
+                    }
+                  }
+                }, _callee3, undefined);
+              }));
+
+              return function (_x4) {
+                return _ref5.apply(this, arguments);
+              };
+            }()));
+
+          case 3:
+            return _context4.abrupt('return', userIDsMap);
+
+          case 4:
+          case 'end':
+            return _context4.stop();
+        }
+      }
+    }, _callee4, undefined);
+  }));
+
+  return function insertUsers(_x2, _x3) {
+    return _ref4.apply(this, arguments);
+  };
+}();
+
+(0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+  var database, users, userIDsMap;
+  return _regenerator2.default.wrap(function _callee5$(_context5) {
+    while (1) {
+      switch (_context5.prev = _context5.next) {
+        case 0:
+          _context5.prev = 0;
+
+          console.log('Setup database connection...');
+          _context5.next = 4;
+          return setupDatabase();
+
+        case 4:
+          database = _context5.sent;
+
+          console.log('Start migration to ' + DATABASE_TYPE);
+
+          users = getFiles(_settings2.default.USERS_DIRECTORY).map(parseFile);
+          _context5.next = 9;
+          return insertUsers(database, users);
+
+        case 9:
+          userIDsMap = _context5.sent;
+          _context5.next = 12;
+          return _promise2.default.all(getFiles(_settings2.default.WEBHOOKS_DIRECTORY).map(parseFile).map(mapOwnerID(userIDsMap)).map(filterID).map(insertItem(database, 'webhooks')));
+
+        case 12:
+          _context5.next = 14;
+          return _promise2.default.all(getFiles(_settings2.default.DEVICE_DIRECTORY).map(parseFile).map(mapOwnerID(userIDsMap)).map(translateDeviceID).map(filterID).map(insertItem(database, 'deviceAttributes')));
+
+        case 14:
+
+          console.log('All files migrated to the database successfully!');
+          process.exit(0);
+          _context5.next = 22;
+          break;
+
+        case 18:
+          _context5.prev = 18;
+          _context5.t0 = _context5['catch'](0);
+
+          console.log(_context5.t0);
+          process.exit(1);
+
+        case 22:
+        case 'end':
+          return _context5.stop();
+      }
+    }
+  }, _callee5, undefined, [[0, 18]]);
+}))();
\ No newline at end of file
diff --git a/dist/settings.js b/dist/settings.js
index 55e561db..86a6025b 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -40,7 +40,8 @@ exports.default = {
       nativeObjectID: true,
       searchInArray: true
     },
-    PATH: _path2.default.join(__dirname, '../data/db')
+    PATH: _path2.default.join(__dirname, '../data/db'),
+    URL: null
   },
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
diff --git a/package.json b/package.json
index 7e0fa2a5..b74346a9 100644
--- a/package.json
+++ b/package.json
@@ -28,9 +28,11 @@
   "main": "./dist/exports.js",
   "scripts": {
     "build": "babel ./src --out-dir ./dist",
-    "build:watch": "babel ./src --out-dir ./dist --watch",
     "build:clean": "rimraf ./build",
+    "build:watch": "babel ./src --out-dir ./dist --watch",
     "lint": "eslint --fix --max-warnings 0 -- .",
+    "migrate-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
+    "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
     "prebuild": "npm run build:clean",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
     "start:prod": "npm run build && node ./dist/main.js",
diff --git a/src/scripts/migrateFilesToDatabase.js b/src/scripts/migrateFilesToDatabase.js
new file mode 100644
index 00000000..b35f5cb0
--- /dev/null
+++ b/src/scripts/migrateFilesToDatabase.js
@@ -0,0 +1,102 @@
+// @flow
+
+import fs from 'fs';
+import settings from '../settings';
+import { MongoClient, ObjectId } from 'mongodb';
+import TingoDb from '../repository/TingoDb';
+import MongoDb from '../repository/MongoDb';
+
+type DatabaseType = 'mongo' | 'tingo';
+
+type Database = TingoDb | MongoDb;
+
+const DATABASE_TYPE: DatabaseType = ((process.argv[2]): any);
+
+const setupDatabase = async (): Promise => {
+  if (DATABASE_TYPE === 'tingo') {
+    return new TingoDb(settings.DB_CONFIG.PATH, settings.DB_CONFIG.OPTIONS);
+  }
+  if (DATABASE_TYPE === 'mongo') {
+    const mongoConnection = await MongoClient.connect(settings.DB_CONFIG.URL);
+    return new MongoDb(mongoConnection);
+  }
+
+  throw new Error('Wrong database type');
+};
+
+const getFiles = (directoryPath: string): Array => {
+  const fileNames = fs.readdirSync(directoryPath)
+    .filter((fileName: string): boolean => fileName.endsWith('.json'));
+
+  return fileNames.map((fileName: string): Buffer =>
+    fs.readFileSync(`${directoryPath}/${fileName}`),
+  );
+};
+
+const parseFile = (file: Buffer): Object => JSON.parse(file.toString());
+
+const mapOwnerID = (userIDsMap: Map): (item: Object) => Object =>
+  (item: Object): Object => ({
+    ...item, ownerID: userIDsMap.get(item.ownerID) || null,
+  });
+
+const translateDeviceID = (item: Object): Object =>
+  ({ ...item, _id: new ObjectId(item.deviceID), id: item.deviceID });
+
+// eslint-disable-next-line no-unused-vars
+const filterID = ({ id, ...otherProps }: Object): Object => ({ ...otherProps });
+
+const insertItem = (
+  database: Object,
+  collectionName: string,
+): (item: Object) => Promise =>
+  async (item: Object): Promise =>
+    await database.insertOne(collectionName, item);
+
+const insertUsers = async (
+  database: Object,
+  users: Array,
+): Promise> => {
+  const userIDsMap = new Map();
+
+  await Promise.all(users.map(async (user: Object): Promise => {
+    const insertedUser = await database.insertOne('users', filterID(user));
+    userIDsMap.set(user.id, insertedUser.id);
+  }));
+
+  return userIDsMap;
+};
+
+(async (): Promise => {
+  try {
+    console.log('Setup database connection...');
+    const database = await setupDatabase();
+    console.log(`Start migration to ${DATABASE_TYPE}`);
+
+    const users = getFiles(settings.USERS_DIRECTORY)
+      .map(parseFile);
+
+    const userIDsMap = await insertUsers(database, users);
+
+    await Promise.all(getFiles(settings.WEBHOOKS_DIRECTORY)
+      .map(parseFile)
+      .map(mapOwnerID(userIDsMap))
+      .map(filterID)
+      .map(insertItem(database, 'webhooks')),
+    );
+
+    await Promise.all(getFiles(settings.DEVICE_DIRECTORY)
+      .map(parseFile)
+      .map(mapOwnerID(userIDsMap))
+      .map(translateDeviceID)
+      .map(filterID)
+      .map(insertItem(database, 'deviceAttributes')),
+    );
+
+    console.log('All files migrated to the database successfully!');
+    process.exit(0);
+  } catch (error) {
+    console.log(error);
+    process.exit(1);
+  }
+})();
diff --git a/src/settings.js b/src/settings.js
index d8051b93..f3c46c7f 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -52,6 +52,7 @@ export default {
       searchInArray: true,
     },
     PATH: path.join(__dirname, '../data/db'),
+    URL: null,
   },
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
diff --git a/src/types.js b/src/types.js
index fc3be4a3..7d0f3f22 100644
--- a/src/types.js
+++ b/src/types.js
@@ -149,7 +149,8 @@ export type Settings = {
   CRYPTO_SALT: string,
   DB_CONFIG: {
     OPTIONS: Object,
-    PATH: string,
+    PATH: ?string,
+    URL: ?string,
   },
   DEVICE_DIRECTORY: string,
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: boolean,
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 4e6b213d..59723539 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -40,5 +40,6 @@ export default {
       searchInArray: true,
     },
     PATH: path.join(__dirname, '../__test_data__/db'),
+    URL: null,
   },
 };

From 9b99c2933c977510ae0936debbfa3d9a1a285ee3 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 23 May 2017 01:27:14 +0200
Subject: [PATCH 371/504] fix main.js Flow errors

---
 dist/main.js | 10 +++++++---
 src/main.js  |  4 ++--
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/dist/main.js b/dist/main.js
index 314b2bb1..09daeb64 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -12,6 +12,10 @@ var _extends2 = require('babel-runtime/helpers/extends');
 
 var _extends3 = _interopRequireDefault(_extends2);
 
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
 var _arrayFlatten = require('array-flatten');
 
 var _arrayFlatten2 = _interopRequireDefault(_arrayFlatten);
@@ -87,10 +91,10 @@ var onServerStartListen = function onServerStartListen() {
 };
 
 var _settings$EXPRESS_SER = _settings2.default.EXPRESS_SERVER_CONFIG,
-    expressConfig = _settings$EXPRESS_SER.CONFIG,
     privateKeyFilePath = _settings$EXPRESS_SER.SSL_PRIVATE_KEY_FILEPATH,
     certificateFilePath = _settings$EXPRESS_SER.SSL_CERTIFICATE_FILEPATH,
-    useSSL = _settings$EXPRESS_SER.USE_SSL;
+    useSSL = _settings$EXPRESS_SER.USE_SSL,
+    expressConfig = (0, _objectWithoutProperties3.default)(_settings$EXPRESS_SER, ['SSL_PRIVATE_KEY_FILEPATH', 'SSL_CERTIFICATE_FILEPATH', 'USE_SSL']);
 
 
 if (useSSL) {
@@ -100,7 +104,7 @@ if (useSSL) {
   }, expressConfig);
   _https2.default.createServer(options, app).listen(NODE_PORT, onServerStartListen);
 } else {
-  _http2.default.createServer((0, _extends3.default)({}, expressConfig), app).listen(NODE_PORT, onServerStartListen);
+  _http2.default.createServer(app).listen(NODE_PORT, onServerStartListen);
 }
 
 var addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map(
diff --git a/src/main.js b/src/main.js
index f2df9b7b..1ad7f55d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -45,10 +45,10 @@ const onServerStartListen = (): void =>
   console.log(`express server started on port ${NODE_PORT}`);
 
 const {
-  CONFIG: expressConfig,
   SSL_PRIVATE_KEY_FILEPATH: privateKeyFilePath,
   SSL_CERTIFICATE_FILEPATH: certificateFilePath,
   USE_SSL: useSSL,
+  ...expressConfig
 } = settings.EXPRESS_SERVER_CONFIG;
 
 if (useSSL) {
@@ -66,7 +66,7 @@ if (useSSL) {
     .listen(NODE_PORT, onServerStartListen);
 } else {
   http
-    .createServer({ ...expressConfig }, (app: any))
+    .createServer((app: any))
     .listen(NODE_PORT, onServerStartListen);
 }
 

From ba0aa363deb57a10bf620bffef48cdead1f68a0c Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Mon, 22 May 2017 18:43:01 -0700
Subject: [PATCH 372/504] Added extra logging

---
 dist/repository/MongoDb.js | 4 +++-
 src/repository/MongoDb.js  | 4 +++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index a256a111..51522093 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -50,7 +50,9 @@ var MongoDb = function (_BaseMongoRepository) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                return _context.abrupt('return', callback(_this._database.collection(collectionName)));
+                return _context.abrupt('return', callback(_this._database.collection(collectionName)).catch(function (error) {
+                  return console.error(error);
+                }));
 
               case 1:
               case 'end':
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index 67bf1b0e..ff824d43 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -14,7 +14,9 @@ class MongoDb extends BaseMongoRepository {
   __runForCollection = async (
     collectionName: string,
     callback: (collection: Object) => Promise<*>,
-  ): Promise<*> => callback(this._database.collection(collectionName));
+  ): Promise<*> => callback(
+    this._database.collection(collectionName),
+  ).catch((error: Error): void => console.error(error));
 }
 
 export default MongoDb;

From 1c3515289c8c76c0e2e3a83ad829c34c32526ebd Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Wed, 24 May 2017 09:34:18 -0700
Subject: [PATCH 373/504] Added webhook logger which can be overridden - closes
 #57

---
 dist/defaultBindings.js         |  9 +++++++-
 dist/lib/WebhookLogger.js       | 40 +++++++++++++++++++++++++++++++++
 dist/lib/promisify.js           | 38 +++----------------------------
 dist/managers/WebhookManager.js | 15 ++++++++-----
 package.json                    |  2 +-
 src/defaultBindings.js          | 10 ++++++++-
 src/lib/WebhookLogger.js        | 14 ++++++++++++
 src/lib/promisify.js            | 17 --------------
 src/managers/WebhookManager.js  | 12 ++++++++++
 src/types.js                    |  4 ++++
 10 files changed, 100 insertions(+), 61 deletions(-)
 create mode 100644 dist/lib/WebhookLogger.js
 create mode 100644 src/lib/WebhookLogger.js

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 90d4095b..3fedb00b 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -42,6 +42,10 @@ var _WebhooksController = require('./controllers/WebhooksController');
 
 var _WebhooksController2 = _interopRequireDefault(_WebhooksController);
 
+var _WebhookLogger = require('./lib/WebhookLogger');
+
+var _WebhookLogger2 = _interopRequireDefault(_WebhookLogger);
+
 var _DeviceManager = require('./managers/DeviceManager');
 
 var _DeviceManager2 = _interopRequireDefault(_DeviceManager);
@@ -101,6 +105,9 @@ exports.default = function (container, newSettings) {
 
   container.bindClass('Database', _TingoDb2.default, ['DATABASE_PATH', 'DATABASE_OPTIONS']);
 
+  // lib
+  container.bindClass('IWebhookLogger', _WebhookLogger2.default, []);
+
   // controllers
   container.bindClass('DeviceClaimsController', _DeviceClaimsController2.default, ['DeviceManager', 'ClaimCodeManager']);
   container.bindClass('DevicesController', _DevicesController2.default, ['DeviceManager']);
@@ -114,7 +121,7 @@ exports.default = function (container, newSettings) {
   // managers
   container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'DeviceServer']);
   container.bindClass('EventManager', _EventManager2.default, ['EventPublisher']);
-  container.bindClass('WebhookManager', _WebhookManager2.default, ['WebhookRepository', 'EventPublisher']);
+  container.bindClass('WebhookManager', _WebhookManager2.default, ['WebhookRepository', 'EventPublisher', 'IWebhookLogger']);
 
   // Repositories
   container.bindClass('DeviceAttributeRepository', _DeviceAttributeDatabaseRepository2.default, ['Database']);
diff --git a/dist/lib/WebhookLogger.js b/dist/lib/WebhookLogger.js
new file mode 100644
index 00000000..ec7a8bb5
--- /dev/null
+++ b/dist/lib/WebhookLogger.js
@@ -0,0 +1,40 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _types = require('../types');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var WebhookLogger = function () {
+  function WebhookLogger() {
+    (0, _classCallCheck3.default)(this, WebhookLogger);
+  }
+
+  (0, _createClass3.default)(WebhookLogger, [{
+    key: 'log',
+    value: function log() {
+      var _console;
+
+      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+        args[_key] = arguments[_key];
+      }
+
+      this._lastLog = args;
+      (_console = console).log.apply(_console, args);
+    }
+  }]);
+  return WebhookLogger;
+}();
+
+exports.default = WebhookLogger;
\ No newline at end of file
diff --git a/dist/lib/promisify.js b/dist/lib/promisify.js
index aead0c0c..cfbeb3c0 100644
--- a/dist/lib/promisify.js
+++ b/dist/lib/promisify.js
@@ -1,19 +1,11 @@
-'use strict';
+"use strict";
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.promisifyByPrototype = exports.promisify = undefined;
+exports.promisify = undefined;
 
-var _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-property-names');
-
-var _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames);
-
-var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
-
-var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
-
-var _promise = require('babel-runtime/core-js/promise');
+var _promise = require("babel-runtime/core-js/promise");
 
 var _promise2 = _interopRequireDefault(_promise);
 
@@ -33,28 +25,4 @@ var promisify = exports.promisify = function promisify(object, fnName) {
       resolve(result);
     }]));
   });
-};
-
-var promisifyByPrototype = exports.promisifyByPrototype = function promisifyByPrototype(object) {
-  var prototype = (0, _getPrototypeOf2.default)(object);
-
-  var fnNames = (0, _getOwnPropertyNames2.default)(prototype).filter(function (propName) {
-    return typeof prototype[propName] === 'function';
-  });
-
-  var resultObject = {};
-
-  fnNames.forEach(function (fnName) {
-    resultObject[fnName] = function () {
-      var _object$fnName;
-
-      for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
-        args[_key2] = arguments[_key2];
-      }
-
-      return promisify((_object$fnName = object[fnName]).bind.apply(_object$fnName, [object].concat(args)));
-    };
-  });
-
-  return resultObject;
 };
\ No newline at end of file
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index c5be0fd0..fa0cd715 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -98,7 +98,7 @@ var WEBHOOK_DEFAULTS = {
   rejectUnauthorized: true
 };
 
-var WebhookManager = function WebhookManager(webhookRepository, eventPublisher) {
+var WebhookManager = function WebhookManager(webhookRepository, eventPublisher, webhookLogger) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, WebhookManager);
@@ -362,21 +362,23 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher)
                   userID: event.userID
                 });
               });
-              _context6.next = 27;
+
+              _this._webhookLogger.log(event, webhook, requestOptions, _responseBody, responseEventData);
+              _context6.next = 28;
               break;
 
-            case 24:
-              _context6.prev = 24;
+            case 25:
+              _context6.prev = 25;
               _context6.t0 = _context6['catch'](0);
 
               _logger2.default.error('webhookError: ' + _context6.t0);
 
-            case 27:
+            case 28:
             case 'end':
               return _context6.stop();
           }
         }
-      }, _callee6, _this, [[0, 24]]);
+      }, _callee6, _this, [[0, 25]]);
     }));
 
     return function (_x7, _x8) {
@@ -486,6 +488,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher)
 
   this._webhookRepository = webhookRepository;
   this._eventPublisher = eventPublisher;
+  this._webhookLogger = webhookLogger;
 
   (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
     return _regenerator2.default.wrap(function _callee7$(_context7) {
diff --git a/package.json b/package.json
index b74346a9..f5d200cf 100644
--- a/package.json
+++ b/package.json
@@ -103,7 +103,7 @@
     "eslint-plugin-flowtype": "^2.28.2",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-sorting": "^0.3.0",
-    "flow-bin": "^0.37.0",
+    "flow-bin": "^0.37.4",
     "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
     "rimraf": "^2.5.4",
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 6baf9c87..9f6913c3 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -12,6 +12,7 @@ import ProductsController from './controllers/ProductsController';
 import ProvisioningController from './controllers/ProvisioningController';
 import UsersController from './controllers/UsersController';
 import WebhooksController from './controllers/WebhooksController';
+import WebhookLogger from './lib/WebhookLogger';
 import DeviceManager from './managers/DeviceManager';
 import WebhookManager from './managers/WebhookManager';
 import EventManager from './managers/EventManager';
@@ -48,6 +49,13 @@ export default (container: Container, newSettings: Settings) => {
     ['DATABASE_PATH', 'DATABASE_OPTIONS'],
   );
 
+  // lib
+  container.bindClass(
+    'IWebhookLogger',
+    WebhookLogger,
+    [],
+  );
+
   // controllers
   container.bindClass(
     'DeviceClaimsController',
@@ -112,7 +120,7 @@ export default (container: Container, newSettings: Settings) => {
   container.bindClass(
     'WebhookManager',
     WebhookManager,
-    ['WebhookRepository', 'EventPublisher'],
+    ['WebhookRepository', 'EventPublisher', 'IWebhookLogger'],
   );
 
   // Repositories
diff --git a/src/lib/WebhookLogger.js b/src/lib/WebhookLogger.js
new file mode 100644
index 00000000..a2948bce
--- /dev/null
+++ b/src/lib/WebhookLogger.js
@@ -0,0 +1,14 @@
+// @flow
+
+import { IWebhookLogger } from '../types';
+
+class WebhookLogger implements IWebhookLogger {
+  _lastLog: Array;
+
+  log(...args: Array) {
+    this._lastLog = args;
+    console.log(...args);
+  }
+}
+
+export default WebhookLogger;
diff --git a/src/lib/promisify.js b/src/lib/promisify.js
index 53bf0d37..7189c9e0 100644
--- a/src/lib/promisify.js
+++ b/src/lib/promisify.js
@@ -16,20 +16,3 @@ export const promisify = (
     resolve(result);
   }),
 );
-
-export const promisifyByPrototype = (object: Object): Object => {
-  const prototype = Object.getPrototypeOf(object);
-
-  const fnNames = Object.getOwnPropertyNames(prototype).filter(
-    (propName: string): boolean => typeof prototype[propName] === 'function',
-  );
-
-  const resultObject = {};
-
-  fnNames.forEach((fnName: string) => {
-    resultObject[fnName] = (...args: Array): Promise<*> =>
-      promisify(object[fnName].bind(object, ...args));
-  });
-
-  return resultObject;
-};
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 1316fe8d..35676bb0 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -2,6 +2,7 @@
 
 import type {
   Event,
+  IWebhookLogger,
   Repository,
   RequestOptions,
   RequestType,
@@ -67,13 +68,16 @@ class WebhookManager {
   _subscriptionIDsByWebhookID: Map = new Map();
   _errorsCountByWebhookID: Map = new Map();
   _webhookRepository: Repository;
+  _webhookLogger: IWebhookLogger;
 
   constructor(
     webhookRepository: Repository,
     eventPublisher: EventPublisher,
+    webhookLogger: IWebhookLogger,
   ) {
     this._webhookRepository = webhookRepository;
     this._eventPublisher = eventPublisher;
+    this._webhookLogger = webhookLogger;
 
     (async (): Promise => await this._init())();
   }
@@ -270,6 +274,14 @@ class WebhookManager {
           userID: event.userID,
         });
       });
+
+      this._webhookLogger.log(
+        event,
+        webhook,
+        requestOptions,
+        responseBody,
+        responseEventData,
+      );
     } catch (error) {
       logger.error(`webhookError: ${error}`);
     }
diff --git a/src/types.js b/src/types.js
index 7d0f3f22..2a5cf64f 100644
--- a/src/types.js
+++ b/src/types.js
@@ -51,6 +51,10 @@ export type WebhookMutator = {
   url: string,
 };
 
+export interface IWebhookLogger {
+  log(...args: Array): void,
+}
+
 export type RequestType = 'DELETE' | 'GET' | 'POST' | 'PUT';
 
 export type Client = {

From 06c00c33c656aba4e92d99437458e4735c99b1b4 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 26 May 2017 16:09:41 +0200
Subject: [PATCH 374/504] add interfaces for repositories

---
 dist/exports.js                               |   7 +-
 .../DeviceAttributeDatabaseRepository.js      |  64 ++---
 dist/repository/MongoDb.js                    |  10 +-
 dist/repository/TingoDb.js                    |  10 +-
 dist/repository/UserDatabaseRepository.js     | 267 ++++++++++++------
 dist/repository/UserFileRepository.js         | 126 ++++-----
 src/controllers/UsersController.js            |   6 +-
 src/exports.js                                |   3 -
 src/managers/DeviceManager.js                 |  18 +-
 src/managers/WebhookManager.js                |   6 +-
 ...{BaseMongoRepository.js => BaseMongoDb.js} |   6 +-
 .../DeviceAttributeDatabaseRepository.js      |  34 ++-
 .../DeviceFirmwareFileRepository.js           |   4 +-
 src/repository/MongoDb.js                     |   4 +-
 src/repository/TingoDb.js                     |   4 +-
 src/repository/UserDatabaseRepository.js      |  33 ++-
 src/repository/UserFileRepository.js          |  75 +++--
 src/repository/WebhookDatabaseRepository.js   |   8 +-
 src/repository/WebhookFileRepository.js       |   4 +-
 src/types.js                                  |  44 ++-
 20 files changed, 437 insertions(+), 296 deletions(-)
 rename src/repository/{BaseMongoRepository.js => BaseMongoDb.js} (95%)

diff --git a/dist/exports.js b/dist/exports.js
index b343774a..2f77acf3 100644
--- a/dist/exports.js
+++ b/dist/exports.js
@@ -3,7 +3,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.settings = exports.logger = exports.defaultBindings = exports.createApp = exports.MongoDb = exports.BaseMongoRepository = undefined;
+exports.settings = exports.logger = exports.defaultBindings = exports.createApp = exports.MongoDb = undefined;
 
 var _logger = require('./lib/logger');
 
@@ -21,17 +21,12 @@ var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
-var _BaseMongoRepository = require('./repository/BaseMongoRepository');
-
-var _BaseMongoRepository2 = _interopRequireDefault(_BaseMongoRepository);
-
 var _MongoDb = require('./repository/MongoDb');
 
 var _MongoDb2 = _interopRequireDefault(_MongoDb);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-exports.BaseMongoRepository = _BaseMongoRepository2.default;
 exports.MongoDb = _MongoDb2.default;
 exports.createApp = _app2.default;
 exports.defaultBindings = _defaultBindings2.default;
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index ddfc1dc7..738a8e6f 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -42,14 +42,14 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     }, _callee, _this);
   }));
 
-  this.update = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(model) {
+  this.deleteById = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
+              return _this._database.remove(_this._collectionName, id);
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
@@ -67,17 +67,17 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     };
   }();
 
-  this.deleteById = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(id) {
+  this.doesUserHaveAccess = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(id, userID) {
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
             case 0:
               _context3.next = 2;
-              return _this._database.remove(_this._collectionName, id);
+              return _this._database.findOne(_this._collectionName, { _id: id, ownerID: userID });
 
             case 2:
-              return _context3.abrupt('return', _context3.sent);
+              return _context3.abrupt('return', !!_context3.sent);
 
             case 3:
             case 'end':
@@ -87,24 +87,27 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee3, _this);
     }));
 
-    return function (_x2) {
+    return function (_x2, _x3) {
       return _ref3.apply(this, arguments);
     };
   }();
 
-  this.doesUserHaveAccess = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id, userID) {
+  this.getAll = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
-              _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id, ownerID: userID });
-
-            case 2:
-              return _context4.abrupt('return', !!_context4.sent);
+              query = userID ? { ownerID: userID } : {};
+              _context4.next = 3;
+              return _this._database.find(_this._collectionName, query);
 
             case 3:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 4:
             case 'end':
               return _context4.stop();
           }
@@ -112,22 +115,22 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee4, _this);
     }));
 
-    return function (_x3, _x4) {
+    return function () {
       return _ref4.apply(this, arguments);
     };
   }();
 
-  this.getAll = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+  this.getById = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
       var query;
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
-              query = userID ? { ownerID: userID } : {};
+              query = userID ? { _id: id, ownerID: userID } : { _id: id };
               _context5.next = 3;
-              return _this._database.find(_this._collectionName, query);
+              return _this._database.findOne(_this._collectionName, query);
 
             case 3:
               return _context5.abrupt('return', _context5.sent);
@@ -140,27 +143,24 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee5, _this);
     }));
 
-    return function () {
+    return function (_x5) {
       return _ref5.apply(this, arguments);
     };
   }();
 
-  this.getById = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(id) {
-      var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-      var query;
+  this.update = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(model) {
       return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
           switch (_context6.prev = _context6.next) {
             case 0:
-              query = userID ? { _id: id, ownerID: userID } : { _id: id };
-              _context6.next = 3;
-              return _this._database.findOne(_this._collectionName, query);
+              _context6.next = 2;
+              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
 
-            case 3:
+            case 2:
               return _context6.abrupt('return', _context6.sent);
 
-            case 4:
+            case 3:
             case 'end':
               return _context6.stop();
           }
@@ -168,7 +168,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee6, _this);
     }));
 
-    return function (_x6) {
+    return function (_x7) {
       return _ref6.apply(this, arguments);
     };
   }();
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index 51522093..574274f9 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -28,14 +28,14 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _BaseMongoRepository2 = require('./BaseMongoRepository');
+var _BaseMongoDb2 = require('./BaseMongoDb');
 
-var _BaseMongoRepository3 = _interopRequireDefault(_BaseMongoRepository2);
+var _BaseMongoDb3 = _interopRequireDefault(_BaseMongoDb2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var MongoDb = function (_BaseMongoRepository) {
-  (0, _inherits3.default)(MongoDb, _BaseMongoRepository);
+var MongoDb = function (_BaseMongoDb) {
+  (0, _inherits3.default)(MongoDb, _BaseMongoDb);
 
   function MongoDb(database) {
     var _this2 = this;
@@ -72,6 +72,6 @@ var MongoDb = function (_BaseMongoRepository) {
   }
 
   return MongoDb;
-}(_BaseMongoRepository3.default);
+}(_BaseMongoDb3.default);
 
 exports.default = MongoDb;
\ No newline at end of file
diff --git a/dist/repository/TingoDb.js b/dist/repository/TingoDb.js
index bf7121f0..a27b4e19 100644
--- a/dist/repository/TingoDb.js
+++ b/dist/repository/TingoDb.js
@@ -42,14 +42,14 @@ var _tingodb2 = _interopRequireDefault(_tingodb);
 
 var _promisify = require('../lib/promisify');
 
-var _BaseMongoRepository2 = require('./BaseMongoRepository');
+var _BaseMongoDb2 = require('./BaseMongoDb');
 
-var _BaseMongoRepository3 = _interopRequireDefault(_BaseMongoRepository2);
+var _BaseMongoDb3 = _interopRequireDefault(_BaseMongoDb2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var TingoDb = function (_BaseMongoRepository) {
-  (0, _inherits3.default)(TingoDb, _BaseMongoRepository);
+var TingoDb = function (_BaseMongoDb) {
+  (0, _inherits3.default)(TingoDb, _BaseMongoDb);
 
   function TingoDb(path, options) {
     var _this2 = this;
@@ -350,6 +350,6 @@ var TingoDb = function (_BaseMongoRepository) {
   }
 
   return TingoDb;
-}(_BaseMongoRepository3.default);
+}(_BaseMongoDb3.default);
 
 exports.default = TingoDb;
\ No newline at end of file
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 0c0592ea..33104bce 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -32,24 +32,45 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
   (0, _classCallCheck3.default)(this, UserDatabaseRepository);
   this._collectionName = 'users';
 
-  this.createWithCredentials = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) {
-      var username, password, salt, passwordHash, modelToSave;
+  this.create = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(user) {
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
+            case 0:
+              throw new Error('The method is not implemented');
+
+            case 1:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.createWithCredentials = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
+      var username, password, salt, passwordHash, modelToSave;
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
             case 0:
               username = userCredentials.username, password = userCredentials.password;
-              _context.next = 3;
+              _context2.next = 3;
               return _PasswordHasher2.default.generateSalt();
 
             case 3:
-              salt = _context.sent;
-              _context.next = 6;
+              salt = _context2.sent;
+              _context2.next = 6;
               return _PasswordHasher2.default.hash(password, salt);
 
             case 6:
-              passwordHash = _context.sent;
+              passwordHash = _context2.sent;
               modelToSave = {
                 accessTokens: [],
                 created_at: new Date(),
@@ -58,247 +79,313 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
                 salt: salt,
                 username: username
               };
-              _context.next = 10;
+              _context2.next = 10;
               return _this._database.insertOne(_this._collectionName, modelToSave);
 
             case 10:
-              return _context.abrupt('return', _context.sent);
+              return _context2.abrupt('return', _context2.sent);
 
             case 11:
             case 'end':
-              return _context.stop();
+              return _context2.stop();
           }
         }
-      }, _callee, _this);
+      }, _callee2, _this);
     }));
 
-    return function (_x) {
-      return _ref.apply(this, arguments);
+    return function (_x2) {
+      return _ref2.apply(this, arguments);
     };
   }();
 
   this.deleteAccessToken = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userID, accessToken) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID, accessToken) {
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
-          switch (_context2.prev = _context2.next) {
+          switch (_context3.prev = _context3.next) {
             case 0:
-              _context2.next = 2;
+              _context3.next = 2;
               return _this._database.findAndModify(_this._collectionName, { _id: userID }, null, { $pull: { accessTokens: { accessToken: accessToken } } }, { new: true });
 
             case 2:
-              return _context2.abrupt('return', _context2.sent);
+              return _context3.abrupt('return', _context3.sent);
 
             case 3:
             case 'end':
-              return _context2.stop();
+              return _context3.stop();
           }
         }
-      }, _callee2, _this);
+      }, _callee3, _this);
     }));
 
-    return function (_x2, _x3) {
-      return _ref2.apply(this, arguments);
+    return function (_x3, _x4) {
+      return _ref3.apply(this, arguments);
     };
   }();
 
   this.deleteById = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(id) {
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
-          switch (_context3.prev = _context3.next) {
+          switch (_context4.prev = _context4.next) {
             case 0:
-              _context3.next = 2;
+              _context4.next = 2;
               return _this._database.remove(_this._collectionName, id);
 
             case 2:
-              return _context3.abrupt('return', _context3.sent);
+              return _context4.abrupt('return', _context4.sent);
 
             case 3:
             case 'end':
-              return _context3.stop();
+              return _context4.stop();
           }
         }
-      }, _callee3, _this);
+      }, _callee4, _this);
     }));
 
-    return function (_x4) {
-      return _ref3.apply(this, arguments);
+    return function (_x5) {
+      return _ref4.apply(this, arguments);
     };
   }();
 
+  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+    return _regenerator2.default.wrap(function _callee5$(_context5) {
+      while (1) {
+        switch (_context5.prev = _context5.next) {
+          case 0:
+            throw new Error('The method is not implemented');
+
+          case 1:
+          case 'end':
+            return _context5.stop();
+        }
+      }
+    }, _callee5, _this);
+  }));
+
   this.getByAccessToken = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(accessToken) {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(accessToken) {
       var user;
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
-          switch (_context4.prev = _context4.next) {
+          switch (_context6.prev = _context6.next) {
             case 0:
-              _context4.next = 2;
+              _context6.next = 2;
               return _this._database.findOne(_this._collectionName, { accessTokens: { $elemMatch: { accessToken: accessToken } } });
 
             case 2:
-              user = _context4.sent;
+              user = _context6.sent;
 
               if (user) {
-                _context4.next = 7;
+                _context6.next = 7;
                 break;
               }
 
-              _context4.next = 6;
+              _context6.next = 6;
               return _this._database.findOne(_this._collectionName, { 'accessTokens.accessToken': accessToken });
 
             case 6:
-              user = _context4.sent;
+              user = _context6.sent;
 
             case 7:
-              return _context4.abrupt('return', user);
+              return _context6.abrupt('return', user);
 
             case 8:
             case 'end':
-              return _context4.stop();
+              return _context6.stop();
           }
         }
-      }, _callee4, _this);
+      }, _callee6, _this);
     }));
 
-    return function (_x5) {
-      return _ref4.apply(this, arguments);
+    return function (_x6) {
+      return _ref6.apply(this, arguments);
+    };
+  }();
+
+  this.getById = function () {
+    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
+      return _regenerator2.default.wrap(function _callee7$(_context7) {
+        while (1) {
+          switch (_context7.prev = _context7.next) {
+            case 0:
+              throw new Error('The method is not implemented');
+
+            case 1:
+            case 'end':
+              return _context7.stop();
+          }
+        }
+      }, _callee7, _this);
+    }));
+
+    return function (_x7) {
+      return _ref7.apply(this, arguments);
     };
   }();
 
   this.getByUsername = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(username) {
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
+    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(username) {
+      return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
-          switch (_context5.prev = _context5.next) {
+          switch (_context8.prev = _context8.next) {
             case 0:
-              _context5.next = 2;
+              _context8.next = 2;
               return _this._database.findOne(_this._collectionName, { username: username });
 
             case 2:
-              return _context5.abrupt('return', _context5.sent);
+              return _context8.abrupt('return', _context8.sent);
 
             case 3:
             case 'end':
-              return _context5.stop();
+              return _context8.stop();
           }
         }
-      }, _callee5, _this);
+      }, _callee8, _this);
     }));
 
-    return function (_x6) {
-      return _ref5.apply(this, arguments);
+    return function (_x8) {
+      return _ref8.apply(this, arguments);
     };
   }();
 
   this.isUserNameInUse = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(username) {
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
+    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(username) {
+      return _regenerator2.default.wrap(function _callee9$(_context9) {
         while (1) {
-          switch (_context6.prev = _context6.next) {
+          switch (_context9.prev = _context9.next) {
             case 0:
-              _context6.next = 2;
+              _context9.next = 2;
               return _this.getByUsername(username);
 
             case 2:
-              return _context6.abrupt('return', !!_context6.sent);
+              return _context9.abrupt('return', !!_context9.sent);
 
             case 3:
             case 'end':
-              return _context6.stop();
+              return _context9.stop();
           }
         }
-      }, _callee6, _this);
+      }, _callee9, _this);
     }));
 
-    return function (_x7) {
-      return _ref6.apply(this, arguments);
+    return function (_x9) {
+      return _ref9.apply(this, arguments);
     };
   }();
 
   this.saveAccessToken = function () {
-    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(userID, tokenObject) {
-      return _regenerator2.default.wrap(function _callee7$(_context7) {
+    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(userID, tokenObject) {
+      return _regenerator2.default.wrap(function _callee10$(_context10) {
         while (1) {
-          switch (_context7.prev = _context7.next) {
+          switch (_context10.prev = _context10.next) {
             case 0:
-              _context7.next = 2;
+              _context10.next = 2;
               return _this._database.findAndModify(_this._collectionName, { _id: userID }, null, { $push: { accessTokens: tokenObject } }, { new: true });
 
             case 2:
-              return _context7.abrupt('return', _context7.sent);
+              return _context10.abrupt('return', _context10.sent);
 
             case 3:
             case 'end':
-              return _context7.stop();
+              return _context10.stop();
           }
         }
-      }, _callee7, _this);
+      }, _callee10, _this);
     }));
 
-    return function (_x8, _x9) {
-      return _ref7.apply(this, arguments);
+    return function (_x10, _x11) {
+      return _ref10.apply(this, arguments);
+    };
+  }();
+
+  this.update = function () {
+    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(model) {
+      return _regenerator2.default.wrap(function _callee11$(_context11) {
+        while (1) {
+          switch (_context11.prev = _context11.next) {
+            case 0:
+              throw new Error('The method is not implemented');
+
+            case 1:
+            case 'end':
+              return _context11.stop();
+          }
+        }
+      }, _callee11, _this);
+    }));
+
+    return function (_x12) {
+      return _ref11.apply(this, arguments);
     };
   }();
 
   this.validateLogin = function () {
-    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(username, password) {
+    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username, password) {
       var user, hash;
-      return _regenerator2.default.wrap(function _callee8$(_context8) {
+      return _regenerator2.default.wrap(function _callee12$(_context12) {
         while (1) {
-          switch (_context8.prev = _context8.next) {
+          switch (_context12.prev = _context12.next) {
             case 0:
-              _context8.prev = 0;
-              _context8.next = 3;
+              _context12.prev = 0;
+              _context12.next = 3;
               return _this._database.findOne(_this._collectionName, { username: username });
 
             case 3:
-              user = _context8.sent;
+              user = _context12.sent;
 
               if (user) {
-                _context8.next = 6;
+                _context12.next = 6;
                 break;
               }
 
               throw new _HttpError2.default('User doesn\'t exist', 404);
 
             case 6:
-              _context8.next = 8;
+              _context12.next = 8;
               return _PasswordHasher2.default.hash(password, user.salt);
 
             case 8:
-              hash = _context8.sent;
+              hash = _context12.sent;
 
               if (!(hash !== user.passwordHash)) {
-                _context8.next = 11;
+                _context12.next = 11;
                 break;
               }
 
               throw new _HttpError2.default('Wrong password');
 
             case 11:
-              return _context8.abrupt('return', user);
+              return _context12.abrupt('return', user);
 
             case 14:
-              _context8.prev = 14;
-              _context8.t0 = _context8['catch'](0);
-              throw _context8.t0;
+              _context12.prev = 14;
+              _context12.t0 = _context12['catch'](0);
+              throw _context12.t0;
 
             case 17:
             case 'end':
-              return _context8.stop();
+              return _context12.stop();
           }
         }
-      }, _callee8, _this, [[0, 14]]);
+      }, _callee12, _this, [[0, 14]]);
     }));
 
-    return function (_x10, _x11) {
-      return _ref8.apply(this, arguments);
+    return function (_x13, _x14) {
+      return _ref12.apply(this, arguments);
     };
   }();
 
   this._database = database;
-};
+}
+
+// eslint-disable-next-line no-unused-vars
+
+
+// eslint-disable-next-line no-unused-vars
+
+
+// eslint-disable-next-line no-unused-vars
+;
 
 exports.default = UserDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index ff6f94a6..e79e215e 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -79,7 +79,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(['id']), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = function () {
+var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = function () {
   function UserFileRepository(path) {
     var _this = this;
 
@@ -128,55 +128,41 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
       };
     }();
 
-    this.validateLogin = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) {
-        var user, hash;
+    this.deleteAccessToken = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userID, token) {
+        var user, userToSave;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.prev = 0;
-                _context2.next = 3;
-                return _this.getByUsername(username);
+                _context2.next = 2;
+                return _this.getById(userID);
 
-              case 3:
+              case 2:
                 user = _context2.sent;
 
                 if (user) {
-                  _context2.next = 6;
+                  _context2.next = 5;
                   break;
                 }
 
                 throw new Error('User doesn\'t exist');
 
-              case 6:
+              case 5:
+                userToSave = (0, _extends3.default)({}, user, {
+                  accessTokens: user.accessTokens.filter(function (tokenObject) {
+                    return tokenObject.accessToken !== token;
+                  })
+                });
                 _context2.next = 8;
-                return _PasswordHasher2.default.hash(password, user.salt);
+                return _this.update(userToSave);
 
               case 8:
-                hash = _context2.sent;
-
-                if (!(hash !== user.passwordHash)) {
-                  _context2.next = 11;
-                  break;
-                }
-
-                throw new Error('Wrong password');
-
-              case 11:
-                return _context2.abrupt('return', user);
-
-              case 14:
-                _context2.prev = 14;
-                _context2.t0 = _context2['catch'](0);
-                throw _context2.t0;
-
-              case 17:
               case 'end':
                 return _context2.stop();
             }
           }
-        }, _callee2, _this, [[0, 14]]);
+        }, _callee2, _this);
       }));
 
       return function (_x2, _x3) {
@@ -215,41 +201,55 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
       };
     }();
 
-    this.deleteAccessToken = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, token) {
-        var user, userToSave;
+    this.validateLogin = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(username, password) {
+        var user, hash;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this.getById(userID);
+                _context4.prev = 0;
+                _context4.next = 3;
+                return _this.getByUsername(username);
 
-              case 2:
+              case 3:
                 user = _context4.sent;
 
                 if (user) {
-                  _context4.next = 5;
+                  _context4.next = 6;
                   break;
                 }
 
                 throw new Error('User doesn\'t exist');
 
-              case 5:
-                userToSave = (0, _extends3.default)({}, user, {
-                  accessTokens: user.accessTokens.filter(function (tokenObject) {
-                    return tokenObject.accessToken !== token;
-                  })
-                });
+              case 6:
                 _context4.next = 8;
-                return _this.update(userToSave);
+                return _PasswordHasher2.default.hash(password, user.salt);
 
               case 8:
+                hash = _context4.sent;
+
+                if (!(hash !== user.passwordHash)) {
+                  _context4.next = 11;
+                  break;
+                }
+
+                throw new Error('Wrong password');
+
+              case 11:
+                return _context4.abrupt('return', user);
+
+              case 14:
+                _context4.prev = 14;
+                _context4.t0 = _context4['catch'](0);
+                throw _context4.t0;
+
+              case 17:
               case 'end':
                 return _context4.stop();
             }
           }
-        }, _callee4, _this);
+        }, _callee4, _this, [[0, 14]]);
       }));
 
       return function (_x5, _x6) {
@@ -354,17 +354,16 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
       return create;
     }()
   }, {
-    key: 'update',
+    key: 'deleteById',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(model) {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
               case 0:
-                this._fileManager.writeFile(model.id + '.json', model);
-                return _context7.abrupt('return', model);
+                this._fileManager.deleteFile(id + '.json');
 
-              case 2:
+              case 1:
               case 'end':
                 return _context7.stop();
             }
@@ -372,11 +371,11 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee7, this);
       }));
 
-      function update(_x10) {
+      function deleteById(_x10) {
         return _ref7.apply(this, arguments);
       }
 
-      return update;
+      return deleteById;
     }()
   }, {
     key: 'getAll',
@@ -402,6 +401,10 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
       return getAll;
     }()
+
+    // This isn't a good one to memoize as we can't key off user ID and there
+    // isn't a good way to clear the cache.
+
   }, {
     key: 'getById',
     value: function () {
@@ -458,21 +461,18 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
       return getByUsername;
     }()
-
-    // This isn't a good one to memoize as we can't key off user ID and there
-    // isn't a good way to clear the cache.
-
   }, {
-    key: 'deleteById',
+    key: 'update',
     value: function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(model) {
         return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
             switch (_context11.prev = _context11.next) {
               case 0:
-                this._fileManager.deleteFile(id + '.json');
+                this._fileManager.writeFile(model.id + '.json', model);
+                return _context11.abrupt('return', model);
 
-              case 1:
+              case 2:
               case 'end':
                 return _context11.stop();
             }
@@ -480,11 +480,11 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee11, this);
       }));
 
-      function deleteById(_x13) {
+      function update(_x13) {
         return _ref11.apply(this, arguments);
       }
 
-      return deleteById;
+      return update;
     }()
   }, {
     key: 'isUserNameInUse',
@@ -520,5 +520,5 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     }()
   }]);
   return UserFileRepository;
-}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class));
+}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class));
 exports.default = UserFileRepository;
\ No newline at end of file
diff --git a/src/controllers/UsersController.js b/src/controllers/UsersController.js
index cf165135..ed634efd 100644
--- a/src/controllers/UsersController.js
+++ b/src/controllers/UsersController.js
@@ -1,8 +1,8 @@
 // @flow
 
 import type {
+  IUserRepository,
   UserCredentials,
-  UserRepository,
 } from '../types';
 
 import basicAuthParser from 'basic-auth-parser';
@@ -13,9 +13,9 @@ import httpVerb from '../decorators/httpVerb';
 import route from '../decorators/route';
 
 class UsersController extends Controller {
-  _userRepository: UserRepository;
+  _userRepository: IUserRepository;
 
-  constructor(userRepository: UserRepository) {
+  constructor(userRepository: IUserRepository) {
     super();
     this._userRepository = userRepository;
   }
diff --git a/src/exports.js b/src/exports.js
index 95668014..0b515802 100644
--- a/src/exports.js
+++ b/src/exports.js
@@ -4,12 +4,9 @@ import logger from './lib/logger';
 import createApp from './app';
 import defaultBindings from './defaultBindings';
 import settings from './settings';
-
-import BaseMongoRepository from './repository/BaseMongoRepository';
 import MongoDb from './repository/MongoDb';
 
 export {
-  BaseMongoRepository,
   MongoDb,
   createApp,
   defaultBindings,
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 2103ee99..259dffa3 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -4,25 +4,25 @@ import type { File } from 'express';
 import type { DeviceServer } from 'spark-protocol';
 import type {
   Device,
-  DeviceAttributeRepository,
   DeviceAttributes,
-  Repository,
+  IBaseRepository,
+  IDeviceAttributeRepository,
+  IDeviceFirmwareRepository,
 } from '../types';
-import type DeviceFirmwareRepository from '../repository/DeviceFirmwareFileRepository';
 
 import ursa from 'ursa';
 import HttpError from '../lib/HttpError';
 
 class DeviceManager {
-  _deviceAttributeRepository: DeviceAttributeRepository;
-  _deviceFirmwareRepository: DeviceFirmwareRepository;
-  _deviceKeyRepository: Repository;
+  _deviceAttributeRepository: IDeviceAttributeRepository;
+  _deviceFirmwareRepository: IDeviceFirmwareRepository;
+  _deviceKeyRepository: IBaseRepository;
   _deviceServer: DeviceServer;
 
   constructor(
-    deviceAttributeRepository: DeviceAttributeRepository,
-    deviceFirmwareRepository: DeviceFirmwareRepository,
-    deviceKeyRepository: Repository,
+    deviceAttributeRepository: IDeviceAttributeRepository,
+    deviceFirmwareRepository: IDeviceFirmwareRepository,
+    deviceKeyRepository: IBaseRepository,
     deviceServer: DeviceServer,
   ) {
     this._deviceAttributeRepository = deviceAttributeRepository;
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 35676bb0..325288fc 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -3,7 +3,7 @@
 import type {
   Event,
   IWebhookLogger,
-  Repository,
+  IWebhookRepository,
   RequestOptions,
   RequestType,
   Webhook,
@@ -67,11 +67,11 @@ class WebhookManager {
   _eventPublisher: EventPublisher;
   _subscriptionIDsByWebhookID: Map = new Map();
   _errorsCountByWebhookID: Map = new Map();
-  _webhookRepository: Repository;
+  _webhookRepository: IWebhookRepository;
   _webhookLogger: IWebhookLogger;
 
   constructor(
-    webhookRepository: Repository,
+    webhookRepository: IWebhookRepository,
     eventPublisher: EventPublisher,
     webhookLogger: IWebhookLogger,
   ) {
diff --git a/src/repository/BaseMongoRepository.js b/src/repository/BaseMongoDb.js
similarity index 95%
rename from src/repository/BaseMongoRepository.js
rename to src/repository/BaseMongoDb.js
index 89730864..3d723724 100644
--- a/src/repository/BaseMongoRepository.js
+++ b/src/repository/BaseMongoDb.js
@@ -1,5 +1,7 @@
 // @flow
 
+import type { IBaseDatabase } from '../types';
+
 import { ObjectId } from 'mongodb';
 
 const deepToObjectIdCast = (node: any): any => {
@@ -15,7 +17,7 @@ const deepToObjectIdCast = (node: any): any => {
   return node;
 };
 
-class BaseMongoRepository {
+class BaseMongoDb implements IBaseDatabase {
   insertOne = async (
     collectionName: string,
     entity: Object,
@@ -104,4 +106,4 @@ class BaseMongoRepository {
   };
 }
 
-export default BaseMongoRepository;
+export default BaseMongoDb;
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 6e092fa0..2a873ed3 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -1,12 +1,16 @@
 // @flow
 
-import type { Database, DeviceAttributes } from '../types';
-
-class DeviceAttributeDatabaseRepository {
-  _database: Object;
+import type {
+  DeviceAttributes,
+  IBaseDatabase,
+  IDeviceAttributeRepository,
+} from '../types';
+
+class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
+  _database: IBaseDatabase;
   _collectionName: string = 'deviceAttributes';
 
-  constructor(database: Database) {
+  constructor(database: IBaseDatabase) {
     this._database = database;
   }
 
@@ -14,15 +18,6 @@ class DeviceAttributeDatabaseRepository {
     throw new Error('The method is not implemented');
   };
 
-  update = async (model: DeviceAttributes): Promise =>
-    await this._database.findAndModify(
-      this._collectionName,
-      { _id: model.deviceID },
-      null,
-      { $set: { ...model, _id: model.deviceID, timeStamp: new Date() } },
-      { new: true, upsert: true },
-    );
-
   deleteById = async (id: string): Promise =>
     await this._database.remove(this._collectionName, id);
 
@@ -43,7 +38,16 @@ class DeviceAttributeDatabaseRepository {
   ): Promise => {
     const query = userID ? { _id: id, ownerID: userID } : { _id: id };
     return await this._database.findOne(this._collectionName, query);
-  }
+  };
+
+  update = async (model: DeviceAttributes): Promise =>
+    await this._database.findAndModify(
+      this._collectionName,
+      { _id: model.deviceID },
+      null,
+      { $set: { ...model, _id: model.deviceID, timeStamp: new Date() } },
+      { new: true, upsert: true },
+    );
 }
 
 export default DeviceAttributeDatabaseRepository;
diff --git a/src/repository/DeviceFirmwareFileRepository.js b/src/repository/DeviceFirmwareFileRepository.js
index 4e1ead09..98f74b37 100644
--- a/src/repository/DeviceFirmwareFileRepository.js
+++ b/src/repository/DeviceFirmwareFileRepository.js
@@ -1,8 +1,10 @@
 // @flow
 
+import type { IDeviceFirmwareRepository } from '../types';
+
 import { FileManager, memoizeGet } from 'spark-protocol';
 
-class DeviceFirmwareFileRepository {
+class DeviceFirmwareFileRepository implements IDeviceFirmwareRepository {
   _fileManager: FileManager;
 
   constructor(path: string) {
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index ff824d43..512d882e 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -1,8 +1,8 @@
 // @flow
 
-import BaseMongoRepository from './BaseMongoRepository';
+import BaseMongoDb from './BaseMongoDb';
 
-class MongoDb extends BaseMongoRepository {
+class MongoDb extends BaseMongoDb {
   _database: Object;
 
   constructor(database: Object) {
diff --git a/src/repository/TingoDb.js b/src/repository/TingoDb.js
index 17e1aef2..1c706959 100644
--- a/src/repository/TingoDb.js
+++ b/src/repository/TingoDb.js
@@ -4,9 +4,9 @@ import fs from 'fs';
 import mkdirp from 'mkdirp';
 import tingoDb from 'tingodb';
 import { promisify } from '../lib/promisify';
-import BaseMongoRepository from './BaseMongoRepository';
+import BaseMongoDb from './BaseMongoDb';
 
-class TingoDb extends BaseMongoRepository {
+class TingoDb extends BaseMongoDb {
   _database: Object;
 
   constructor(path: string, options: Object) {
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index 82d5c52a..c4baa190 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -1,18 +1,29 @@
 // @flow
 
-import type { Database, TokenObject, User, UserCredentials } from '../types';
+import type {
+  IBaseDatabase,
+  IUserRepository,
+  TokenObject,
+  User,
+  UserCredentials,
+} from '../types';
 
 import PasswordHasher from '../lib/PasswordHasher';
 import HttpError from '../lib/HttpError';
 
-class UserDatabaseRepository {
-  _database: Object;
+class UserDatabaseRepository implements IUserRepository {
+  _database: IBaseDatabase;
   _collectionName: string = 'users';
 
-  constructor(database: Database) {
+  constructor(database: IBaseDatabase) {
     this._database = database;
   }
 
+  // eslint-disable-next-line no-unused-vars
+  create = async (user: $Shape): Promise => {
+    throw new Error('The method is not implemented');
+  };
+
   createWithCredentials = async (userCredentials: UserCredentials): Promise => {
     const { username, password } = userCredentials;
 
@@ -45,6 +56,10 @@ class UserDatabaseRepository {
   deleteById = async (id: string): Promise =>
     await this._database.remove(this._collectionName, id);
 
+  getAll = async (): Promise> => {
+    throw new Error('The method is not implemented');
+  };
+
   getByAccessToken = async (accessToken: string): Promise => {
     let user = await this._database.findOne(
       this._collectionName,
@@ -62,6 +77,11 @@ class UserDatabaseRepository {
     return user;
   };
 
+  // eslint-disable-next-line no-unused-vars
+  getById = async (id: string): Promise => {
+    throw new Error('The method is not implemented');
+  };
+
   getByUsername = async (username: string): Promise =>
     await this._database.findOne(
       this._collectionName,
@@ -82,6 +102,11 @@ class UserDatabaseRepository {
     { new: true },
   );
 
+  // eslint-disable-next-line no-unused-vars
+  update = async (model: User): Promise => {
+    throw new Error('The method is not implemented');
+  };
+
   validateLogin = async (username: string, password: string): Promise => {
     try {
       const user = await this._database.findOne(
diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js
index 5a382b94..b8a8740c 100644
--- a/src/repository/UserFileRepository.js
+++ b/src/repository/UserFileRepository.js
@@ -1,13 +1,13 @@
 // @flow
 
-import type { TokenObject, User, UserCredentials } from '../types';
+import type { IUserRepository, TokenObject, User, UserCredentials } from '../types';
 
 import uuid from 'uuid';
 import { JSONFileManager, memoizeGet, memoizeSet } from 'spark-protocol';
 import PasswordHasher from '../lib/PasswordHasher';
 import HttpError from '../lib/HttpError';
 
-class UserFileRepository {
+class UserFileRepository implements IUserRepository {
   _fileManager: JSONFileManager;
 
   constructor(path: string) {
@@ -49,10 +49,26 @@ class UserFileRepository {
     return modelToSave;
   }
 
-  @memoizeSet()
-  async update(model: User): Promise {
-    this._fileManager.writeFile(`${model.id}.json`, model);
-    return model;
+  deleteAccessToken = async (userID: string, token: string): Promise<*> => {
+    const user = await this.getById(userID);
+    if (!user) {
+      throw new Error('User doesn\'t exist');
+    }
+
+    const userToSave = {
+      ...user,
+      accessTokens: user.accessTokens.filter(
+        (tokenObject: TokenObject): boolean =>
+        tokenObject.accessToken !== token,
+      ),
+    };
+
+    await this.update(userToSave);
+  };
+
+  @memoizeSet(['id'])
+  async deleteById(id: string): Promise {
+    this._fileManager.deleteFile(`${id}.json`);
   }
 
   @memoizeGet()
@@ -60,6 +76,15 @@ class UserFileRepository {
     return this._fileManager.getAllData();
   }
 
+  // This isn't a good one to memoize as we can't key off user ID and there
+  // isn't a good way to clear the cache.
+  getByAccessToken = async (accessToken: string): Promise =>
+    (await this.getAll()).find((user: User): boolean =>
+      user.accessTokens.some((tokenObject: TokenObject): boolean =>
+        tokenObject.accessToken === accessToken,
+      ),
+    );
+
   @memoizeGet(['id'])
   async getById(id: string): Promise {
     return this._fileManager.getFile(`${id}.json`);
@@ -72,6 +97,12 @@ class UserFileRepository {
     );
   }
 
+  @memoizeSet()
+  async update(model: User): Promise {
+    this._fileManager.writeFile(`${model.id}.json`, model);
+    return model;
+  }
+
   validateLogin = async (username: string, password: string): Promise => {
     try {
       const user = await this.getByUsername(username);
@@ -91,38 +122,6 @@ class UserFileRepository {
     }
   };
 
-  // This isn't a good one to memoize as we can't key off user ID and there
-  // isn't a good way to clear the cache.
-  getByAccessToken = async (accessToken: string): Promise =>
-    (await this.getAll()).find((user: User): boolean =>
-      user.accessTokens.some((tokenObject: TokenObject): boolean =>
-        tokenObject.accessToken === accessToken,
-      ),
-    );
-
-  deleteAccessToken = async (userID: string, token: string): Promise<*> => {
-    const user = await this.getById(userID);
-    if (!user) {
-      throw new Error('User doesn\'t exist');
-    }
-
-    const userToSave = {
-      ...user,
-      accessTokens: user.accessTokens.filter(
-        (tokenObject: TokenObject): boolean =>
-          tokenObject.accessToken !== token,
-      ),
-    };
-
-    await this.update(userToSave);
-  };
-
-  @memoizeSet(['id'])
-  async deleteById(id: string): Promise {
-    this._fileManager.deleteFile(`${id}.json`);
-  }
-
-
   @memoizeGet(['username'])
   async isUserNameInUse(username: string): Promise {
     return (await this.getAll()).some((user: User): boolean =>
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index 34ba9764..b526c274 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -1,12 +1,12 @@
 // @flow
 
-import type { Database, Webhook } from '../types';
+import type { IBaseDatabase, IWebhookRepository, Webhook } from '../types';
 
-class WebhookDatabaseRepository {
-  _database: Object;
+class WebhookDatabaseRepository implements IWebhookRepository {
+  _database: IBaseDatabase;
   _collectionName: string = 'webhooks';
 
-  constructor(database: Database) {
+  constructor(database: IBaseDatabase) {
     this._database = database;
   }
 
diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js
index c3c54efd..06bcb614 100644
--- a/src/repository/WebhookFileRepository.js
+++ b/src/repository/WebhookFileRepository.js
@@ -1,12 +1,12 @@
 // @flow
 
-import type { Webhook, WebhookMutator } from '../types';
+import type { IWebhookRepository, Webhook, WebhookMutator } from '../types';
 
 import uuid from 'uuid';
 import { JSONFileManager, memoizeGet, memoizeSet } from 'spark-protocol';
 import HttpError from '../lib/HttpError';
 
-class WebhookFileRepository {
+class WebhookFileRepository implements IWebhookRepository {
   _fileManager: JSONFileManager;
 
   constructor(path: string) {
diff --git a/src/types.js b/src/types.js
index 2a5cf64f..3d05031f 100644
--- a/src/types.js
+++ b/src/types.js
@@ -1,14 +1,8 @@
 // @flow
+/* eslint-disable */
 
 import type { File } from 'express';
 
-export type Database = {
-  find: (collectionName: string, ...args: Array) => Promise<*>,
-  findOne: (collectionName: string, ...args: Array) => Promise<*>,
-  findAndModify: (collectionName: string, ...args: Array) => Promise<*>,
-  remove: (collectionName: string, id: string) => Promise<*>,
-};
-
 export type Webhook = {
   auth?: { password: string, username: string },
   created_at: Date,
@@ -225,4 +219,40 @@ export type Product = {
   requires_activation_codes: boolean,
   slug: string,
   type: 'Consumer' | 'Hobbyist' | 'Industrial',
+};
+
+export interface IBaseRepository {
+  create(model: TModel | $Shape): Promise;
+  deleteById(id: string): Promise;
+  getAll(): Promise>;
+  getById(id: string, userID: ?string): Promise;
+  update(model: TModel): Promise;
+}
+
+export interface IWebhookRepository extends IBaseRepository {}
+
+export interface IDeviceAttributeRepository extends IBaseRepository {
+  doesUserHaveAccess(id: string, userID: string): Promise;
+}
+
+export interface IUserRepository extends IBaseRepository {
+  createWithCredentials(credentials: UserCredentials): Promise;
+  deleteAccessToken(userID: string, accessToken: string): Promise;
+  getByAccessToken(accessToken: string): Promise;
+  getByUsername(username: string): Promise;
+  isUserNameInUse(username: string): Promise;
+  saveAccessToken(userID: string, tokenObject: TokenObject): Promise;
+  validateLogin(username: string, password: string): Promise;
+}
+
+export interface IDeviceFirmwareRepository {
+  getByName(appName: string): ?Buffer,
+}
+
+export interface IBaseDatabase {
+  find(collectionName: string, ...args: Array): Promise<*>;
+  findAndModify(collectionName: string, ...args: Array): Promise<*>;
+  findOne(collectionName: string, ...args: Array): Promise<*>;
+  insertOne(collectionName: string, ...args: Array): Promise<*>;
+  remove(collectionName: string, id: string): Promise<*>;
 }

From 37e0a4ff03f38a5629e515cec1b1857af9b7ca13 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 4 Jun 2017 02:09:24 +0200
Subject: [PATCH 375/504] implement deviceKeyDatabase repo

---
 dist/defaultBindings.js                       |   5 +
 dist/repository/BaseMongoDb.js                | 349 ++++++++++++++++++
 .../repository/DeviceKeyDatabaseRepository.js | 109 ++++++
 src/defaultBindings.js                        |   6 +
 src/repository/DeviceKeyDatabaseRepository.js |  38 ++
 src/types.js                                  |   5 +
 test/DeviceClaimsController.test.js           |   2 +-
 test/DevicesController.test.js                |   2 +-
 test/ProvisioningController.test.js           |   2 +-
 9 files changed, 515 insertions(+), 3 deletions(-)
 create mode 100644 dist/repository/BaseMongoDb.js
 create mode 100644 dist/repository/DeviceKeyDatabaseRepository.js
 create mode 100644 src/repository/DeviceKeyDatabaseRepository.js

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 3fedb00b..a3854e21 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -70,6 +70,10 @@ var _DeviceAttributeDatabaseRepository = require('./repository/DeviceAttributeDa
 
 var _DeviceAttributeDatabaseRepository2 = _interopRequireDefault(_DeviceAttributeDatabaseRepository);
 
+var _DeviceKeyDatabaseRepository = require('./repository/DeviceKeyDatabaseRepository');
+
+var _DeviceKeyDatabaseRepository2 = _interopRequireDefault(_DeviceKeyDatabaseRepository);
+
 var _UserDatabaseRepository = require('./repository/UserDatabaseRepository');
 
 var _UserDatabaseRepository2 = _interopRequireDefault(_UserDatabaseRepository);
@@ -126,6 +130,7 @@ exports.default = function (container, newSettings) {
   // Repositories
   container.bindClass('DeviceAttributeRepository', _DeviceAttributeDatabaseRepository2.default, ['Database']);
   container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']);
+  container.bindClass('DeviceKeyRepository', _DeviceKeyDatabaseRepository2.default, ['Database']);
   container.bindClass('UserRepository', _UserDatabaseRepository2.default, ['Database']);
   container.bindClass('WebhookRepository', _WebhookDatabaseRepository2.default, ['Database']);
 };
\ No newline at end of file
diff --git a/dist/repository/BaseMongoDb.js b/dist/repository/BaseMongoDb.js
new file mode 100644
index 00000000..92ea5130
--- /dev/null
+++ b/dist/repository/BaseMongoDb.js
@@ -0,0 +1,349 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _mongodb = require('mongodb');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var deepToObjectIdCast = function deepToObjectIdCast(node) {
+  (0, _keys2.default)(node).forEach(function (key) {
+    if (node[key] === Object(node[key])) {
+      deepToObjectIdCast(node[key]);
+    }
+    if (key === '_id') {
+      // eslint-disable-next-line
+      node[key] = new _mongodb.ObjectId(node[key]);
+    }
+  });
+  return node;
+};
+
+var BaseMongoDb = function BaseMongoDb() {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, BaseMongoDb);
+
+  this.insertOne = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
+                  var insertResult;
+                  return _regenerator2.default.wrap(function _callee$(_context) {
+                    while (1) {
+                      switch (_context.prev = _context.next) {
+                        case 0:
+                          _context.next = 2;
+                          return collection.insertOne(entity);
+
+                        case 2:
+                          insertResult = _context.sent;
+                          return _context.abrupt('return', _this.__translateResultItem(insertResult.ops[0]));
+
+                        case 4:
+                        case 'end':
+                          return _context.stop();
+                      }
+                    }
+                  }, _callee, _this);
+                }));
+
+                return function (_x3) {
+                  return _ref2.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x, _x2) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.find = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
+      for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+        args[_key - 2] = arguments[_key];
+      }
+
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
+                  var resultItems;
+                  return _regenerator2.default.wrap(function _callee3$(_context3) {
+                    while (1) {
+                      switch (_context3.prev = _context3.next) {
+                        case 0:
+                          _context3.next = 2;
+                          return collection.find.apply(collection, [_this.__translateQuery(query)].concat(args)).toArray();
+
+                        case 2:
+                          resultItems = _context3.sent;
+                          return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
+
+                        case 4:
+                        case 'end':
+                          return _context3.stop();
+                      }
+                    }
+                  }, _callee3, _this);
+                }));
+
+                return function (_x6) {
+                  return _ref4.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 3:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x4, _x5) {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.findAndModify = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, sort, updateQuery) {
+      for (var _len2 = arguments.length, args = Array(_len2 > 4 ? _len2 - 4 : 0), _key2 = 4; _key2 < _len2; _key2++) {
+        args[_key2 - 4] = arguments[_key2];
+      }
+
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
+        while (1) {
+          switch (_context6.prev = _context6.next) {
+            case 0:
+              _context6.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
+                  var modifyResult;
+                  return _regenerator2.default.wrap(function _callee5$(_context5) {
+                    while (1) {
+                      switch (_context5.prev = _context5.next) {
+                        case 0:
+                          _context5.next = 2;
+                          return collection.findAndModify.apply(collection, [_this.__translateQuery(query), sort, _this.__translateQuery(updateQuery)].concat(args));
+
+                        case 2:
+                          modifyResult = _context5.sent;
+                          return _context5.abrupt('return', _this.__translateResultItem(modifyResult.value));
+
+                        case 4:
+                        case 'end':
+                          return _context5.stop();
+                      }
+                    }
+                  }, _callee5, _this);
+                }));
+
+                return function (_x11) {
+                  return _ref6.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context6.abrupt('return', _context6.sent);
+
+            case 3:
+            case 'end':
+              return _context6.stop();
+          }
+        }
+      }, _callee6, _this);
+    }));
+
+    return function (_x7, _x8, _x9, _x10) {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.findOne = function () {
+    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
+      for (var _len3 = arguments.length, args = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
+        args[_key3 - 2] = arguments[_key3];
+      }
+
+      return _regenerator2.default.wrap(function _callee8$(_context8) {
+        while (1) {
+          switch (_context8.prev = _context8.next) {
+            case 0:
+              _context8.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
+                  var resultItem;
+                  return _regenerator2.default.wrap(function _callee7$(_context7) {
+                    while (1) {
+                      switch (_context7.prev = _context7.next) {
+                        case 0:
+                          _context7.next = 2;
+                          return collection.findOne.apply(collection, [_this.__translateQuery(query)].concat(args));
+
+                        case 2:
+                          resultItem = _context7.sent;
+                          return _context7.abrupt('return', _this.__translateResultItem(resultItem));
+
+                        case 4:
+                        case 'end':
+                          return _context7.stop();
+                      }
+                    }
+                  }, _callee7, _this);
+                }));
+
+                return function (_x14) {
+                  return _ref8.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context8.abrupt('return', _context8.sent);
+
+            case 3:
+            case 'end':
+              return _context8.stop();
+          }
+        }
+      }, _callee8, _this);
+    }));
+
+    return function (_x12, _x13) {
+      return _ref7.apply(this, arguments);
+    };
+  }();
+
+  this.remove = function () {
+    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, id) {
+      return _regenerator2.default.wrap(function _callee10$(_context10) {
+        while (1) {
+          switch (_context10.prev = _context10.next) {
+            case 0:
+              _context10.next = 2;
+              return _this.__runForCollection(collectionName, function () {
+                var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
+                  return _regenerator2.default.wrap(function _callee9$(_context9) {
+                    while (1) {
+                      switch (_context9.prev = _context9.next) {
+                        case 0:
+                          _context9.next = 2;
+                          return collection.remove(_this.__translateQuery({ _id: id }));
+
+                        case 2:
+                          return _context9.abrupt('return', _context9.sent);
+
+                        case 3:
+                        case 'end':
+                          return _context9.stop();
+                      }
+                    }
+                  }, _callee9, _this);
+                }));
+
+                return function (_x17) {
+                  return _ref10.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context10.abrupt('return', _context10.sent);
+
+            case 3:
+            case 'end':
+              return _context10.stop();
+          }
+        }
+      }, _callee10, _this);
+    }));
+
+    return function (_x15, _x16) {
+      return _ref9.apply(this, arguments);
+    };
+  }();
+
+  this.__runForCollection = function () {
+    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
+      return _regenerator2.default.wrap(function _callee11$(_context11) {
+        while (1) {
+          switch (_context11.prev = _context11.next) {
+            case 0:
+              throw new Error('Not implemented ' + callback.toString());
+
+            case 1:
+            case 'end':
+              return _context11.stop();
+          }
+        }
+      }, _callee11, _this);
+    }));
+
+    return function (_x18, _x19) {
+      return _ref11.apply(this, arguments);
+    };
+  }();
+
+  this.__translateQuery = function (query) {
+    return deepToObjectIdCast(query);
+  };
+
+  this.__translateResultItem = function (item) {
+    if (!item) {
+      return null;
+    }
+    var _id = item._id,
+        otherProps = (0, _objectWithoutProperties3.default)(item, ['_id']);
+
+    return (0, _extends3.default)({}, otherProps, { id: _id.toString() });
+  };
+};
+
+exports.default = BaseMongoDb;
\ No newline at end of file
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
new file mode 100644
index 00000000..c1607bbc
--- /dev/null
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -0,0 +1,109 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, DeviceKeyDatabaseRepository);
+  this._collectionName = 'deviceKeys';
+
+  this.deleteById = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID) {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return _this._database.remove(_this._collectionName, deviceID);
+
+            case 2:
+              return _context.abrupt('return', _context.sent);
+
+            case 3:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.getById = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
+      var keyObject;
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: deviceID });
+
+            case 2:
+              keyObject = _context2.sent;
+              return _context2.abrupt('return', keyObject ? keyObject.key : null);
+
+            case 4:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x2) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.update = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, key) {
+      var keyObject;
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              _context3.next = 2;
+              return _this._database.findAndModify(_this._collectionName, { _id: deviceID }, null, { $set: { _id: deviceID, key: key } }, { new: true, upsert: true });
+
+            case 2:
+              keyObject = _context3.sent;
+              return _context3.abrupt('return', keyObject.key);
+
+            case 4:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function (_x3, _x4) {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this._database = database;
+};
+
+exports.default = DeviceKeyDatabaseRepository;
\ No newline at end of file
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 9f6913c3..58602016 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -20,6 +20,7 @@ import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileReposit
 import TingoDb from './repository/TingoDb';
 import DeviceAttributeDatabaseRepository from
   './repository/DeviceAttributeDatabaseRepository';
+import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
 import settings from './settings';
@@ -134,6 +135,11 @@ export default (container: Container, newSettings: Settings) => {
     DeviceFirmwareFileRepository,
     ['FIRMWARE_DIRECTORY'],
   );
+  container.bindClass(
+    'DeviceKeyRepository',
+    DeviceKeyDatabaseRepository,
+    ['Database'],
+  );
   container.bindClass(
     'UserRepository',
     UserDatabaseRepository,
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
new file mode 100644
index 00000000..0f024ee3
--- /dev/null
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -0,0 +1,38 @@
+// @flow
+
+import type { IBaseDatabase, IDeviceKeyRepository } from '../types';
+
+class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
+  _database: IBaseDatabase;
+  _collectionName: string = 'deviceKeys';
+
+  constructor(database: IBaseDatabase) {
+    this._database = database;
+  }
+
+  deleteById = async (deviceID: string): Promise =>
+    await this._database.remove(this._collectionName, deviceID);
+
+  getById = async (deviceID: string): Promise => {
+    const keyObject = await this._database.findOne(
+      this._collectionName,
+      { _id: deviceID },
+    );
+
+    return keyObject ? keyObject.key : null;
+  }
+
+  update = async (deviceID: string, key: string): Promise => {
+    const keyObject = await this._database.findAndModify(
+      this._collectionName,
+      { _id: deviceID },
+      null,
+      { $set: { _id: deviceID, key } },
+      { new: true, upsert: true },
+    );
+
+    return keyObject.key;
+  }
+}
+
+export default DeviceKeyDatabaseRepository;
diff --git a/src/types.js b/src/types.js
index 3d05031f..c66035ba 100644
--- a/src/types.js
+++ b/src/types.js
@@ -235,6 +235,11 @@ export interface IDeviceAttributeRepository extends IBaseRepository;
 }
 
+export interface IDeviceKeyRepository {
+  getById(deviceID: string): Promise;
+  update(deviceID: string, key: string): Promise;
+}
+
 export interface IUserRepository extends IBaseRepository {
   createWithCredentials(credentials: UserCredentials): Promise;
   deleteAccessToken(userID: string, accessToken: string): Promise;
diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js
index 68229a75..e7ef9b71 100644
--- a/test/DeviceClaimsController.test.js
+++ b/test/DeviceClaimsController.test.js
@@ -72,5 +72,5 @@ test(
 test.after.always(async (): Promise => {
   await container.constitute('UserRepository').deleteById(testUser.id);
   await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').delete(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
 });
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index 055f064e..4b4e57a5 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -568,5 +568,5 @@ test.after.always(async (): Promise => {
   await TestData.deleteCustomFirmwareBinary(customFirmwareFilePath);
   await container.constitute('UserRepository').deleteById(testUser.id);
   await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').delete(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
 });
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index c990aa15..e1aa8841 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -73,5 +73,5 @@ test('should throw an error if public key is not provided', async t => {
 test.after.always(async (): Promise => {
   await container.constitute('UserRepository').deleteById(testUser.id);
   await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').delete(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
 });

From 597cafe8b378150e3fe6414aa26583698da4a8b8 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 4 Jun 2017 02:27:06 +0200
Subject: [PATCH 376/504] throw a error if device is already claimed by the
 user

---
 dist/managers/DeviceManager.js | 14 +++++++++++---
 src/managers/DeviceManager.js  |  4 ++++
 test/DevicesController.test.js | 16 ++++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index ad4ca1d4..762f0dc8 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -72,16 +72,24 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('The device belongs to someone else.');
 
             case 7:
+              if (!(deviceAttributes.ownerID && deviceAttributes.ownerID === userID)) {
+                _context.next = 9;
+                break;
+              }
+
+              throw new _HttpError2.default('The device is already claimed.');
+
+            case 9:
               attributesToSave = (0, _extends3.default)({}, deviceAttributes, {
                 ownerID: userID
               });
-              _context.next = 10;
+              _context.next = 12;
               return _this._deviceAttributeRepository.update(attributesToSave);
 
-            case 10:
+            case 12:
               return _context.abrupt('return', _context.sent);
 
-            case 11:
+            case 13:
             case 'end':
               return _context.stop();
           }
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 259dffa3..388f95ba 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -45,6 +45,10 @@ class DeviceManager {
       throw new HttpError('The device belongs to someone else.');
     }
 
+    if (deviceAttributes.ownerID && deviceAttributes.ownerID === userID) {
+      throw new HttpError('The device is already claimed.');
+    }
+
     const attributesToSave = {
       ...deviceAttributes,
       ownerID: userID,
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index 055f064e..6ff05313 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -194,6 +194,22 @@ test.serial('should claim device', async t => {
   t.is(getDeviceResponse.status, 200);
 });
 
+test.serial(
+  'should throw a error if device is already claimed by the user',
+  async t => {
+    const claimDeviceResponse = await request(app)
+      .post('/v1/devices')
+      .set('Content-Type', 'application/x-www-form-urlencoded')
+      .send({
+        access_token: userToken,
+        id: DEVICE_ID,
+      });
+
+    t.is(claimDeviceResponse.status, 400);
+    t.is(claimDeviceResponse.body.error, 'The device is already claimed.');
+  },
+);
+
 test.serial(
   'should throw an error if device belongs to somebody else',
   async t => {

From ffacabe38e5d89dffe66b8363a667077bdd13181 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 4 Jun 2017 12:53:43 -0700
Subject: [PATCH 377/504] Updates and improvements from setting up
 particle-collider

---
 dist/OAuthModel.js                          |   10 +-
 dist/lib/logger.js                          |   64 +-
 dist/managers/FirmwareCompilationManager.js |    6 +-
 dist/oauthClients.json                      |   10 +
 dist/settings.js                            |    4 +-
 package-lock.json                           | 4726 +++++++++++++++++++
 package.json                                |    5 +-
 src/OAuthModel.js                           |    8 +-
 src/lib/WebhookLogger.js                    |    6 +-
 src/lib/logger.js                           |   23 +-
 src/managers/DeviceManager.js               |    1 +
 src/managers/FirmwareCompilationManager.js  |    3 +-
 src/oauthClients.json                       |   15 +-
 src/settings.js                             |    4 +-
 src/types.js                                |    2 +-
 test/DeviceClaimsController.test.js         |    2 +-
 test/ProvisioningController.test.js         |    2 +-
 test/setup/settings.js                      |    2 +-
 18 files changed, 4825 insertions(+), 68 deletions(-)
 create mode 100644 dist/oauthClients.json
 create mode 100644 package-lock.json

diff --git a/dist/OAuthModel.js b/dist/OAuthModel.js
index 6c7ca2dd..ff79d906 100644
--- a/dist/OAuthModel.js
+++ b/dist/OAuthModel.js
@@ -16,13 +16,13 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _oauthClients = require('./oauthClients.json');
+
+var _oauthClients2 = _interopRequireDefault(_oauthClients);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var OAUTH_CLIENTS = [{
-  clientId: 'CLI2',
-  clientSecret: 'client_secret_here',
-  grants: ['password']
-}];
+var OAUTH_CLIENTS = _oauthClients2.default;
 
 var OauthModel = function OauthModel(userRepository) {
   var _this = this;
diff --git a/dist/lib/logger.js b/dist/lib/logger.js
index 17958cd0..2675bb33 100644
--- a/dist/lib/logger.js
+++ b/dist/lib/logger.js
@@ -1,39 +1,22 @@
-"use strict";
+'use strict';
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
-var _createClass2 = require("babel-runtime/helpers/createClass");
+var _createClass2 = require('babel-runtime/helpers/createClass');
 
 var _createClass3 = _interopRequireDefault(_createClass2);
 
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+var _chalk = require('chalk');
+
+var _chalk2 = _interopRequireDefault(_chalk);
 
-/**
-*    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
-*
-*    This program is free software: you can redistribute it and/or modify
-*    it under the terms of the GNU Affero General Public License, version 3,
-*    as published by the Free Software Foundation.
-*
-*    This program is distributed in the hope that it will be useful,
-*    but WITHOUT ANY WARRANTY; without even the implied warranty of
-*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-*    GNU Affero General Public License for more details.
-*
-*    You should have received a copy of the GNU Affero General Public License
-*    along with this program.  If not, see .
-*
-*    You can download the source here: https://github.com/spark/spark-server
-*
-* 
-*
-*/
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var Logger = function () {
   function Logger() {
@@ -41,23 +24,38 @@ var Logger = function () {
   }
 
   (0, _createClass3.default)(Logger, null, [{
-    key: "log",
+    key: 'log',
     value: function log() {
-      var _console;
-
       // eslint-disable-next-line prefer-rest-params
-      (_console = console).log.apply(_console, arguments);
+      //console.log(...arguments);
     }
   }, {
-    key: "error",
+    key: 'error',
     value: function error() {
-      var _console2;
-
       // eslint-disable-next-line prefer-rest-params
-      (_console2 = console).error.apply(_console2, arguments);
+      console.error(_chalk2.default.red.apply(_chalk2.default, arguments));
     }
   }]);
   return Logger;
-}();
+}(); /**
+     *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
+     *
+     *    This program is free software: you can redistribute it and/or modify
+     *    it under the terms of the GNU Affero General Public License, version 3,
+     *    as published by the Free Software Foundation.
+     *
+     *    This program is distributed in the hope that it will be useful,
+     *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+     *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     *    GNU Affero General Public License for more details.
+     *
+     *    You should have received a copy of the GNU Affero General Public License
+     *    along with this program.  If not, see .
+     *
+     *    You can download the source here: https://github.com/spark/spark-server
+     *
+     * 
+     *
+     */
 
 exports.default = Logger;
\ No newline at end of file
diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js
index d124a01d..c69114e6 100644
--- a/dist/managers/FirmwareCompilationManager.js
+++ b/dist/managers/FirmwareCompilationManager.js
@@ -36,6 +36,10 @@ var _fs = require('fs');
 
 var _fs2 = _interopRequireDefault(_fs);
 
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 var _path = require('path');
 
 var _path2 = _interopRequireDefault(_path);
@@ -158,7 +162,7 @@ FirmwareCompilationManager.compileSource = function () {
             errors = [];
 
             makeProcess.stderr.on('data', function (data) {
-              console.log('' + data);
+              _logger2.default.error('' + data);
               errors.push('' + data);
             });
 
diff --git a/dist/oauthClients.json b/dist/oauthClients.json
new file mode 100644
index 00000000..048381d1
--- /dev/null
+++ b/dist/oauthClients.json
@@ -0,0 +1,10 @@
+[{
+  "clientId": "CLI2",
+  "clientSecret": "client_secret_here",
+  "grants": ["password"]
+},
+{
+  "clientId": "particle-collider",
+  "clientSecret": "particle-collider",
+  "grants": ["password"]
+}]
diff --git a/dist/settings.js b/dist/settings.js
index 86a6025b..c5b89de0 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -23,8 +23,8 @@ exports.default = {
   WEBHOOKS_DIRECTORY: _path2.default.join(__dirname, '../data/webhooks'),
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
-  CRYPTO_SALT: 'aes-128-cbc',
-  LOG_REQUESTS: true,
+  CRYPTO_ALGORITHM: 'aes-128-cbc',
+  LOG_REQUESTS: false,
   LOGIN_ROUTE: '/oauth/token',
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..72d802a0
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,4726 @@
+{
+  "name": "spark-server",
+  "version": "0.1.1",
+  "lockfileVersion": 1,
+  "dependencies": {
+    "abbrev": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
+      "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8="
+    },
+    "accepts": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
+      "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo="
+    },
+    "acorn": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
+      "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+      "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
+      "dev": true,
+      "dependencies": {
+        "acorn": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+          "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
+          "dev": true
+        }
+      }
+    },
+    "agent-base": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz",
+      "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=",
+      "dependencies": {
+        "semver": {
+          "version": "5.0.3",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz",
+          "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no="
+        }
+      }
+    },
+    "ajv": {
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+      "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY="
+    },
+    "ajv-keywords": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz",
+      "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
+      "dev": true
+    },
+    "ansi-align": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz",
+      "integrity": "sha1-LwwWWIKXOa3V67FeawxuNCPwFro=",
+      "dev": true
+    },
+    "ansi-escapes": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
+      "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+    },
+    "ansi-styles": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+    },
+    "any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
+    },
+    "anymatch": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
+      "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc="
+    },
+    "append-field": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz",
+      "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo="
+    },
+    "argparse": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
+      "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
+      "dev": true
+    },
+    "arr-diff": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+      "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8="
+    },
+    "arr-exclude": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/arr-exclude/-/arr-exclude-1.0.0.tgz",
+      "integrity": "sha1-38fC5VKicHI8zaBM8xKMjL/lxjE=",
+      "dev": true
+    },
+    "arr-flatten": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz",
+      "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E="
+    },
+    "array-differ": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
+      "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=",
+      "dev": true
+    },
+    "array-find-index": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+      "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+      "dev": true
+    },
+    "array-flatten": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
+      "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY="
+    },
+    "array-union": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "dev": true
+    },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+      "dev": true
+    },
+    "array-unique": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+      "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM="
+    },
+    "arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
+    },
+    "asn1": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+      "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
+    },
+    "assert-plus": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+      "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
+    },
+    "assertion-error": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
+      "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw="
+    },
+    "async": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+      "dev": true
+    },
+    "async-each": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
+      "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0="
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "auto-bind": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-0.1.0.tgz",
+      "integrity": "sha1-einvyMI4jT1XjgL8LfUxyB/8HuE=",
+      "dev": true
+    },
+    "ava": {
+      "version": "0.17.0",
+      "resolved": "https://registry.npmjs.org/ava/-/ava-0.17.0.tgz",
+      "integrity": "sha1-NZ4qiWFoAe8Dkpw88QqdT45FHQI=",
+      "dev": true,
+      "dependencies": {
+        "ms": {
+          "version": "0.7.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz",
+          "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=",
+          "dev": true
+        }
+      }
+    },
+    "ava-files": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/ava-files/-/ava-files-0.2.0.tgz",
+      "integrity": "sha1-x7i24uDOpjtXpuJ+DbFFx8Gc/iA=",
+      "dev": true
+    },
+    "ava-init": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/ava-init/-/ava-init-0.1.6.tgz",
+      "integrity": "sha1-7xntCyS2vzWdrW+63xoF2DY5XJE=",
+      "dev": true
+    },
+    "aws-sign2": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+      "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
+    },
+    "aws4": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+      "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
+    },
+    "babel-cli": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.24.1.tgz",
+      "integrity": "sha1-IHzXBbumFImy6kG1MSNBz2rKIoM="
+    },
+    "babel-code-frame": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
+      "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ="
+    },
+    "babel-core": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz",
+      "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM="
+    },
+    "babel-eslint": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz",
+      "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=",
+      "dev": true
+    },
+    "babel-generator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz",
+      "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc="
+    },
+    "babel-helper-bindify-decorators": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
+      "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=",
+      "dev": true
+    },
+    "babel-helper-builder-binary-assignment-operator-visitor": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
+      "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=",
+      "dev": true
+    },
+    "babel-helper-call-delegate": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
+      "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
+      "dev": true
+    },
+    "babel-helper-define-map": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz",
+      "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=",
+      "dev": true
+    },
+    "babel-helper-explode-assignable-expression": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz",
+      "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=",
+      "dev": true
+    },
+    "babel-helper-explode-class": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz",
+      "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=",
+      "dev": true
+    },
+    "babel-helper-function-name": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
+      "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+      "dev": true
+    },
+    "babel-helper-get-function-arity": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
+      "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+      "dev": true
+    },
+    "babel-helper-hoist-variables": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
+      "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
+      "dev": true
+    },
+    "babel-helper-optimise-call-expression": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
+      "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
+      "dev": true
+    },
+    "babel-helper-regex": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz",
+      "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=",
+      "dev": true
+    },
+    "babel-helper-remap-async-to-generator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz",
+      "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=",
+      "dev": true
+    },
+    "babel-helper-replace-supers": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
+      "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
+      "dev": true
+    },
+    "babel-helpers": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
+      "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI="
+    },
+    "babel-loader": {
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-6.4.1.tgz",
+      "integrity": "sha1-CzQRLVsHSKjc2/Uaz2+b1C1QuMo="
+    },
+    "babel-messages": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+      "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4="
+    },
+    "babel-plugin-ava-throws-helper": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-ava-throws-helper/-/babel-plugin-ava-throws-helper-0.1.0.tgz",
+      "integrity": "sha1-lREHcIoSIIAmv4ykzvGKh7ybDP4=",
+      "dev": true
+    },
+    "babel-plugin-check-es2015-constants": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
+      "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
+      "dev": true
+    },
+    "babel-plugin-detective": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-detective/-/babel-plugin-detective-2.0.0.tgz",
+      "integrity": "sha1-bmQug8IqM1J5dU6+LXVNJjX0nxM=",
+      "dev": true
+    },
+    "babel-plugin-espower": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-2.3.2.tgz",
+      "integrity": "sha1-VRa4/NsmyfDh2BYHSfbkxl5xJx4=",
+      "dev": true
+    },
+    "babel-plugin-syntax-async-functions": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
+      "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=",
+      "dev": true
+    },
+    "babel-plugin-syntax-async-generators": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz",
+      "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=",
+      "dev": true
+    },
+    "babel-plugin-syntax-class-constructor-call": {
+      "version": "6.18.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz",
+      "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=",
+      "dev": true
+    },
+    "babel-plugin-syntax-class-properties": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
+      "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
+      "dev": true
+    },
+    "babel-plugin-syntax-decorators": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
+      "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
+      "dev": true
+    },
+    "babel-plugin-syntax-do-expressions": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz",
+      "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=",
+      "dev": true
+    },
+    "babel-plugin-syntax-dynamic-import": {
+      "version": "6.18.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
+      "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=",
+      "dev": true
+    },
+    "babel-plugin-syntax-exponentiation-operator": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
+      "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=",
+      "dev": true
+    },
+    "babel-plugin-syntax-export-extensions": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz",
+      "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=",
+      "dev": true
+    },
+    "babel-plugin-syntax-flow": {
+      "version": "6.18.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
+      "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=",
+      "dev": true
+    },
+    "babel-plugin-syntax-function-bind": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz",
+      "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=",
+      "dev": true
+    },
+    "babel-plugin-syntax-object-rest-spread": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
+      "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
+      "dev": true
+    },
+    "babel-plugin-syntax-trailing-function-commas": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz",
+      "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=",
+      "dev": true
+    },
+    "babel-plugin-transform-async-generator-functions": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz",
+      "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=",
+      "dev": true
+    },
+    "babel-plugin-transform-async-to-generator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz",
+      "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=",
+      "dev": true
+    },
+    "babel-plugin-transform-class-constructor-call": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz",
+      "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=",
+      "dev": true
+    },
+    "babel-plugin-transform-class-properties": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz",
+      "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=",
+      "dev": true
+    },
+    "babel-plugin-transform-decorators": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz",
+      "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=",
+      "dev": true
+    },
+    "babel-plugin-transform-decorators-legacy": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz",
+      "integrity": "sha1-dBtY9sW86eYCfgiC2cmU8E82aSU=",
+      "dev": true
+    },
+    "babel-plugin-transform-do-expressions": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz",
+      "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-arrow-functions": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
+      "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-block-scoped-functions": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
+      "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-block-scoping": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz",
+      "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-classes": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
+      "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-computed-properties": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
+      "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-destructuring": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
+      "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-duplicate-keys": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz",
+      "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-for-of": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
+      "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-function-name": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
+      "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-literals": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
+      "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-amd": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz",
+      "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-commonjs": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz",
+      "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-systemjs": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz",
+      "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-modules-umd": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz",
+      "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-object-super": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
+      "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-parameters": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
+      "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-shorthand-properties": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
+      "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-spread": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
+      "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-sticky-regex": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz",
+      "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-template-literals": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
+      "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-typeof-symbol": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz",
+      "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
+      "dev": true
+    },
+    "babel-plugin-transform-es2015-unicode-regex": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz",
+      "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
+      "dev": true
+    },
+    "babel-plugin-transform-exponentiation-operator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz",
+      "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=",
+      "dev": true
+    },
+    "babel-plugin-transform-export-extensions": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz",
+      "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=",
+      "dev": true
+    },
+    "babel-plugin-transform-flow-strip-types": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz",
+      "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=",
+      "dev": true
+    },
+    "babel-plugin-transform-function-bind": {
+      "version": "6.22.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz",
+      "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=",
+      "dev": true
+    },
+    "babel-plugin-transform-object-rest-spread": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz",
+      "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=",
+      "dev": true
+    },
+    "babel-plugin-transform-regenerator": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz",
+      "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=",
+      "dev": true
+    },
+    "babel-plugin-transform-runtime": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz",
+      "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
+      "dev": true
+    },
+    "babel-plugin-transform-strict-mode": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
+      "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
+      "dev": true
+    },
+    "babel-polyfill": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz",
+      "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0="
+    },
+    "babel-preset-es2015": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
+      "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=",
+      "dev": true
+    },
+    "babel-preset-es2015-node4": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-es2015-node4/-/babel-preset-es2015-node4-2.1.1.tgz",
+      "integrity": "sha1-4x8pCFm1hhnIz6JB0bC8kA+UHNs=",
+      "dev": true
+    },
+    "babel-preset-es2016": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz",
+      "integrity": "sha1-+QC/k+LrwNJ235uKtZck6/2Vn4s=",
+      "dev": true
+    },
+    "babel-preset-es2017": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-es2017/-/babel-preset-es2017-6.24.1.tgz",
+      "integrity": "sha1-WXvq37n38gi8/YoS6bKym4svFNE=",
+      "dev": true
+    },
+    "babel-preset-latest": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-latest/-/babel-preset-latest-6.24.1.tgz",
+      "integrity": "sha1-Z33gaRVKdIXC0lxXfAL2JLhbheg=",
+      "dev": true
+    },
+    "babel-preset-stage-0": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz",
+      "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=",
+      "dev": true
+    },
+    "babel-preset-stage-1": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz",
+      "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=",
+      "dev": true
+    },
+    "babel-preset-stage-2": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz",
+      "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=",
+      "dev": true
+    },
+    "babel-preset-stage-3": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz",
+      "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=",
+      "dev": true
+    },
+    "babel-register": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz",
+      "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118="
+    },
+    "babel-runtime": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
+      "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs="
+    },
+    "babel-template": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz",
+      "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM="
+    },
+    "babel-traverse": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz",
+      "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU="
+    },
+    "babel-types": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz",
+      "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU="
+    },
+    "babylon": {
+      "version": "6.17.2",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz",
+      "integrity": "sha1-IB0l71+JLEG65JSIsI2w3Udun1w="
+    },
+    "balanced-match": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+      "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+    },
+    "basic-auth": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
+      "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ="
+    },
+    "basic-auth-parser": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz",
+      "integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE="
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+      "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+      "optional": true
+    },
+    "big.js": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz",
+      "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg="
+    },
+    "binary-extensions": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz",
+      "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q="
+    },
+    "binary-version-reader": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/binary-version-reader/-/binary-version-reader-0.5.1.tgz",
+      "integrity": "sha1-1LC9MGFlJlsPCcjHHiBd0K4ffHs="
+    },
+    "bindings": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
+      "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
+    },
+    "bluebird": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
+      "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw="
+    },
+    "body-parser": {
+      "version": "1.17.2",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz",
+      "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=",
+      "dependencies": {
+        "debug": {
+          "version": "2.6.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+        }
+      }
+    },
+    "boom": {
+      "version": "2.10.1",
+      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+      "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
+    },
+    "boxen": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.6.0.tgz",
+      "integrity": "sha1-g2TUJIrDT/DvGy8r9JpsYM4NgbY=",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
+      "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k="
+    },
+    "braces": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+      "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc="
+    },
+    "bson": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz",
+      "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw="
+    },
+    "buf-compare": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz",
+      "integrity": "sha1-/vKNqLgROgoNtEMLC2Rntpcws0o=",
+      "dev": true
+    },
+    "buffer-crc32": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+      "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
+    },
+    "buffer-shims": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
+      "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
+    },
+    "builtin-modules": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+      "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+      "dev": true
+    },
+    "busboy": {
+      "version": "0.2.14",
+      "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
+      "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        }
+      }
+    },
+    "bytes": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz",
+      "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk="
+    },
+    "caching-transform": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-1.0.1.tgz",
+      "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=",
+      "dev": true
+    },
+    "call-matcher": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz",
+      "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=",
+      "dev": true
+    },
+    "call-signature": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/call-signature/-/call-signature-0.0.2.tgz",
+      "integrity": "sha1-qEq8glpV70yysCi9dOIFpluaSZY=",
+      "dev": true
+    },
+    "caller-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
+      "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
+      "dev": true
+    },
+    "callsites": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
+      "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
+      "dev": true
+    },
+    "camel-case": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-1.2.2.tgz",
+      "integrity": "sha1-Gsp8TRlTWaLOmVV5NDPG5VQlEfI="
+    },
+    "camelcase": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+      "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+      "dev": true
+    },
+    "camelcase-keys": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+      "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+      "dev": true
+    },
+    "capture-stack-trace": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
+      "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=",
+      "dev": true
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
+    "chai": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz",
+      "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc="
+    },
+    "chalk": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg="
+    },
+    "chokidar": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
+      "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg="
+    },
+    "ci-info": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.0.0.tgz",
+      "integrity": "sha1-3FKF8rTiUYIWg2gcOBwziPRuxTQ=",
+      "dev": true
+    },
+    "circular-json": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz",
+      "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=",
+      "dev": true
+    },
+    "clean-yaml-object": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz",
+      "integrity": "sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g=",
+      "dev": true
+    },
+    "cli-boxes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
+      "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
+      "dev": true
+    },
+    "cli-cursor": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
+      "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+      "dev": true
+    },
+    "cli-spinners": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz",
+      "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=",
+      "dev": true
+    },
+    "cli-truncate": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
+      "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
+      "dev": true
+    },
+    "cli-width": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz",
+      "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=",
+      "dev": true
+    },
+    "clone": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
+      "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk="
+    },
+    "co": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
+    },
+    "co-bluebird": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/co-bluebird/-/co-bluebird-1.1.0.tgz",
+      "integrity": "sha1-yLnzqTIKftMJh9zKGlw8/1llXHw=",
+      "dependencies": {
+        "bluebird": {
+          "version": "2.11.0",
+          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+          "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
+        }
+      }
+    },
+    "co-use": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/co-use/-/co-use-1.1.0.tgz",
+      "integrity": "sha1-xrs83xDLc17Kqdru2kbXJclKTmI="
+    },
+    "co-with-promise": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/co-with-promise/-/co-with-promise-4.6.0.tgz",
+      "integrity": "sha1-QT59tvWJOmC5Qs9JLEvsk9tBWrc=",
+      "dev": true,
+      "dependencies": {
+        "pinkie": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz",
+          "integrity": "sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=",
+          "dev": true
+        },
+        "pinkie-promise": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
+          "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=",
+          "dev": true
+        }
+      }
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+      "dev": true
+    },
+    "combined-stream": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+      "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
+    },
+    "commander": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+      "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q="
+    },
+    "common-path-prefix": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-1.0.0.tgz",
+      "integrity": "sha1-zVL28HEuC6q5fW+XModPIvR3UsA=",
+      "dev": true
+    },
+    "commondir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
+    },
+    "component-emitter": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+      "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "concat-stream": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+      "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc="
+    },
+    "configstore": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/configstore/-/configstore-2.1.0.tgz",
+      "integrity": "sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=",
+      "dev": true,
+      "dependencies": {
+        "uuid": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+          "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
+          "dev": true
+        }
+      }
+    },
+    "constitute": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/constitute/-/constitute-1.6.2.tgz",
+      "integrity": "sha1-up8rM/FRCsTRrD3cjUrnzlooqjI="
+    },
+    "contains-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+      "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+      "dev": true
+    },
+    "content-disposition": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+      "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
+    },
+    "content-type": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
+      "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0="
+    },
+    "convert-source-map": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz",
+      "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU="
+    },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "cookiejar": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz",
+      "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=",
+      "dev": true
+    },
+    "core-assert": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz",
+      "integrity": "sha1-+F4s+b/tKPdzzIs/pcW2m9wC/j8=",
+      "dev": true
+    },
+    "core-js": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
+      "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4="
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "create-error-class": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
+      "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
+      "dev": true
+    },
+    "cross-spawn": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
+      "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
+      "dev": true
+    },
+    "cryptiles": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+      "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g="
+    },
+    "currently-unhandled": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+      "dev": true
+    },
+    "d": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
+      "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8="
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+        }
+      }
+    },
+    "date-time": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/date-time/-/date-time-0.1.1.tgz",
+      "integrity": "sha1-7S9tk9l5DOL9ZtW1/z7dW7y/Owc=",
+      "dev": true
+    },
+    "debug": {
+      "version": "2.6.8",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+      "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true
+    },
+    "deep-eql": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
+      "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=",
+      "dependencies": {
+        "type-detect": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz",
+          "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI="
+        }
+      }
+    },
+    "deep-equal": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+      "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+      "dev": true
+    },
+    "deep-extend": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
+      "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=",
+      "dev": true
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+      "dev": true
+    },
+    "del": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+      "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
+      "dev": true,
+      "dependencies": {
+        "globby": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+          "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
+          "dev": true
+        }
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
+    "depd": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
+      "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "detect-indent": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+      "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg="
+    },
+    "dicer": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
+      "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        }
+      }
+    },
+    "doctrine": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
+      "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
+      "dev": true
+    },
+    "dot-prop": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz",
+      "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
+      "dev": true
+    },
+    "duplexer": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
+      "dev": true
+    },
+    "duplexer2": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+      "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
+      "dev": true
+    },
+    "duplexify": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz",
+      "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=",
+      "dev": true
+    },
+    "eastasianwidth": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.1.1.tgz",
+      "integrity": "sha1-RNZW3p2kFWlEZzNTZfsxR7hXK3w=",
+      "dev": true
+    },
+    "ecc-jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+      "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+      "optional": true
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "emojis-list": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+      "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
+    },
+    "empower-core": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz",
+      "integrity": "sha1-Wt71ZgiOMfuoC6CjbfR9cJQWkUQ=",
+      "dev": true
+    },
+    "encodeurl": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
+      "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
+    },
+    "end-of-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+      "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=",
+      "dev": true,
+      "dependencies": {
+        "once": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+          "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
+          "dev": true
+        }
+      }
+    },
+    "error-ex": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
+      "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
+      "dev": true
+    },
+    "es5-ext": {
+      "version": "0.10.22",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.22.tgz",
+      "integrity": "sha512-YXTXSlZkJsVwMEVljp1Bh5P9+Raa3524OMl9kywGMp1aazKTCnAqORRL/8dkuqNHk+LRYe0LezuS8PlUt3+mOw=="
+    },
+    "es6-iterator": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
+      "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI="
+    },
+    "es6-map": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
+      "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
+      "dev": true
+    },
+    "es6-promise": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
+      "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q="
+    },
+    "es6-set": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
+      "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
+      "dev": true
+    },
+    "es6-symbol": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
+      "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc="
+    },
+    "es6-weak-map": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
+      "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+    },
+    "escope": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
+      "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
+      "dev": true
+    },
+    "eslint": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
+      "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=",
+      "dev": true,
+      "dependencies": {
+        "strip-bom": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+          "dev": true
+        },
+        "user-home": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
+          "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-config-airbnb-base": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-10.0.1.tgz",
+      "integrity": "sha1-8X1OUpksHUXRt3E++81ezQ5+BQY=",
+      "dev": true
+    },
+    "eslint-import-resolver-node": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz",
+      "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=",
+      "dev": true
+    },
+    "eslint-module-utils": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.0.0.tgz",
+      "integrity": "sha1-pvjCHZATWHWc3DXbrBmCrh7li84=",
+      "dev": true,
+      "dependencies": {
+        "debug": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+          "dev": true
+        },
+        "ms": {
+          "version": "0.7.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-flowtype": {
+      "version": "2.34.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.34.0.tgz",
+      "integrity": "sha1-uYdfMUZS5QgWI8nSsYo0a7t1nAk=",
+      "dev": true
+    },
+    "eslint-plugin-import": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.3.0.tgz",
+      "integrity": "sha1-N8gB4K2g4pbL3yDD85OstbUq82s=",
+      "dev": true,
+      "dependencies": {
+        "doctrine": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+          "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+          "dev": true
+        },
+        "find-up": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+          "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+          "dev": true
+        },
+        "load-json-file": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+          "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+          "dev": true
+        },
+        "path-type": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+          "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+          "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+          "dev": true
+        },
+        "read-pkg-up": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+          "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+          "dev": true
+        },
+        "strip-bom": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-sorting": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-sorting/-/eslint-plugin-sorting-0.3.0.tgz",
+      "integrity": "sha1-rPQ8qI80tCuj0Pa/DgxQSFog9g0=",
+      "dev": true
+    },
+    "espower-location-detector": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz",
+      "integrity": "sha1-oXt+zFnTDheeK+9z+0E3cEyzMbU=",
+      "dev": true
+    },
+    "espree": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
+      "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
+      "dev": true
+    },
+    "esprima": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+      "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+      "dev": true
+    },
+    "espurify": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.7.0.tgz",
+      "integrity": "sha1-HFz2y8zDLm9jk4C9T5kfq5up0iY=",
+      "dev": true
+    },
+    "esquery": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
+      "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
+      "dev": true
+    },
+    "esrecurse": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz",
+      "integrity": "sha1-RxO2U2rffyrE8yfVWed1a/9kgiA=",
+      "dev": true,
+      "dependencies": {
+        "estraverse": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz",
+          "integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=",
+          "dev": true
+        }
+      }
+    },
+    "estraverse": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
+    },
+    "etag": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz",
+      "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE="
+    },
+    "event-emitter": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk="
+    },
+    "event-stream": {
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
+      "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
+      "dev": true
+    },
+    "exit-hook": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
+      "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
+      "dev": true
+    },
+    "expand-brackets": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+      "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s="
+    },
+    "expand-range": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
+      "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc="
+    },
+    "express": {
+      "version": "4.15.3",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz",
+      "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=",
+      "dependencies": {
+        "array-flatten": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+          "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+        },
+        "debug": {
+          "version": "2.6.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+        }
+      }
+    },
+    "express-oauth-server": {
+      "version": "2.0.0-b1",
+      "resolved": "https://registry.npmjs.org/express-oauth-server/-/express-oauth-server-2.0.0-b1.tgz",
+      "integrity": "sha1-Gj3/oy8b/OBKiKSTIOL+ixP3cZI="
+    },
+    "extend": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+      "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
+    },
+    "extglob": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+      "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE="
+    },
+    "extsprintf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+      "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "dev": true
+    },
+    "figures": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+      "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+      "dev": true
+    },
+    "file-entry-cache": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
+      "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
+      "dev": true
+    },
+    "filename-regex": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
+      "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY="
+    },
+    "fill-range": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
+      "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM="
+    },
+    "filled-array": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/filled-array/-/filled-array-1.1.0.tgz",
+      "integrity": "sha1-w8T2xmO5I0WamqKZEtLQMfFQf4Q=",
+      "dev": true
+    },
+    "finalhandler": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz",
+      "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=",
+      "dependencies": {
+        "debug": {
+          "version": "2.6.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+        }
+      }
+    },
+    "find-cache-dir": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
+      "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk="
+    },
+    "find-up": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+      "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8="
+    },
+    "flat-cache": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz",
+      "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=",
+      "dev": true
+    },
+    "flow-bin": {
+      "version": "0.37.4",
+      "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.37.4.tgz",
+      "integrity": "sha1-PY2i73RugOcw0WbgkED0GYlpt2s=",
+      "dev": true
+    },
+    "fn-name": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz",
+      "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=",
+      "dev": true
+    },
+    "follow-redirects": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz",
+      "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk="
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
+    },
+    "for-own": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+      "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4="
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+      "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE="
+    },
+    "formatio": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz",
+      "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=",
+      "dev": true
+    },
+    "formidable": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz",
+      "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=",
+      "dev": true
+    },
+    "forwarded": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz",
+      "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M="
+    },
+    "fresh": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz",
+      "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44="
+    },
+    "from": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
+      "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
+      "dev": true
+    },
+    "fs-readdir-recursive": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz",
+      "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA="
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "fsevents": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz",
+      "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=",
+      "optional": true,
+      "dependencies": {
+        "abbrev": {
+          "version": "1.1.0",
+          "bundled": true,
+          "optional": true
+        },
+        "ansi-regex": {
+          "version": "2.1.1",
+          "bundled": true
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "bundled": true,
+          "optional": true
+        },
+        "aproba": {
+          "version": "1.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "are-we-there-yet": {
+          "version": "1.1.2",
+          "bundled": true,
+          "optional": true
+        },
+        "asn1": {
+          "version": "0.2.3",
+          "bundled": true,
+          "optional": true
+        },
+        "assert-plus": {
+          "version": "0.2.0",
+          "bundled": true,
+          "optional": true
+        },
+        "asynckit": {
+          "version": "0.4.0",
+          "bundled": true,
+          "optional": true
+        },
+        "aws-sign2": {
+          "version": "0.6.0",
+          "bundled": true,
+          "optional": true
+        },
+        "aws4": {
+          "version": "1.6.0",
+          "bundled": true,
+          "optional": true
+        },
+        "balanced-match": {
+          "version": "0.4.2",
+          "bundled": true
+        },
+        "bcrypt-pbkdf": {
+          "version": "1.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "block-stream": {
+          "version": "0.0.9",
+          "bundled": true
+        },
+        "boom": {
+          "version": "2.10.1",
+          "bundled": true
+        },
+        "brace-expansion": {
+          "version": "1.1.6",
+          "bundled": true
+        },
+        "buffer-shims": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "caseless": {
+          "version": "0.11.0",
+          "bundled": true,
+          "optional": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "bundled": true,
+          "optional": true
+        },
+        "code-point-at": {
+          "version": "1.1.0",
+          "bundled": true
+        },
+        "combined-stream": {
+          "version": "1.0.5",
+          "bundled": true
+        },
+        "commander": {
+          "version": "2.9.0",
+          "bundled": true,
+          "optional": true
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "bundled": true
+        },
+        "console-control-strings": {
+          "version": "1.1.0",
+          "bundled": true
+        },
+        "core-util-is": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "cryptiles": {
+          "version": "2.0.5",
+          "bundled": true,
+          "optional": true
+        },
+        "dashdash": {
+          "version": "1.14.1",
+          "bundled": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            }
+          }
+        },
+        "debug": {
+          "version": "2.2.0",
+          "bundled": true,
+          "optional": true
+        },
+        "deep-extend": {
+          "version": "0.4.1",
+          "bundled": true,
+          "optional": true
+        },
+        "delayed-stream": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "delegates": {
+          "version": "1.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "ecc-jsbn": {
+          "version": "0.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "bundled": true,
+          "optional": true
+        },
+        "extend": {
+          "version": "3.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "extsprintf": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "forever-agent": {
+          "version": "0.6.1",
+          "bundled": true,
+          "optional": true
+        },
+        "form-data": {
+          "version": "2.1.2",
+          "bundled": true,
+          "optional": true
+        },
+        "fs.realpath": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "fstream": {
+          "version": "1.0.10",
+          "bundled": true
+        },
+        "fstream-ignore": {
+          "version": "1.0.5",
+          "bundled": true,
+          "optional": true
+        },
+        "gauge": {
+          "version": "2.7.3",
+          "bundled": true,
+          "optional": true
+        },
+        "generate-function": {
+          "version": "2.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "generate-object-property": {
+          "version": "1.2.0",
+          "bundled": true,
+          "optional": true
+        },
+        "getpass": {
+          "version": "0.1.6",
+          "bundled": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            }
+          }
+        },
+        "glob": {
+          "version": "7.1.1",
+          "bundled": true
+        },
+        "graceful-fs": {
+          "version": "4.1.11",
+          "bundled": true
+        },
+        "graceful-readlink": {
+          "version": "1.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "har-validator": {
+          "version": "2.0.6",
+          "bundled": true,
+          "optional": true
+        },
+        "has-ansi": {
+          "version": "2.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "has-unicode": {
+          "version": "2.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "hawk": {
+          "version": "3.1.3",
+          "bundled": true,
+          "optional": true
+        },
+        "hoek": {
+          "version": "2.16.3",
+          "bundled": true
+        },
+        "http-signature": {
+          "version": "1.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "inflight": {
+          "version": "1.0.6",
+          "bundled": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "bundled": true
+        },
+        "ini": {
+          "version": "1.3.4",
+          "bundled": true,
+          "optional": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "is-my-json-valid": {
+          "version": "2.15.0",
+          "bundled": true,
+          "optional": true
+        },
+        "is-property": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "is-typedarray": {
+          "version": "1.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "bundled": true
+        },
+        "isstream": {
+          "version": "0.1.2",
+          "bundled": true,
+          "optional": true
+        },
+        "jodid25519": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "jsbn": {
+          "version": "0.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "json-schema": {
+          "version": "0.2.3",
+          "bundled": true,
+          "optional": true
+        },
+        "json-stringify-safe": {
+          "version": "5.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "jsonpointer": {
+          "version": "4.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "jsprim": {
+          "version": "1.3.1",
+          "bundled": true,
+          "optional": true
+        },
+        "mime-db": {
+          "version": "1.26.0",
+          "bundled": true
+        },
+        "mime-types": {
+          "version": "2.1.14",
+          "bundled": true
+        },
+        "minimatch": {
+          "version": "3.0.3",
+          "bundled": true
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "bundled": true
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "bundled": true
+        },
+        "ms": {
+          "version": "0.7.1",
+          "bundled": true,
+          "optional": true
+        },
+        "node-pre-gyp": {
+          "version": "0.6.33",
+          "bundled": true,
+          "optional": true
+        },
+        "nopt": {
+          "version": "3.0.6",
+          "bundled": true,
+          "optional": true
+        },
+        "npmlog": {
+          "version": "4.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "number-is-nan": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "oauth-sign": {
+          "version": "0.8.2",
+          "bundled": true,
+          "optional": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "bundled": true,
+          "optional": true
+        },
+        "once": {
+          "version": "1.4.0",
+          "bundled": true
+        },
+        "path-is-absolute": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "pinkie": {
+          "version": "2.0.4",
+          "bundled": true,
+          "optional": true
+        },
+        "pinkie-promise": {
+          "version": "2.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "process-nextick-args": {
+          "version": "1.0.7",
+          "bundled": true
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "bundled": true,
+          "optional": true
+        },
+        "qs": {
+          "version": "6.3.1",
+          "bundled": true,
+          "optional": true
+        },
+        "rc": {
+          "version": "1.1.7",
+          "bundled": true,
+          "optional": true,
+          "dependencies": {
+            "minimist": {
+              "version": "1.2.0",
+              "bundled": true,
+              "optional": true
+            }
+          }
+        },
+        "readable-stream": {
+          "version": "2.2.2",
+          "bundled": true,
+          "optional": true
+        },
+        "request": {
+          "version": "2.79.0",
+          "bundled": true,
+          "optional": true
+        },
+        "rimraf": {
+          "version": "2.5.4",
+          "bundled": true
+        },
+        "semver": {
+          "version": "5.3.0",
+          "bundled": true,
+          "optional": true
+        },
+        "set-blocking": {
+          "version": "2.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "signal-exit": {
+          "version": "3.0.2",
+          "bundled": true,
+          "optional": true
+        },
+        "sntp": {
+          "version": "1.0.9",
+          "bundled": true,
+          "optional": true
+        },
+        "sshpk": {
+          "version": "1.10.2",
+          "bundled": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            }
+          }
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "bundled": true
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "stringstream": {
+          "version": "0.0.5",
+          "bundled": true,
+          "optional": true
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "bundled": true
+        },
+        "strip-json-comments": {
+          "version": "2.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "bundled": true,
+          "optional": true
+        },
+        "tar": {
+          "version": "2.2.1",
+          "bundled": true
+        },
+        "tar-pack": {
+          "version": "3.3.0",
+          "bundled": true,
+          "optional": true,
+          "dependencies": {
+            "once": {
+              "version": "1.3.3",
+              "bundled": true,
+              "optional": true
+            },
+            "readable-stream": {
+              "version": "2.1.5",
+              "bundled": true,
+              "optional": true
+            }
+          }
+        },
+        "tough-cookie": {
+          "version": "2.3.2",
+          "bundled": true,
+          "optional": true
+        },
+        "tunnel-agent": {
+          "version": "0.4.3",
+          "bundled": true,
+          "optional": true
+        },
+        "tweetnacl": {
+          "version": "0.14.5",
+          "bundled": true,
+          "optional": true
+        },
+        "uid-number": {
+          "version": "0.0.6",
+          "bundled": true,
+          "optional": true
+        },
+        "util-deprecate": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "uuid": {
+          "version": "3.0.1",
+          "bundled": true,
+          "optional": true
+        },
+        "verror": {
+          "version": "1.3.6",
+          "bundled": true,
+          "optional": true
+        },
+        "wide-align": {
+          "version": "1.1.0",
+          "bundled": true,
+          "optional": true
+        },
+        "wrappy": {
+          "version": "1.0.2",
+          "bundled": true
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "bundled": true,
+          "optional": true
+        }
+      }
+    },
+    "function-bind": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz",
+      "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=",
+      "dev": true
+    },
+    "generate-function": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+      "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
+      "dev": true
+    },
+    "generate-object-property": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+      "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+      "dev": true
+    },
+    "get-port": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/get-port/-/get-port-2.1.0.tgz",
+      "integrity": "sha1-h4P53OvR7qSVozThpqJR54iHqxo=",
+      "dev": true
+    },
+    "get-stdin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+      "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+      "dev": true
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+        }
+      }
+    },
+    "github": {
+      "version": "8.2.1",
+      "resolved": "https://registry.npmjs.org/github/-/github-8.2.1.tgz",
+      "integrity": "sha1-YWsiEfvNHMhjFmmu1nZT5i61OBY="
+    },
+    "glob": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ=="
+    },
+    "glob-base": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+      "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q="
+    },
+    "glob-parent": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+      "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg="
+    },
+    "globals": {
+      "version": "9.17.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-9.17.0.tgz",
+      "integrity": "sha1-DAymltm5u2lNLlRwvTd3fKrVAoY="
+    },
+    "globby": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+      "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+      "dev": true
+    },
+    "got": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
+      "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
+      "dev": true
+    },
+    "graceful-fs": {
+      "version": "4.1.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+      "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+    },
+    "graceful-readlink": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+      "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
+    },
+    "h5.buffers": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/h5.buffers/-/h5.buffers-0.1.1.tgz",
+      "integrity": "sha1-JEh3MFMOSwDkm1rxcEldrPpAEb4="
+    },
+    "h5.coap": {
+      "version": "git+https://github.com/morkai/h5.coap.git#51c3b2a4cb1af7f43d20e25a9d8658fed9169b9c"
+    },
+    "har-schema": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+      "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4="
+    },
+    "har-validator": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+      "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio="
+    },
+    "has": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
+      "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
+      "dev": true
+    },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE="
+    },
+    "has-color": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz",
+      "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=",
+      "dev": true
+    },
+    "has-flag": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+      "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+      "dev": true
+    },
+    "hawk": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+      "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ="
+    },
+    "hoek": {
+      "version": "2.16.3",
+      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+      "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+    },
+    "hogan.js": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
+      "integrity": "sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=",
+      "dependencies": {
+        "mkdirp": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
+          "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4="
+        }
+      }
+    },
+    "home-or-tmp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
+      "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg="
+    },
+    "hosted-git-info": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz",
+      "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=",
+      "dev": true
+    },
+    "http-errors": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz",
+      "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc="
+    },
+    "http-signature": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+      "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8="
+    },
+    "https-proxy-agent": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz",
+      "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY="
+    },
+    "iconv-lite": {
+      "version": "0.4.15",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz",
+      "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es="
+    },
+    "ignore": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz",
+      "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=",
+      "dev": true
+    },
+    "ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
+      "dev": true
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+      "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+      "dev": true
+    },
+    "infinity-agent": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz",
+      "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "ini": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+      "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+      "dev": true
+    },
+    "inquirer": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
+      "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=",
+      "dev": true
+    },
+    "interpret": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz",
+      "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=",
+      "dev": true
+    },
+    "invariant": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+      "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A="
+    },
+    "ipaddr.js": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz",
+      "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew="
+    },
+    "irregular-plurals": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.2.0.tgz",
+      "integrity": "sha1-OPKZg0uowAwwvpxVThNyaXUv86w=",
+      "dev": true
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-binary-path": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg="
+    },
+    "is-buffer": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
+      "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
+    },
+    "is-builtin-module": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+      "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+      "dev": true
+    },
+    "is-ci": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz",
+      "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=",
+      "dev": true
+    },
+    "is-dotfile": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+      "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE="
+    },
+    "is-equal-shallow": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+      "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ="
+    },
+    "is-error": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.1.tgz",
+      "integrity": "sha1-aEqW2EB2V3yY9M20DG0mpRI78Zw=",
+      "dev": true
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
+    },
+    "is-extglob": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+      "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA="
+    },
+    "is-finite": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko="
+    },
+    "is-fullwidth-code-point": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+      "dev": true
+    },
+    "is-generator": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz",
+      "integrity": "sha1-wUwhBX7TbjKNuANHlmxpP4hjifM="
+    },
+    "is-generator-fn": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz",
+      "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+      "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM="
+    },
+    "is-my-json-valid": {
+      "version": "2.16.0",
+      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
+      "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
+      "dev": true
+    },
+    "is-npm": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
+      "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=",
+      "dev": true
+    },
+    "is-number": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+      "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8="
+    },
+    "is-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+      "dev": true
+    },
+    "is-observable": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz",
+      "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=",
+      "dev": true
+    },
+    "is-path-cwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+      "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+      "dev": true
+    },
+    "is-path-in-cwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
+      "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
+      "dev": true
+    },
+    "is-path-inside": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
+      "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
+      "dev": true
+    },
+    "is-plain-obj": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+      "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+      "dev": true
+    },
+    "is-posix-bracket": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
+      "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q="
+    },
+    "is-primitive": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+      "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU="
+    },
+    "is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
+    },
+    "is-property": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+      "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+      "dev": true
+    },
+    "is-redirect": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+      "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
+      "dev": true
+    },
+    "is-resolvable": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+      "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
+      "dev": true
+    },
+    "is-retry-allowed": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
+      "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=",
+      "dev": true
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "is-url": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.2.tgz",
+      "integrity": "sha1-SYkFpZO/R8wtnn9zg3K792lsfyY=",
+      "dev": true
+    },
+    "is-utf8": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isobject": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+      "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk="
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
+    "jodid25519": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+      "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+      "optional": true
+    },
+    "js-tokens": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
+      "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc="
+    },
+    "js-yaml": {
+      "version": "3.8.4",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
+      "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
+      "dev": true
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "optional": true
+    },
+    "jsesc": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+      "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s="
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "json-stable-stringify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8="
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "json5": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+      "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
+    },
+    "jsonify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
+    },
+    "jsonpointer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+      "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+      "dev": true
+    },
+    "jsprim": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
+      "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+        }
+      }
+    },
+    "kind-of": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+      "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="
+    },
+    "last-line-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz",
+      "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=",
+      "dev": true
+    },
+    "latest-version": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz",
+      "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=",
+      "dev": true
+    },
+    "lazy-req": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/lazy-req/-/lazy-req-1.1.0.tgz",
+      "integrity": "sha1-va6+rTD42CQDnODOFJ1Nqge6H6w=",
+      "dev": true
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "dev": true
+    },
+    "load-json-file": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+      "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+      "dev": true
+    },
+    "loader-utils": {
+      "version": "0.2.17",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+      "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g="
+    },
+    "locate-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+      "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+      "dev": true,
+      "dependencies": {
+        "path-exists": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "dev": true
+        }
+      }
+    },
+    "lodash": {
+      "version": "4.17.4",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+    },
+    "lodash._baseassign": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
+      "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
+      "dev": true
+    },
+    "lodash._basecopy": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
+      "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
+      "dev": true
+    },
+    "lodash._bindcallback": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
+      "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=",
+      "dev": true
+    },
+    "lodash._createassigner": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz",
+      "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=",
+      "dev": true
+    },
+    "lodash._getnative": {
+      "version": "3.9.1",
+      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
+      "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
+      "dev": true
+    },
+    "lodash._isiterateecall": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
+      "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
+      "dev": true
+    },
+    "lodash.assign": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz",
+      "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=",
+      "dev": true
+    },
+    "lodash.cond": {
+      "version": "4.5.2",
+      "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
+      "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=",
+      "dev": true
+    },
+    "lodash.debounce": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+      "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+      "dev": true
+    },
+    "lodash.defaults": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz",
+      "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=",
+      "dev": true
+    },
+    "lodash.difference": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
+      "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=",
+      "dev": true
+    },
+    "lodash.flatten": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+      "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
+      "dev": true
+    },
+    "lodash.isarguments": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+      "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
+      "dev": true
+    },
+    "lodash.isarray": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
+      "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
+      "dev": true
+    },
+    "lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
+      "dev": true
+    },
+    "lodash.keys": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
+      "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
+      "dev": true
+    },
+    "lodash.restparam": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
+      "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
+      "dev": true
+    },
+    "lolex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz",
+      "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=",
+      "dev": true
+    },
+    "loose-envify": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+      "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg="
+    },
+    "loud-rejection": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+      "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+      "dev": true
+    },
+    "lower-case": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+      "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
+    },
+    "lowercase-keys": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+      "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=",
+      "dev": true
+    },
+    "lru-cache": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
+      "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=",
+      "dev": true
+    },
+    "lru-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
+      "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM="
+    },
+    "map-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+      "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+      "dev": true
+    },
+    "map-stream": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
+      "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
+      "dev": true
+    },
+    "matcher": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/matcher/-/matcher-0.1.2.tgz",
+      "integrity": "sha1-7yDL3mTCTFDMYa9bg+4LG4/wAQE=",
+      "dev": true
+    },
+    "max-timeout": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/max-timeout/-/max-timeout-1.0.0.tgz",
+      "integrity": "sha1-to9povmeC0dv1Msj4gWcp1BxXh8=",
+      "dev": true
+    },
+    "md5-hex": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz",
+      "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=",
+      "dev": true
+    },
+    "md5-o-matic": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz",
+      "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=",
+      "dev": true
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "memoizee": {
+      "version": "0.4.5",
+      "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.5.tgz",
+      "integrity": "sha1-G8PqHkvgVt1HXVIZede+PV5bIcg="
+    },
+    "meow": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+      "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "micromatch": {
+      "version": "2.3.11",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+      "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU="
+    },
+    "mime": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
+      "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM="
+    },
+    "mime-db": {
+      "version": "1.27.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
+      "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
+    },
+    "mime-types": {
+      "version": "2.1.15",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
+      "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0="
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA=="
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
+    },
+    "moment": {
+      "version": "2.18.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
+      "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
+    },
+    "mongodb": {
+      "version": "2.2.28",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.28.tgz",
+      "integrity": "sha1-2P9FdUNm4Dlz+iWb9PEUR4WNplc=",
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.2.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz",
+          "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE="
+        }
+      }
+    },
+    "mongodb-core": {
+      "version": "2.1.12",
+      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.12.tgz",
+      "integrity": "sha1-FTEZJRG8Fu8WCsauDMRndv/YRR0="
+    },
+    "moniker": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/moniker/-/moniker-0.1.2.tgz",
+      "integrity": "sha1-hy37pXXc6o+gSlE1sT1fJL7MyX4="
+    },
+    "morgan": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz",
+      "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc="
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "multer": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz",
+      "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=",
+      "dependencies": {
+        "object-assign": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+          "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
+        }
+      }
+    },
+    "multimatch": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",
+      "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=",
+      "dev": true
+    },
+    "mute-stream": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
+      "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
+      "dev": true
+    },
+    "nan": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
+      "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U="
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+      "dev": true
+    },
+    "negotiator": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+      "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+    },
+    "nested-error-stacks": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz",
+      "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=",
+      "dev": true
+    },
+    "netrc": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz",
+      "integrity": "sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ="
+    },
+    "next-tick": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+      "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
+    },
+    "node-status-codes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
+      "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=",
+      "dev": true
+    },
+    "nodemon": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.11.0.tgz",
+      "integrity": "sha1-ImxWK9KnsT09dRi0mtSCijYj0Gw=",
+      "dev": true,
+      "dependencies": {
+        "configstore": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz",
+          "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=",
+          "dev": true
+        },
+        "got": {
+          "version": "3.3.1",
+          "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz",
+          "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=",
+          "dev": true,
+          "dependencies": {
+            "object-assign": {
+              "version": "3.0.0",
+              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+              "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=",
+              "dev": true
+            }
+          }
+        },
+        "latest-version": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz",
+          "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=",
+          "dev": true
+        },
+        "package-json": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz",
+          "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=",
+          "dev": true
+        },
+        "repeating": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz",
+          "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=",
+          "dev": true
+        },
+        "timed-out": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz",
+          "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=",
+          "dev": true
+        },
+        "update-notifier": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz",
+          "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=",
+          "dev": true
+        },
+        "uuid": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
+          "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
+          "dev": true
+        }
+      }
+    },
+    "nopt": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4="
+    },
+    "normalize-package-data": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz",
+      "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=",
+      "dev": true
+    },
+    "normalize-path": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+      "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk="
+    },
+    "nullthrows": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.0.0.tgz",
+      "integrity": "sha1-NHFeU7nevgdQp3Iz/UlKWDWi2Zk="
+    },
+    "number-is-nan": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+    },
+    "oauth-sign": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+      "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
+    },
+    "oauth2-server": {
+      "version": "3.0.0-b3.1",
+      "resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.0-b3.1.tgz",
+      "integrity": "sha1-DmCDK7BBRr0xcOCB4J5kGy70Fj4=",
+      "dependencies": {
+        "bluebird": {
+          "version": "2.11.0",
+          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+          "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
+        },
+        "lodash": {
+          "version": "3.10.1",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
+        }
+      }
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "object.omit": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
+      "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo="
+    },
+    "observable-to-promise": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-0.4.0.tgz",
+      "integrity": "sha1-KK/nFkUwjy1B1x9HrT/s4aN35Ss=",
+      "dev": true
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc="
+    },
+    "on-headers": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
+      "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c="
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
+    },
+    "onetime": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+      "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+      "dev": true
+    },
+    "option-chain": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/option-chain/-/option-chain-0.1.1.tgz",
+      "integrity": "sha1-6bgR4AbxwPVIAvKClb/Ilw+Nz70=",
+      "dev": true
+    },
+    "optionator": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+      "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+      "dev": true
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+    },
+    "os-shim": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz",
+      "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=",
+      "dev": true
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+    },
+    "osenv": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
+      "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+      "dev": true
+    },
+    "output-file-sync": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz",
+      "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY="
+    },
+    "p-limit": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz",
+      "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=",
+      "dev": true
+    },
+    "p-locate": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+      "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+      "dev": true
+    },
+    "package-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-1.2.0.tgz",
+      "integrity": "sha1-AD5WzVe3NqbtYRTMK4FUJnJ3DkQ=",
+      "dev": true
+    },
+    "package-json": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
+      "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
+      "dev": true
+    },
+    "parse-glob": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+      "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw="
+    },
+    "parse-json": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+      "dev": true
+    },
+    "parse-ms": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz",
+      "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=",
+      "dev": true
+    },
+    "parseurl": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
+      "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY="
+    },
+    "path-exists": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+      "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s="
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
+      "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "path-type": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+      "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+      "dev": true
+    },
+    "pause-stream": {
+      "version": "0.0.11",
+      "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
+      "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
+      "dev": true
+    },
+    "performance-now": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+      "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU="
+    },
+    "pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+      "dev": true
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o="
+    },
+    "pkg-conf": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz",
+      "integrity": "sha1-N45W1v0T6Iv7b0ol33qD+qvduls=",
+      "dev": true
+    },
+    "pkg-dir": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
+      "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q="
+    },
+    "plur": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz",
+      "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=",
+      "dev": true
+    },
+    "pluralize": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz",
+      "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=",
+      "dev": true
+    },
+    "power-assert-context-formatter": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/power-assert-context-formatter/-/power-assert-context-formatter-1.1.1.tgz",
+      "integrity": "sha1-7bo1LT7YpgMRTWZyZazOYNaJzN8=",
+      "dev": true
+    },
+    "power-assert-context-traversal": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/power-assert-context-traversal/-/power-assert-context-traversal-1.1.1.tgz",
+      "integrity": "sha1-iMq8oNE7Y1nwfT0+ivppkmRXftk=",
+      "dev": true
+    },
+    "power-assert-renderer-assertion": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/power-assert-renderer-assertion/-/power-assert-renderer-assertion-1.1.1.tgz",
+      "integrity": "sha1-y/wOd+AIao+Wrz8djme57n4ozpg=",
+      "dev": true
+    },
+    "power-assert-renderer-base": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/power-assert-renderer-base/-/power-assert-renderer-base-1.1.1.tgz",
+      "integrity": "sha1-lqZQxv0F7hvB9mtUrWFELIs/Y+s=",
+      "dev": true
+    },
+    "power-assert-renderer-diagram": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/power-assert-renderer-diagram/-/power-assert-renderer-diagram-1.1.2.tgz",
+      "integrity": "sha1-ZV+PcRk1qbbVQbhjJ2VHF8Y3qYY=",
+      "dev": true
+    },
+    "power-assert-renderer-succinct": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/power-assert-renderer-succinct/-/power-assert-renderer-succinct-1.1.1.tgz",
+      "integrity": "sha1-wqRosjgiq9b4Diq6UyI0ewnfR24=",
+      "dev": true
+    },
+    "power-assert-util-string-width": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/power-assert-util-string-width/-/power-assert-util-string-width-1.1.1.tgz",
+      "integrity": "sha1-vmWet5N/3S5smncmjar2S9W3xZI=",
+      "dev": true
+    },
+    "pre-commit": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz",
+      "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "dev": true
+        }
+      }
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+      "dev": true
+    },
+    "prepend-http": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+      "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+      "dev": true
+    },
+    "preserve": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+      "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
+    },
+    "pretty-ms": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz",
+      "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=",
+      "dev": true,
+      "dependencies": {
+        "plur": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz",
+          "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=",
+          "dev": true
+        }
+      }
+    },
+    "private": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/private/-/private-0.1.7.tgz",
+      "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE="
+    },
+    "process-nextick-args": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+      "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+    },
+    "progress": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+      "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+      "dev": true
+    },
+    "promisify-any": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/promisify-any/-/promisify-any-2.0.1.tgz",
+      "integrity": "sha1-QD4AqIE/F1JCq1D+M6afjuzkcwU=",
+      "dependencies": {
+        "bluebird": {
+          "version": "2.11.0",
+          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+          "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
+        }
+      }
+    },
+    "proxy-addr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz",
+      "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM="
+    },
+    "ps-tree": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
+      "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=",
+      "dev": true
+    },
+    "pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
+    },
+    "punycode": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+      "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+    },
+    "qs": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+      "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM="
+    },
+    "randomatic": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz",
+      "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs="
+    },
+    "range-parser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+      "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
+    },
+    "raw-body": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz",
+      "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y="
+    },
+    "rc": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz",
+      "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
+    "read-all-stream": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz",
+      "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=",
+      "dev": true
+    },
+    "read-pkg": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+      "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+      "dev": true
+    },
+    "read-pkg-up": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+      "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+      "dev": true
+    },
+    "readable-stream": {
+      "version": "2.2.10",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.10.tgz",
+      "integrity": "sha512-HQEnnoV404e0EtwB9yNiuk2tJ+egeVC8Y9QBAxzDg8DBJt4BzRp+yQuIb/t3FIWkSTmIi+sgx7yVv/ZM0GNoqw=="
+    },
+    "readdirp": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
+      "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg="
+    },
+    "readline2": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
+      "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
+      "dev": true
+    },
+    "rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+      "dev": true
+    },
+    "redent": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+      "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+      "dev": true
+    },
+    "regenerate": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz",
+      "integrity": "sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA=",
+      "dev": true
+    },
+    "regenerator-runtime": {
+      "version": "0.10.5",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+      "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
+    },
+    "regenerator-transform": {
+      "version": "0.9.11",
+      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz",
+      "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=",
+      "dev": true
+    },
+    "regex-cache": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz",
+      "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU="
+    },
+    "regexpu-core": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
+      "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=",
+      "dev": true
+    },
+    "registry-auth-token": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz",
+      "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=",
+      "dev": true
+    },
+    "registry-url": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+      "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+      "dev": true
+    },
+    "regjsgen": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+      "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+      "dev": true
+    },
+    "regjsparser": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+      "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+      "dev": true,
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        }
+      }
+    },
+    "remove-trailing-separator": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz",
+      "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ="
+    },
+    "repeat-element": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
+      "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo="
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+    },
+    "repeating": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo="
+    },
+    "request": {
+      "version": "2.81.0",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+      "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA="
+    },
+    "require_optional": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.0.tgz",
+      "integrity": "sha1-UqhhN6hJco62ClVTNhf4+RT1mr8="
+    },
+    "require-precompiled": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/require-precompiled/-/require-precompiled-0.1.0.tgz",
+      "integrity": "sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo=",
+      "dev": true
+    },
+    "require-uncached": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
+      "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
+      "dev": true,
+      "dependencies": {
+        "resolve-from": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
+          "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
+          "dev": true
+        }
+      }
+    },
+    "resolve": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
+      "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=",
+      "dev": true
+    },
+    "resolve-cwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-1.0.0.tgz",
+      "integrity": "sha1-Tq7qQe0EDRcCRX32SkKysH0kb58=",
+      "dev": true
+    },
+    "resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+    },
+    "restore-cursor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
+      "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
+      "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0="
+    },
+    "rmfr": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/rmfr/-/rmfr-1.0.3.tgz",
+      "integrity": "sha1-QrN4U6gnso7/k7AYc3R3GnbpkHk="
+    },
+    "run-async": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+      "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=",
+      "dev": true
+    },
+    "rx-lite": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
+      "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
+      "dev": true
+    },
+    "safe": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/safe/-/safe-0.3.9.tgz",
+      "integrity": "sha1-F4FZvuRXkawhYoruLau3SlLV0HI="
+    },
+    "safe-buffer": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.0.tgz",
+      "integrity": "sha512-aSLEDudu6OoRr/2rU609gRmnYboRLxgDG1z9o2Q0os7236FwvcqIOO8r8U5JUEwivZOhDaKlFO4SbPTJYyBEyQ=="
+    },
+    "samsam": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz",
+      "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=",
+      "dev": true
+    },
+    "semver": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+      "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+    },
+    "semver-diff": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+      "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
+      "dev": true
+    },
+    "send": {
+      "version": "0.15.3",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz",
+      "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=",
+      "dependencies": {
+        "debug": {
+          "version": "2.6.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+        }
+      }
+    },
+    "sentence-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-1.1.3.tgz",
+      "integrity": "sha1-gDSq/CFFdy06vhUJqkLJ4QQtwTk="
+    },
+    "serve-static": {
+      "version": "1.12.3",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz",
+      "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI="
+    },
+    "set-immediate-shim": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+      "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
+    },
+    "setprototypeof": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
+      "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "shelljs": {
+      "version": "0.7.7",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz",
+      "integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=",
+      "dev": true
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
+    },
+    "sinon": {
+      "version": "1.17.7",
+      "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz",
+      "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=",
+      "dev": true
+    },
+    "slash": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+      "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
+    },
+    "slice-ansi": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+      "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+      "dev": true
+    },
+    "slide": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
+      "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=",
+      "dev": true
+    },
+    "sntp": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+      "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg="
+    },
+    "sort-keys": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
+      "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
+      "dev": true
+    },
+    "source-map": {
+      "version": "0.5.6",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
+      "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI="
+    },
+    "source-map-support": {
+      "version": "0.4.15",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz",
+      "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
+    },
+    "spark-protocol": {
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#0d48e94bde43e642c31682fe47364f6ec265d530"
+    },
+    "spawn-sync": {
+      "version": "1.0.15",
+      "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
+      "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=",
+      "dev": true
+    },
+    "spdx-correct": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+      "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
+      "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=",
+      "dev": true
+    },
+    "spdx-license-ids": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
+      "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
+      "dev": true
+    },
+    "split": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
+      "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
+      "dev": true
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
+    "sshpk": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz",
+      "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=",
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+        }
+      }
+    },
+    "stack-utils": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-0.4.0.tgz",
+      "integrity": "sha1-lAy4L8z6hOj/Lz/fKT/ngBa+zNE=",
+      "dev": true
+    },
+    "standard-error": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/standard-error/-/standard-error-1.1.0.tgz",
+      "integrity": "sha1-I+UWj6HAggGJ5YEnAaeQWFENDTQ="
+    },
+    "standard-http-error": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/standard-http-error/-/standard-http-error-1.2.0.tgz",
+      "integrity": "sha1-ELvTHNhvAj0wE6WCCL4344UZ/ac="
+    },
+    "statuses": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
+      "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
+    },
+    "stream-combiner": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
+      "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
+      "dev": true
+    },
+    "stream-consume": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz",
+      "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8="
+    },
+    "stream-shift": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+      "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
+      "dev": true
+    },
+    "streamsearch": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
+      "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
+    },
+    "string_decoder": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz",
+      "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg="
+    },
+    "string-length": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
+      "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=",
+      "dev": true
+    },
+    "string-width": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+      "dev": true
+    },
+    "stringifier": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/stringifier/-/stringifier-1.3.0.tgz",
+      "integrity": "sha1-3vGDQvaTPbDy2/yaoCF1tEjBeVk=",
+      "dev": true
+    },
+    "stringstream": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+      "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8="
+    },
+    "strip-bom": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+      "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+      "dev": true
+    },
+    "strip-indent": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+      "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+      "dev": true
+    },
+    "strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+      "dev": true
+    },
+    "superagent": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz",
+      "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
+      "dev": true,
+      "dependencies": {
+        "form-data": {
+          "version": "1.0.0-rc4",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
+          "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=",
+          "dev": true
+        }
+      }
+    },
+    "supertest": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/supertest/-/supertest-2.0.1.tgz",
+      "integrity": "sha1-oFgIHXiPFRXUcA11Aogea3WeRM0=",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+    },
+    "symbol": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/symbol/-/symbol-0.2.3.tgz",
+      "integrity": "sha1-O5hzuKkB5Hxu/iFSajrDcu8ou8c=",
+      "dev": true
+    },
+    "symbol-observable": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz",
+      "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=",
+      "dev": true
+    },
+    "table": {
+      "version": "3.8.3",
+      "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
+      "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
+      "dev": true,
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz",
+          "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=",
+          "dev": true
+        }
+      }
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "dev": true
+    },
+    "the-argv": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/the-argv/-/the-argv-1.0.0.tgz",
+      "integrity": "sha1-AIRwUAVzDdhNt1UlPJMa45jblSI=",
+      "dev": true
+    },
+    "thenify": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
+      "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk="
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "through2": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+      "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+      "dev": true
+    },
+    "time-require": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/time-require/-/time-require-0.1.2.tgz",
+      "integrity": "sha1-+eEss3D8JgXhFARYK6VO9corLZg=",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz",
+          "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz",
+          "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=",
+          "dev": true
+        },
+        "parse-ms": {
+          "version": "0.1.2",
+          "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-0.1.2.tgz",
+          "integrity": "sha1-3T+iXtbC78e93hKtm0bBY6opIk4=",
+          "dev": true
+        },
+        "pretty-ms": {
+          "version": "0.2.2",
+          "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-0.2.2.tgz",
+          "integrity": "sha1-2oeaaC/zOjcBEEbxPWJ/Z8c7hPY=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz",
+          "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=",
+          "dev": true
+        }
+      }
+    },
+    "timed-out": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz",
+      "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=",
+      "dev": true
+    },
+    "timers-ext": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
+      "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ="
+    },
+    "tingodb": {
+      "version": "git+https://github.com/Brewskey/tingodb.git#4a743f88f422651a9338f8a06db61248ed4c8b49",
+      "dependencies": {
+        "lodash": {
+          "version": "4.11.2",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.11.2.tgz",
+          "integrity": "sha1-1rQzixEKWOIdrlzrz9u/0rxM2zs="
+        }
+      }
+    },
+    "to-fast-properties": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+      "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
+    },
+    "touch": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-1.0.0.tgz",
+      "integrity": "sha1-RJy+LbrlqMgDjjDXH6D/RklHxN4=",
+      "dev": true
+    },
+    "tough-cookie": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+      "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo="
+    },
+    "traverse": {
+      "version": "0.6.6",
+      "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
+      "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=",
+      "dev": true
+    },
+    "trim-newlines": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+      "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+      "dev": true
+    },
+    "trim-right": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+      "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
+    },
+    "tryit": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
+      "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
+      "dev": true
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0="
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+      "optional": true
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "dev": true
+    },
+    "type-detect": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz",
+      "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI="
+    },
+    "type-is": {
+      "version": "1.6.15",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz",
+      "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA="
+    },
+    "type-name": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/type-name/-/type-name-2.0.2.tgz",
+      "integrity": "sha1-7+fUEj2KxSr/9/QMfk3sUmYAj7Q=",
+      "dev": true
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+    },
+    "uglifyjs": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/uglifyjs/-/uglifyjs-2.4.11.tgz",
+      "integrity": "sha1-NEDWTgRXWViVJEGOtkHGi7kNET4=",
+      "dev": true
+    },
+    "uid2": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
+      "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=",
+      "dev": true
+    },
+    "undefsafe": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-0.0.3.tgz",
+      "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=",
+      "dev": true
+    },
+    "unique-temp-dir": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz",
+      "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=",
+      "dev": true
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "unzip-response": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz",
+      "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=",
+      "dev": true
+    },
+    "update-notifier": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-1.0.3.tgz",
+      "integrity": "sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=",
+      "dev": true
+    },
+    "upper-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+    },
+    "url-parse-lax": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+      "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
+      "dev": true
+    },
+    "ursa": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmjs.org/ursa/-/ursa-0.9.4.tgz",
+      "integrity": "sha1-Ciq/t9xCZ/czsPjy/H8siV1ApBM="
+    },
+    "user-home": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
+      "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA="
+    },
+    "util": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+      "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+      "dev": true,
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+          "dev": true
+        }
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "utils-merge": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
+      "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg="
+    },
+    "uuid": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
+      "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE="
+    },
+    "v8flags": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz",
+      "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ="
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+      "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
+      "dev": true
+    },
+    "validator.js": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/validator.js/-/validator.js-1.2.3.tgz",
+      "integrity": "sha1-+OYj9g5TutpUiAMzZJlwmWo0RuE="
+    },
+    "vary": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz",
+      "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc="
+    },
+    "verror": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+      "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw="
+    },
+    "when": {
+      "version": "3.7.8",
+      "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz",
+      "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I="
+    },
+    "which": {
+      "version": "1.2.14",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
+      "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
+      "dev": true
+    },
+    "widest-line": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz",
+      "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=",
+      "dev": true
+    },
+    "wordwrap": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "write": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
+      "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
+      "dev": true
+    },
+    "write-file-atomic": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz",
+      "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=",
+      "dev": true
+    },
+    "write-json-file": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-1.2.0.tgz",
+      "integrity": "sha1-LV3+lqvDyIkFfJOXGqQAXvtUgTQ=",
+      "dev": true
+    },
+    "write-pkg": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-1.0.0.tgz",
+      "integrity": "sha1-rriqnU14jh2JPfsIVJaLVDqRn1c=",
+      "dev": true
+    },
+    "xdg-basedir": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz",
+      "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=",
+      "dev": true
+    },
+    "xtend": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+    },
+    "yallist": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+      "dev": true
+    }
+  }
+}
diff --git a/package.json b/package.json
index f5d200cf..5600ad4c 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
   ],
   "main": "./dist/exports.js",
   "scripts": {
-    "build": "babel ./src --out-dir ./dist",
+    "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
     "lint": "eslint --fix --max-warnings 0 -- .",
@@ -35,7 +35,7 @@
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
     "prebuild": "npm run build:clean",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
-    "start:prod": "npm run build && node ./dist/main.js",
+    "start:prod": "npm run build && node --prof ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",
@@ -64,6 +64,7 @@
     "basic-auth-parser": "0.0.2",
     "binary-version-reader": "^0.5.0",
     "body-parser": "^1.15.2",
+    "chalk": "^1.1.3",
     "constitute": "^1.6.2",
     "express": "^4.14.0",
     "express-oauth-server": "^2.0.0-b1",
diff --git a/src/OAuthModel.js b/src/OAuthModel.js
index 73f67821..bfc05431 100644
--- a/src/OAuthModel.js
+++ b/src/OAuthModel.js
@@ -1,5 +1,7 @@
 // @flow
 
+import oauthClients from './oauthClients.json';
+
 import type {
   Client,
   TokenObject,
@@ -7,11 +9,7 @@ import type {
   UserRepository,
 } from './types';
 
-const OAUTH_CLIENTS = [{
-  clientId: 'CLI2',
-  clientSecret: 'client_secret_here',
-  grants: ['password'],
-}];
+const OAUTH_CLIENTS = oauthClients;
 
 class OauthModel {
   _userRepository: UserRepository;
diff --git a/src/lib/WebhookLogger.js b/src/lib/WebhookLogger.js
index a2948bce..79ef39a0 100644
--- a/src/lib/WebhookLogger.js
+++ b/src/lib/WebhookLogger.js
@@ -1,13 +1,15 @@
 // @flow
 
-import { IWebhookLogger } from '../types';
+import type { IWebhookLogger } from '../types';
+
+import logger from './logger';
 
 class WebhookLogger implements IWebhookLogger {
   _lastLog: Array;
 
   log(...args: Array) {
     this._lastLog = args;
-    console.log(...args);
+    logger.log(...args);
   }
 }
 
diff --git a/src/lib/logger.js b/src/lib/logger.js
index 0bd2c049..a0f77af2 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -19,15 +19,26 @@
 *
 */
 
+import chalk from 'chalk';
+import settings from '../settings';
+
 class Logger {
-  static log() {
-    // eslint-disable-next-line prefer-rest-params
-    console.log(...arguments);
+  static log(...params: Array) {
+    if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
+      console.log(...params);
+    }
+  }
+
+  static info(...params: Array) {
+    console.log(chalk.cyan(...params));
+  }
+
+  static warn(...params: Array) {
+    console.warn(chalk.yellow(...params));
   }
 
-  static error() {
-    // eslint-disable-next-line prefer-rest-params
-    console.error(...arguments);
+  static error(...params: Array) {
+    console.error(chalk.red(...params));
   }
 }
 
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 2103ee99..b2f68d36 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -240,6 +240,7 @@ class DeviceManager {
     }
 
     await this._deviceKeyRepository.update(deviceID, publicKey);
+
     const existingAttributes = await this._deviceAttributeRepository.getById(
       deviceID,
     );
diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js
index 4196f207..5566baec 100644
--- a/src/managers/FirmwareCompilationManager.js
+++ b/src/managers/FirmwareCompilationManager.js
@@ -4,6 +4,7 @@ import type { File } from 'express';
 
 import crypto from 'crypto';
 import fs from 'fs';
+import logger from '../lib/logger';
 import path from 'path';
 import mkdirp from 'mkdirp';
 import rmfr from 'rmfr';
@@ -123,7 +124,7 @@ class FirmwareCompilationManager {
 
     const errors = [];
     makeProcess.stderr.on('data', (data: string) => {
-      console.log(`${data}`);
+      logger.error(`${data}`);
       errors.push(`${data}`);
     });
 
diff --git a/src/oauthClients.json b/src/oauthClients.json
index 72cd0afb..048381d1 100644
--- a/src/oauthClients.json
+++ b/src/oauthClients.json
@@ -1,5 +1,10 @@
-[{
-  "clientId": "CLI2",
-  "clientSecret": "client_secret_here",
-  "grants": ["password"]
-}]
+[{
+  "clientId": "CLI2",
+  "clientSecret": "client_secret_here",
+  "grants": ["password"]
+},
+{
+  "clientId": "particle-collider",
+  "clientSecret": "particle-collider",
+  "grants": ["password"]
+}]
diff --git a/src/settings.js b/src/settings.js
index f3c46c7f..1f1c7e4b 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -34,8 +34,8 @@ export default {
   WEBHOOKS_DIRECTORY: path.join(__dirname, '../data/webhooks'),
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
-  CRYPTO_SALT: 'aes-128-cbc',
-  LOG_REQUESTS: true,
+  CRYPTO_ALGORITHM: 'aes-128-cbc',
+  LOG_REQUESTS: false,
   LOGIN_ROUTE: '/oauth/token',
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,
diff --git a/src/types.js b/src/types.js
index 2a5cf64f..0a720217 100644
--- a/src/types.js
+++ b/src/types.js
@@ -150,7 +150,7 @@ export type Settings = {
   ACCESS_TOKEN_LIFETIME: number,
   API_TIMEOUT: number,
   BUILD_DIRECTORY: string,
-  CRYPTO_SALT: string,
+  CRYPTO_ALGORITHM: string,
   DB_CONFIG: {
     OPTIONS: Object,
     PATH: ?string,
diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js
index 68229a75..e7ef9b71 100644
--- a/test/DeviceClaimsController.test.js
+++ b/test/DeviceClaimsController.test.js
@@ -72,5 +72,5 @@ test(
 test.after.always(async (): Promise => {
   await container.constitute('UserRepository').deleteById(testUser.id);
   await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').delete(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
 });
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index c990aa15..e1aa8841 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -73,5 +73,5 @@ test('should throw an error if public key is not provided', async t => {
 test.after.always(async (): Promise => {
   await container.constitute('UserRepository').deleteById(testUser.id);
   await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').delete(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
 });
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 59723539..5b0a5a99 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -17,7 +17,7 @@ export default {
 
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000,
-  CRYPTO_SALT: 'aes-128-cbc',
+  CRYPTO_ALGORITHM: 'aes-128-cbc',
   LOG_REQUESTS: false,
   LOGIN_ROUTE: '/oauth/token',
 

From 44dc6caf79964120fcad5ccf16a2e8a84ecf2922 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 4 Jun 2017 12:55:09 -0700
Subject: [PATCH 378/504] Fixing tests.. again.

---
 test/DevicesController.test.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index 055f064e..4b4e57a5 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -568,5 +568,5 @@ test.after.always(async (): Promise => {
   await TestData.deleteCustomFirmwareBinary(customFirmwareFilePath);
   await container.constitute('UserRepository').deleteById(testUser.id);
   await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').delete(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
 });

From 229fa34a0202a8b3da3addb61890300e12a75c48 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 4 Jun 2017 13:06:30 -0700
Subject: [PATCH 379/504] Fixing logger so it stringifies objects correctly.

---
 src/lib/logger.js | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/lib/logger.js b/src/lib/logger.js
index a0f77af2..df1135cb 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -22,23 +22,27 @@
 import chalk from 'chalk';
 import settings from '../settings';
 
+function _transform(...params: Array): Array {
+  return params.map(JSON.stringify);
+}
+
 class Logger {
   static log(...params: Array) {
     if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
-      console.log(...params);
+      console.log(_transform(...params));
     }
   }
 
   static info(...params: Array) {
-    console.log(chalk.cyan(...params));
+    console.log(chalk.cyan(_transform(...params)));
   }
 
   static warn(...params: Array) {
-    console.warn(chalk.yellow(...params));
+    console.warn(chalk.yellow(_transform(...params)));
   }
 
   static error(...params: Array) {
-    console.error(chalk.red(...params));
+    console.error(chalk.red(_transform(...params)));
   }
 }
 

From 3d36fc9f4aed41c8d0f29d9a6f38cbcd6ccd8b23 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 4 Jun 2017 16:34:14 -0700
Subject: [PATCH 380/504] Rebuild

---
 dist/controllers/OauthClientsController.js  | 14 ++--
 dist/controllers/ProductsController.js      | 22 +++---
 dist/lib/WebhookLogger.js                   |  8 +--
 dist/lib/logger.js                          | 76 ++++++++++++++-------
 dist/managers/FirmwareCompilationManager.js |  3 +-
 package-lock.json                           |  2 +-
 package.json                                |  4 +-
 7 files changed, 79 insertions(+), 50 deletions(-)

diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js
index 69a818aa..2aaeeaad 100644
--- a/dist/controllers/OauthClientsController.js
+++ b/dist/controllers/OauthClientsController.js
@@ -85,7 +85,13 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = function (_Controller) {
+var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'
+// eslint-disable-next-line class-methods-use-this
+), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'
+// eslint-disable-next-line class-methods-use-this
+), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'
+// eslint-disable-next-line class-methods-use-this
+), (_class = function (_Controller) {
   (0, _inherits3.default)(OauthClientsController, _Controller);
 
   function OauthClientsController() {
@@ -95,8 +101,6 @@ var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
 
   (0, _createClass3.default)(OauthClientsController, [{
     key: 'createClient',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
         return _regenerator2.default.wrap(function _callee$(_context) {
@@ -121,8 +125,6 @@ var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
     }()
   }, {
     key: 'editClient',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
@@ -147,8 +149,6 @@ var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
     }()
   }, {
     key: 'deleteClient',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
         return _regenerator2.default.wrap(function _callee3$(_context3) {
diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index e9382aa3..1ec39bb1 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -86,7 +86,17 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec7 = (0, _httpVerb2.default)('post'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/device_claims'), _dec9 = (0, _httpVerb2.default)('get'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec11 = (0, _httpVerb2.default)('post'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec15 = (0, _httpVerb2.default)('put'), _dec16 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec17 = (0, _httpVerb2.default)('delete'), _dec18 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec19 = (0, _httpVerb2.default)('get'), _dec20 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec21 = (0, _httpVerb2.default)('get'), _dec22 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec23 = (0, _httpVerb2.default)('delete'), _dec24 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) {
+var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'
+// eslint-disable-next-line class-methods-use-this
+), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'
+// eslint-disable-next-line class-methods-use-this
+), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec7 = (0, _httpVerb2.default)('post'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/device_claims'
+// eslint-disable-next-line class-methods-use-this
+), _dec9 = (0, _httpVerb2.default)('get'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec11 = (0, _httpVerb2.default)('post'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec15 = (0, _httpVerb2.default)('put'), _dec16 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec17 = (0, _httpVerb2.default)('delete'), _dec18 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'
+// eslint-disable-next-line class-methods-use-this
+), _dec19 = (0, _httpVerb2.default)('get'), _dec20 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec21 = (0, _httpVerb2.default)('get'), _dec22 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec23 = (0, _httpVerb2.default)('delete'), _dec24 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'
+// eslint-disable-next-line class-methods-use-this
+), (_class = function (_Controller) {
   (0, _inherits3.default)(ProductsController, _Controller);
 
   function ProductsController(deviceManager) {
@@ -100,8 +110,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
   (0, _createClass3.default)(ProductsController, [{
     key: 'getProducts',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
         return _regenerator2.default.wrap(function _callee$(_context) {
@@ -126,8 +134,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     }()
   }, {
     key: 'createProduct',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
@@ -176,8 +182,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     }()
   }, {
     key: 'generateClaimCode',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
@@ -301,8 +305,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     }()
   }, {
     key: 'removeDeviceFromProduct',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIdOrSlug, deviceID) {
         return _regenerator2.default.wrap(function _callee9$(_context9) {
@@ -375,8 +377,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     }()
   }, {
     key: 'removeTeamMember',
-
-    // eslint-disable-next-line class-methods-use-this
     value: function () {
       var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug, username) {
         return _regenerator2.default.wrap(function _callee12$(_context12) {
diff --git a/dist/lib/WebhookLogger.js b/dist/lib/WebhookLogger.js
index ec7a8bb5..0f4e6fb5 100644
--- a/dist/lib/WebhookLogger.js
+++ b/dist/lib/WebhookLogger.js
@@ -12,7 +12,9 @@ var _createClass2 = require('babel-runtime/helpers/createClass');
 
 var _createClass3 = _interopRequireDefault(_createClass2);
 
-var _types = require('../types');
+var _logger = require('./logger');
+
+var _logger2 = _interopRequireDefault(_logger);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
@@ -24,14 +26,12 @@ var WebhookLogger = function () {
   (0, _createClass3.default)(WebhookLogger, [{
     key: 'log',
     value: function log() {
-      var _console;
-
       for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
         args[_key] = arguments[_key];
       }
 
       this._lastLog = args;
-      (_console = console).log.apply(_console, args);
+      _logger2.default.log.apply(_logger2.default, args);
     }
   }]);
   return WebhookLogger;
diff --git a/dist/lib/logger.js b/dist/lib/logger.js
index 2675bb33..682ef9e6 100644
--- a/dist/lib/logger.js
+++ b/dist/lib/logger.js
@@ -12,12 +12,49 @@ var _createClass2 = require('babel-runtime/helpers/createClass');
 
 var _createClass3 = _interopRequireDefault(_createClass2);
 
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
 var _chalk = require('chalk');
 
 var _chalk2 = _interopRequireDefault(_chalk);
 
+var _settings = require('../settings');
+
+var _settings2 = _interopRequireDefault(_settings);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+/**
+*    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
+*
+*    This program is free software: you can redistribute it and/or modify
+*    it under the terms of the GNU Affero General Public License, version 3,
+*    as published by the Free Software Foundation.
+*
+*    This program is distributed in the hope that it will be useful,
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*    GNU Affero General Public License for more details.
+*
+*    You should have received a copy of the GNU Affero General Public License
+*    along with this program.  If not, see .
+*
+*    You can download the source here: https://github.com/spark/spark-server
+*
+* 
+*
+*/
+
+function _transform() {
+  for (var _len = arguments.length, params = Array(_len), _key = 0; _key < _len; _key++) {
+    params[_key] = arguments[_key];
+  }
+
+  return params.map(_stringify2.default);
+}
+
 var Logger = function () {
   function Logger() {
     (0, _classCallCheck3.default)(this, Logger);
@@ -26,36 +63,27 @@ var Logger = function () {
   (0, _createClass3.default)(Logger, null, [{
     key: 'log',
     value: function log() {
-      // eslint-disable-next-line prefer-rest-params
-      //console.log(...arguments);
+      if (_settings2.default.SHOW_VERBOSE_DEVICE_LOGS) {
+        console.log(_transform.apply(undefined, arguments));
+      }
+    }
+  }, {
+    key: 'info',
+    value: function info() {
+      console.log(_chalk2.default.cyan(_transform.apply(undefined, arguments)));
+    }
+  }, {
+    key: 'warn',
+    value: function warn() {
+      console.warn(_chalk2.default.yellow(_transform.apply(undefined, arguments)));
     }
   }, {
     key: 'error',
     value: function error() {
-      // eslint-disable-next-line prefer-rest-params
-      console.error(_chalk2.default.red.apply(_chalk2.default, arguments));
+      console.error(_chalk2.default.red(_transform.apply(undefined, arguments)));
     }
   }]);
   return Logger;
-}(); /**
-     *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
-     *
-     *    This program is free software: you can redistribute it and/or modify
-     *    it under the terms of the GNU Affero General Public License, version 3,
-     *    as published by the Free Software Foundation.
-     *
-     *    This program is distributed in the hope that it will be useful,
-     *    but WITHOUT ANY WARRANTY; without even the implied warranty of
-     *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-     *    GNU Affero General Public License for more details.
-     *
-     *    You should have received a copy of the GNU Affero General Public License
-     *    along with this program.  If not, see .
-     *
-     *    You can download the source here: https://github.com/spark/spark-server
-     *
-     * 
-     *
-     */
+}();
 
 exports.default = Logger;
\ No newline at end of file
diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js
index c69114e6..5dc4e9c0 100644
--- a/dist/managers/FirmwareCompilationManager.js
+++ b/dist/managers/FirmwareCompilationManager.js
@@ -150,7 +150,8 @@ FirmwareCompilationManager.compileSource = function () {
               var combinedPath = _path2.default.join(appPath, fileName);
 
               while (_fs2.default.existsSync(combinedPath)) {
-                combinedPath = _path2.default.join(appPath, '' + _path2.default.basename(fileName, fileExtension) + ('_' + iterator++ + fileExtension));
+                combinedPath = _path2.default.join(appPath, '' + _path2.default.basename(fileName, fileExtension) + ('_' + iterator++ + fileExtension) // eslint-disable-line no-plusplus
+                );
               }
 
               _fs2.default.writeFileSync(combinedPath, file.buffer);
diff --git a/package-lock.json b/package-lock.json
index 72d802a0..f4cfd1d1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4170,7 +4170,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#0d48e94bde43e642c31682fe47364f6ec265d530"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#7ea6c12a1a71b58725bf278a8a7c5086c7977ac6"
     },
     "spawn-sync": {
       "version": "1.0.15",
diff --git a/package.json b/package.json
index 5600ad4c..f7cabb30 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
     "prebuild": "npm run build:clean",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
-    "start:prod": "npm run build && node --prof ./dist/main.js",
+    "start:prod": "npm run build && node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",
@@ -88,7 +88,7 @@
     "ava": "^0.17.0",
     "babel-eslint": "^7.1.1",
     "babel-plugin-transform-class-properties": "^6.19.0",
-    "babel-plugin-transform-decorators": "^6.13.0",
+    "babel-plugin-transform-decorators": "^6.24.1",
     "babel-plugin-transform-decorators-legacy": "^1.3.4",
     "babel-plugin-transform-es2015-destructuring": "^6.19.0",
     "babel-plugin-transform-es2015-spread": "^6.8.0",

From d69cfce31d0879429b4ea9ec56fda37b05eab94d Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 4 Jun 2017 17:20:14 -0700
Subject: [PATCH 381/504] Fixing timeouts for loading large amounts of data.

---
 dist/repository/DeviceAttributeDatabaseRepository.js | 2 +-
 dist/repository/WebhookDatabaseRepository.js         | 2 +-
 src/repository/DeviceAttributeDatabaseRepository.js  | 6 +++++-
 src/repository/WebhookDatabaseRepository.js          | 6 +++++-
 4 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 738a8e6f..6311cf55 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -102,7 +102,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
             case 0:
               query = userID ? { ownerID: userID } : {};
               _context4.next = 3;
-              return _this._database.find(_this._collectionName, query);
+              return _this._database.find(_this._collectionName, query, { timeout: false });
 
             case 3:
               return _context4.abrupt('return', _context4.sent);
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index dae0a545..c60a2da9 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -90,7 +90,7 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
             case 0:
               query = userID ? { ownerID: userID } : {};
               _context3.next = 3;
-              return _this._database.find(_this._collectionName, query);
+              return _this._database.find(_this._collectionName, query, { timeout: false });
 
             case 3:
               return _context3.abrupt('return', _context3.sent);
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 2a873ed3..bf7105ee 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -29,7 +29,11 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-    return await this._database.find(this._collectionName, query);
+    return await this._database.find(
+      this._collectionName,
+      query,
+      {timeout:false},
+    );
   };
 
   getById = async (
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index b526c274..c3cdab50 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -24,7 +24,11 @@ class WebhookDatabaseRepository implements IWebhookRepository {
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-    return await this._database.find(this._collectionName, query);
+    return await this._database.find(
+      this._collectionName,
+      query,
+      {timeout:false},
+    );
   };
 
   getById = async (id: string, userID: ?string = null): Promise => {

From fc6e52ecc28b32dc02e494b0cf40ac573e03ed8e Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 4 Jun 2017 19:00:05 -0700
Subject: [PATCH 382/504] Fixing access token so it doesn't expire so soon

---
 dist/RouteConfig.js                                 | 2 +-
 src/RouteConfig.js                                  | 2 +-
 src/repository/DeviceAttributeDatabaseRepository.js | 2 +-
 src/repository/WebhookDatabaseRepository.js         | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js
index 1ebec26b..a3e7194c 100644
--- a/dist/RouteConfig.js
+++ b/dist/RouteConfig.js
@@ -99,7 +99,7 @@ var serverSentEventsMiddleware = function serverSentEventsMiddleware() {
 
 exports.default = function (app, container, controllers, settings) {
   var oauth = new _expressOauthServer2.default({
-    ACCESS_TOKEN_LIFETIME: settings.ACCESS_TOKEN_LIFETIME,
+    accessTokenLifetime: settings.ACCESS_TOKEN_LIFETIME,
     allowBearerTokensInQueryString: true,
     model: new _OAuthModel2.default(container.constitute('UserRepository'))
   });
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 6364407f..951fec3f 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -62,7 +62,7 @@ export default (
   settings: Settings,
 ) => {
   const oauth = new OAuthServer({
-    ACCESS_TOKEN_LIFETIME: settings.ACCESS_TOKEN_LIFETIME,
+    accessTokenLifetime: settings.ACCESS_TOKEN_LIFETIME,
     allowBearerTokensInQueryString: true,
     model: new OAuthModel(container.constitute('UserRepository')),
   });
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index bf7105ee..d103e052 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -32,7 +32,7 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     return await this._database.find(
       this._collectionName,
       query,
-      {timeout:false},
+      { timeout: false },
     );
   };
 
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index c3cdab50..c6cb0bc9 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -27,7 +27,7 @@ class WebhookDatabaseRepository implements IWebhookRepository {
     return await this._database.find(
       this._collectionName,
       query,
-      {timeout:false},
+      { timeout: false },
     );
   };
 

From 86ccecf3bcfacdd3bbfbba96def6bd4cd4205886 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 4 Jun 2017 22:10:45 +0200
Subject: [PATCH 383/504] made deviceKeyRepository consistent with
 IBaseRepository

---
 dist/managers/DeviceManager.js                |  2 +-
 .../repository/DeviceKeyDatabaseRepository.js | 84 ++++++++++++++-----
 src/managers/DeviceManager.js                 |  9 +-
 src/repository/DeviceKeyDatabaseRepository.js | 30 ++++---
 src/types.js                                  | 10 ++-
 5 files changed, 90 insertions(+), 45 deletions(-)

diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 762f0dc8..2fedd25b 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -516,7 +516,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 11:
               _context11.next = 13;
-              return _this._deviceKeyRepository.update(deviceID, publicKey);
+              return _this._deviceKeyRepository.update({ deviceID: deviceID, key: publicKey });
 
             case 13:
               _context11.next = 15;
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index c1607bbc..60420a44 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -8,6 +8,10 @@ var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
@@ -24,14 +28,14 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
   (0, _classCallCheck3.default)(this, DeviceKeyDatabaseRepository);
   this._collectionName = 'deviceKeys';
 
-  this.deleteById = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID) {
+  this.create = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
             case 0:
               _context.next = 2;
-              return _this._database.remove(_this._collectionName, deviceID);
+              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({ _id: model.deviceID }, model));
 
             case 2:
               return _context.abrupt('return', _context.sent);
@@ -49,21 +53,19 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
     };
   }();
 
-  this.getById = function () {
+  this.deleteById = function () {
     var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
-      var keyObject;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: deviceID });
+              return _this._database.remove(_this._collectionName, deviceID);
 
             case 2:
-              keyObject = _context2.sent;
-              return _context2.abrupt('return', keyObject ? keyObject.key : null);
+              return _context2.abrupt('return', _context2.sent);
 
-            case 4:
+            case 3:
             case 'end':
               return _context2.stop();
           }
@@ -76,30 +78,68 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
     };
   }();
 
+  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+    return _regenerator2.default.wrap(function _callee3$(_context3) {
+      while (1) {
+        switch (_context3.prev = _context3.next) {
+          case 0:
+            throw new Error('The method is not implemented.');
+
+          case 1:
+          case 'end':
+            return _context3.stop();
+        }
+      }
+    }, _callee3, _this);
+  }));
+
+  this.getById = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: deviceID });
+
+            case 2:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 3:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x3) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
   this.update = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, key) {
-      var keyObject;
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(model) {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
-          switch (_context3.prev = _context3.next) {
+          switch (_context5.prev = _context5.next) {
             case 0:
-              _context3.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: deviceID }, null, { $set: { _id: deviceID, key: key } }, { new: true, upsert: true });
+              _context5.next = 2;
+              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model) }, { new: true, upsert: true });
 
             case 2:
-              keyObject = _context3.sent;
-              return _context3.abrupt('return', keyObject.key);
+              return _context5.abrupt('return', _context5.sent);
 
-            case 4:
+            case 3:
             case 'end':
-              return _context3.stop();
+              return _context5.stop();
           }
         }
-      }, _callee3, _this);
+      }, _callee5, _this);
     }));
 
-    return function (_x3, _x4) {
-      return _ref3.apply(this, arguments);
+    return function (_x4) {
+      return _ref5.apply(this, arguments);
     };
   }();
 
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 35c9f851..94bd26c8 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -5,7 +5,7 @@ import type { DeviceServer } from 'spark-protocol';
 import type {
   Device,
   DeviceAttributes,
-  IBaseRepository,
+  IDeviceKeyRepository,
   IDeviceAttributeRepository,
   IDeviceFirmwareRepository,
 } from '../types';
@@ -16,13 +16,13 @@ import HttpError from '../lib/HttpError';
 class DeviceManager {
   _deviceAttributeRepository: IDeviceAttributeRepository;
   _deviceFirmwareRepository: IDeviceFirmwareRepository;
-  _deviceKeyRepository: IBaseRepository;
+  _deviceKeyRepository: IDeviceKeyRepository;
   _deviceServer: DeviceServer;
 
   constructor(
     deviceAttributeRepository: IDeviceAttributeRepository,
     deviceFirmwareRepository: IDeviceFirmwareRepository,
-    deviceKeyRepository: IBaseRepository,
+    deviceKeyRepository: IDeviceKeyRepository,
     deviceServer: DeviceServer,
   ) {
     this._deviceAttributeRepository = deviceAttributeRepository;
@@ -243,8 +243,7 @@ class DeviceManager {
       throw new HttpError(`Key error ${error}`);
     }
 
-    await this._deviceKeyRepository.update(deviceID, publicKey);
-
+    await this._deviceKeyRepository.update({ deviceID, key: publicKey });
     const existingAttributes = await this._deviceAttributeRepository.getById(
       deviceID,
     );
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index 0f024ee3..c5b5fd6b 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -1,6 +1,6 @@
 // @flow
 
-import type { IBaseDatabase, IDeviceKeyRepository } from '../types';
+import type { DeviceKeyObject, IBaseDatabase, IDeviceKeyRepository } from '../types';
 
 class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
   _database: IBaseDatabase;
@@ -10,29 +10,33 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
     this._database = database;
   }
 
+  create = async (model: DeviceKeyObject): Promise =>
+    await this._database.insertOne(
+      this._collectionName,
+      { _id: model.deviceID, ...model },
+    );
+
   deleteById = async (deviceID: string): Promise =>
     await this._database.remove(this._collectionName, deviceID);
 
-  getById = async (deviceID: string): Promise => {
-    const keyObject = await this._database.findOne(
+  getAll = async (): Promise> => {
+    throw new Error('The method is not implemented.');
+  }
+
+  getById = async (deviceID: string): Promise =>
+    await this._database.findOne(
       this._collectionName,
       { _id: deviceID },
     );
 
-    return keyObject ? keyObject.key : null;
-  }
-
-  update = async (deviceID: string, key: string): Promise => {
-    const keyObject = await this._database.findAndModify(
+  update = async (model: DeviceKeyObject): Promise =>
+    await this._database.findAndModify(
       this._collectionName,
-      { _id: deviceID },
+      { _id: model.deviceID },
       null,
-      { $set: { _id: deviceID, key } },
+      { $set: { ...model } },
       { new: true, upsert: true },
     );
-
-    return keyObject.key;
-  }
 }
 
 export default DeviceKeyDatabaseRepository;
diff --git a/src/types.js b/src/types.js
index aafc87bc..41241c80 100644
--- a/src/types.js
+++ b/src/types.js
@@ -74,6 +74,11 @@ export type DeviceAttributes = {
   timestamp: Date,
 };
 
+export type DeviceKeyObject = {
+  deviceID: string,
+  key: string,
+};
+
 export type Event = EventData & {
   ttl: number,
   publishedAt: Date,
@@ -235,10 +240,7 @@ export interface IDeviceAttributeRepository extends IBaseRepository;
 }
 
-export interface IDeviceKeyRepository {
-  getById(deviceID: string): Promise;
-  update(deviceID: string, key: string): Promise;
-}
+export interface IDeviceKeyRepository extends IBaseRepository {}
 
 export interface IUserRepository extends IBaseRepository {
   createWithCredentials(credentials: UserCredentials): Promise;

From b6d11f7be384adf59c262081933e5a0fe7157b85 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 3 Jun 2017 02:33:42 +0200
Subject: [PATCH 384/504] implement Administrator permissions for
 deviceAttributes and webhooks. The first try.

---
 src/OAuthModel.js                             |   4 +
 src/RouteConfig.js                            |   8 +-
 src/controllers/DeviceClaimsController.js     |   2 +-
 src/controllers/DevicesController.js          |  19 +---
 src/controllers/WebhooksController.js         |  21 ++--
 src/defaultBindings.js                        |  20 +++-
 src/lib/utils.js                              |   9 ++
 src/managers/DeviceManager.js                 |  85 +++++++--------
 src/managers/PermissionManager.js             | 100 ++++++++++++++++++
 src/managers/WebhookManager.js                |  26 ++---
 src/repository/BaseMongoDb.js                 |   6 +-
 .../DeviceAttributeDatabaseRepository.js      |  20 +---
 src/repository/UserDatabaseRepository.js      |  35 ++++--
 src/repository/UserFileRepository.js          |  65 +++++++-----
 src/repository/WebhookDatabaseRepository.js   |   6 +-
 src/settings.js                               |   2 +
 src/types.js                                  |  38 ++++---
 test/DevicesController.test.js                |   2 +-
 test/WebhookManager.test.js                   |  28 ++---
 test/setup/settings.js                        |   2 +
 20 files changed, 317 insertions(+), 181 deletions(-)
 create mode 100644 src/lib/utils.js
 create mode 100644 src/managers/PermissionManager.js

diff --git a/src/OAuthModel.js b/src/OAuthModel.js
index bfc05431..93caa3b7 100644
--- a/src/OAuthModel.js
+++ b/src/OAuthModel.js
@@ -10,6 +10,7 @@ import type {
 } from './types';
 
 const OAUTH_CLIENTS = oauthClients;
+import { generateRandomToken } from './lib/utils';
 
 class OauthModel {
   _userRepository: UserRepository;
@@ -18,6 +19,9 @@ class OauthModel {
     this._userRepository = userRepository;
   }
 
+  generateAccessToken = async (): Promise =>
+    await generateRandomToken();
+
   getAccessToken = async (bearerToken: string): ?Object => {
     const user = await this._userRepository.getByAccessToken(bearerToken);
     if (!user) {
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 951fec3f..7bf69805 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -25,13 +25,15 @@ const maybe = (middleware: Middleware, condition: boolean): Middleware =>
     }
   };
 
-const injectUserMiddleware = (): Middleware =>
+const injectUserMiddleware = (container: Container): Middleware =>
   (request: $Request, response: $Response, next: NextFunction) => {
     const oauthInfo = response.locals.oauth;
     if (oauthInfo) {
       const token = (oauthInfo: any).token;
+      const user = token && token.user;
       // eslint-disable-next-line no-param-reassign
-      (request: any).user = token && token.user;
+      (request: any).user = user;
+      container.constitute('UserRepository').setCurrentUser(user);
     }
     next();
   };
@@ -97,7 +99,7 @@ export default (
         route,
         maybe(oauth.authenticate(), !anonymous),
         maybe(serverSentEventsMiddleware(), serverSentEvents),
-        injectUserMiddleware(),
+        injectUserMiddleware(container),
         maybe(filesMiddleware(allowedUploads), allowedUploads),
         async (request: $Request, response: $Response): Promise => {
           const argumentNames = (route.match(/:[\w]*/g) || []).map(
diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js
index 71767945..cde6174a 100644
--- a/src/controllers/DeviceClaimsController.js
+++ b/src/controllers/DeviceClaimsController.js
@@ -28,7 +28,7 @@ class DeviceClaimsController extends Controller {
       this.user.id,
     );
 
-    const devices = await this._deviceManager.getAll(this.user.id);
+    const devices = await this._deviceManager.getAll();
     const deviceIDs = devices.map(
       (device: Device): string => device.deviceID,
     );
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index 6e7473d5..4e38071b 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -64,7 +64,7 @@ class DevicesController extends Controller {
   @httpVerb('delete')
   @route('/v1/devices/:deviceID')
   async unclaimDevice(deviceID: string): Promise<*> {
-    await this._deviceManager.unclaimDevice(deviceID, this.user.id);
+    await this._deviceManager.unclaimDevice(deviceID);
     return this.ok({ ok: true });
   }
 
@@ -72,7 +72,7 @@ class DevicesController extends Controller {
   @route('/v1/devices')
   async getDevices(): Promise<*> {
     try {
-      const devices = await this._deviceManager.getAll(this.user.id);
+      const devices = await this._deviceManager.getAll();
       return this.ok(devices.map((device: Device): DeviceAPIType =>
         deviceToAPI(device)),
       );
@@ -85,10 +85,7 @@ class DevicesController extends Controller {
   @httpVerb('get')
   @route('/v1/devices/:deviceID')
   async getDevice(deviceID: string): Promise<*> {
-    const device = await this._deviceManager.getDetailsByID(
-      deviceID,
-      this.user.id,
-    );
+    const device = await this._deviceManager.getDetailsByID(deviceID);
     return this.ok(deviceToAPI(device));
   }
 
@@ -101,7 +98,6 @@ class DevicesController extends Controller {
     try {
       const varValue = await this._deviceManager.getVariableValue(
         deviceID,
-        this.user.id,
         varName,
       );
 
@@ -131,7 +127,6 @@ class DevicesController extends Controller {
     if (postBody.name) {
       const updatedAttributes = await this._deviceManager.renameDevice(
         deviceID,
-        this.user.id,
         postBody.name,
       );
       return this.ok({ name: updatedAttributes.name, ok: true });
@@ -141,7 +136,6 @@ class DevicesController extends Controller {
     if (postBody.app_id) {
       const flashStatus = await this._deviceManager.flashKnownApp(
         deviceID,
-        this.user.id,
         postBody.app_id,
       );
 
@@ -174,7 +168,6 @@ class DevicesController extends Controller {
 
       await this._deviceManager.raiseYourHand(
         deviceID,
-        this.user.id,
         !!parseInt(postBody.signal, 10),
       );
 
@@ -194,15 +187,11 @@ class DevicesController extends Controller {
     try {
       const result = await this._deviceManager.callFunction(
         deviceID,
-        this.user.id,
         functionName,
         postBody,
       );
+      const device = await this._deviceManager.getByID(deviceID);
 
-      const device = await this._deviceManager.getByID(
-        deviceID,
-        this.user.id,
-      );
       return this.ok(deviceToAPI(device, result));
     } catch (error) {
       const errorMessage = error.message;
diff --git a/src/controllers/WebhooksController.js b/src/controllers/WebhooksController.js
index 02d5f805..5859d1cd 100644
--- a/src/controllers/WebhooksController.js
+++ b/src/controllers/WebhooksController.js
@@ -34,20 +34,13 @@ class WebhooksController extends Controller {
   @httpVerb('get')
   @route('/v1/webhooks')
   async getAll(): Promise<*> {
-    return this.ok(
-      await this._webhookManager.getAll(this.user.id),
-    );
+    return this.ok(await this._webhookManager.getAll());
   }
 
   @httpVerb('get')
-  @route('/v1/webhooks/:webhookId')
-  async getById(webhookId: string): Promise<*> {
-    return this.ok(
-      await this._webhookManager.getByID(
-        webhookId,
-        this.user.id,
-      ),
-    );
+  @route('/v1/webhooks/:webhookID')
+  async getByID(webhookID: string): Promise<*> {
+    return this.ok(await this._webhookManager.getByID(webhookID));
   }
 
   @httpVerb('post')
@@ -73,9 +66,9 @@ class WebhooksController extends Controller {
   }
 
   @httpVerb('delete')
-  @route('/v1/webhooks/:webhookId')
-  async deleteById(webhookId: string): Promise<*> {
-    await this._webhookManager.deleteByID(webhookId, this.user.id);
+  @route('/v1/webhooks/:webhookID')
+  async deleteByID(webhookID: string): Promise<*> {
+    await this._webhookManager.deleteByID(webhookID);
     return this.ok({ ok: true });
   }
 }
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 58602016..d479a314 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -16,6 +16,7 @@ import WebhookLogger from './lib/WebhookLogger';
 import DeviceManager from './managers/DeviceManager';
 import WebhookManager from './managers/WebhookManager';
 import EventManager from './managers/EventManager';
+import PermissionManager from './managers/PermissionManager';
 import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository';
 import TingoDb from './repository/TingoDb';
 import DeviceAttributeDatabaseRepository from
@@ -52,7 +53,7 @@ export default (container: Container, newSettings: Settings) => {
 
   // lib
   container.bindClass(
-    'IWebhookLogger',
+    'WebhookLogger',
     WebhookLogger,
     [],
   );
@@ -76,6 +77,15 @@ export default (container: Container, newSettings: Settings) => {
     EventsController,
     ['EventManager'],
   );
+  container.bindClass(
+    'PermissionManager',
+    PermissionManager,
+    [
+      'DeviceAttributeRepository',
+      'UserRepository',
+      'WebhookRepository',
+    ],
+  );
   container.bindClass(
     'OauthClientsController',
     OauthClientsController,
@@ -111,6 +121,7 @@ export default (container: Container, newSettings: Settings) => {
       'DeviceFirmwareRepository',
       'DeviceKeyRepository',
       'DeviceServer',
+      'PermissionManager',
     ],
   );
   container.bindClass(
@@ -121,7 +132,12 @@ export default (container: Container, newSettings: Settings) => {
   container.bindClass(
     'WebhookManager',
     WebhookManager,
-    ['WebhookRepository', 'EventPublisher', 'IWebhookLogger'],
+    [
+      'EventPublisher',
+      'PermissionManager',
+      'WebhookLogger',
+      'WebhookRepository',
+    ],
   );
 
   // Repositories
diff --git a/src/lib/utils.js b/src/lib/utils.js
new file mode 100644
index 00000000..8637dd3a
--- /dev/null
+++ b/src/lib/utils.js
@@ -0,0 +1,9 @@
+// @flow
+
+import crypto from 'crypto';
+import { promisify } from './promisify';
+
+export const generateRandomToken = async (): Promise => {
+  const randomBytesBuffer = await promisify(crypto, 'randomBytes', 256);
+  return crypto.createHash('sha1').update(randomBytesBuffer).digest('hex');
+};
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 94bd26c8..c1c6ced0 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -2,6 +2,7 @@
 
 import type { File } from 'express';
 import type { DeviceServer } from 'spark-protocol';
+import type PermissionManager from './PermissionManager';
 import type {
   Device,
   DeviceAttributes,
@@ -18,17 +19,20 @@ class DeviceManager {
   _deviceFirmwareRepository: IDeviceFirmwareRepository;
   _deviceKeyRepository: IDeviceKeyRepository;
   _deviceServer: DeviceServer;
+  _permissionManager: PermissionManager;
 
   constructor(
     deviceAttributeRepository: IDeviceAttributeRepository,
     deviceFirmwareRepository: IDeviceFirmwareRepository,
     deviceKeyRepository: IDeviceKeyRepository,
     deviceServer: DeviceServer,
+    permissionManager: PermissionManager,
   ) {
     this._deviceAttributeRepository = deviceAttributeRepository;
     this._deviceFirmwareRepository = deviceFirmwareRepository;
     this._deviceKeyRepository = deviceKeyRepository;
     this._deviceServer = deviceServer;
+    this._permissionManager = permissionManager;
   }
 
   claimDevice = async (
@@ -56,12 +60,9 @@ class DeviceManager {
     return await this._deviceAttributeRepository.update(attributesToSave);
   };
 
-  unclaimDevice = async (
-    deviceID: string,
-    userID: string,
-  ): Promise => {
+  unclaimDevice = async (deviceID: string): Promise => {
     const deviceAttributes =
-      await this._deviceAttributeRepository.getById(deviceID, userID);
+      await this._permissionManager.getEntityByID('deviceAttributes', deviceID);
 
     if (!deviceAttributes) {
       throw new HttpError('No device found', 404);
@@ -74,10 +75,10 @@ class DeviceManager {
     return await this._deviceAttributeRepository.update(attributesToSave);
   };
 
-  getByID = async (deviceID: string, userID: string): Promise => {
-    const attributes = await this._deviceAttributeRepository.getById(
+  getByID = async (deviceID: string): Promise => {
+    const attributes = await this._permissionManager.getEntityByID(
+      'deviceAttributes',
       deviceID,
-      userID,
     );
 
     if (!attributes) {
@@ -94,14 +95,11 @@ class DeviceManager {
     };
   };
 
-  getDetailsByID = async (
-    deviceID: string,
-    userID: string,
-  ): Promise => {
+  getDetailsByID = async (deviceID: string): Promise => {
     const device = this._deviceServer.getDevice(deviceID);
 
     const [attributes, description] = await Promise.all([
-      this._deviceAttributeRepository.getById(deviceID, userID),
+      this._permissionManager.getEntityByID('deviceAttributes', deviceID),
       device && device.getDescription(),
     ]);
 
@@ -119,9 +117,10 @@ class DeviceManager {
     };
   };
 
-  getAll = async (userID: string): Promise> => {
+  getAll = async (): Promise> => {
     const devicesAttributes =
-      await this._deviceAttributeRepository.getAll(userID);
+      await this._permissionManager.getAllEntitiesForCurrentUser('deviceAttributes');
+
     const devicePromises = devicesAttributes.map(
       async (attributes: DeviceAttributes): Promise => {
         const device = this._deviceServer.getDevice(attributes.deviceID);
@@ -140,18 +139,13 @@ class DeviceManager {
 
   callFunction = async (
     deviceID: string,
-    userID: string,
     functionName: string,
     functionArguments: {[key: string]: string},
   ): Promise<*> => {
-    const doesUserHaveAccess =
-      await this._deviceAttributeRepository.doesUserHaveAccess(
-        deviceID,
-        userID,
-      );
-    if (!doesUserHaveAccess) {
-      throw new HttpError('No device found', 404);
-    }
+    await this._permissionManager.checkPermissionsForEntityByID(
+      'deviceAttributes',
+      deviceID,
+    );
 
     const device = this._deviceServer.getDevice(deviceID);
     if (!device) {
@@ -166,17 +160,12 @@ class DeviceManager {
 
   getVariableValue = async (
     deviceID: string,
-    userID: string,
     varName: string,
   ): Promise<*> => {
-    const doesUserHaveAccess =
-      await this._deviceAttributeRepository.doesUserHaveAccess(
-        deviceID,
-        userID,
-      );
-    if (!doesUserHaveAccess) {
-      throw new HttpError('No device found', 404);
-    }
+    await this._permissionManager.checkPermissionsForEntityByID(
+      'deviceAttributes',
+      deviceID,
+    );
 
     const device = this._deviceServer.getDevice(deviceID);
     if (!device) {
@@ -190,6 +179,11 @@ class DeviceManager {
     deviceID: string,
     file: File,
   ): Promise => {
+    await this._permissionManager.checkPermissionsForEntityByID(
+      'deviceAttributes',
+      deviceID,
+    );
+
     const device = this._deviceServer.getDevice(deviceID);
     if (!device) {
       throw new HttpError('Could not get device for ID', 404);
@@ -200,15 +194,12 @@ class DeviceManager {
 
   flashKnownApp = async (
     deviceID: string,
-    userID: string,
     appName: string,
   ): Promise => {
-    if (await !this._deviceAttributeRepository.doesUserHaveAccess(
+    await this._permissionManager.checkPermissionsForEntityByID(
+      'deviceAttributes',
       deviceID,
-      userID,
-    )) {
-      throw new HttpError('No device found', 404);
-    }
+    );
 
     const knownFirmware = this._deviceFirmwareRepository.getByName(appName);
 
@@ -256,20 +247,17 @@ class DeviceManager {
     };
     await this._deviceAttributeRepository.update(attributes);
 
-    return await this.getByID(deviceID, userID);
+    return await this.getByID(deviceID);
   };
 
   raiseYourHand = async (
     deviceID: string,
-    userID: string,
     shouldShowSignal: boolean,
   ): Promise => {
-    if (await !this._deviceAttributeRepository.doesUserHaveAccess(
+    await this._permissionManager.checkPermissionsForEntityByID(
+      'deviceAttributes',
       deviceID,
-      userID,
-    )) {
-      throw new HttpError('No device found', 404);
-    }
+    );
 
     const device = this._deviceServer.getDevice(deviceID);
     if (!device) {
@@ -281,12 +269,11 @@ class DeviceManager {
 
   renameDevice = async (
     deviceID: string,
-    userID: string,
     name: string,
   ): Promise => {
-    const attributes = await this._deviceAttributeRepository.getById(
+    const attributes = await this._permissionManager.getEntityByID(
+      'deviceAttributes',
       deviceID,
-      userID,
     );
 
     if (!attributes) {
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
new file mode 100644
index 00000000..ffbb0869
--- /dev/null
+++ b/src/managers/PermissionManager.js
@@ -0,0 +1,100 @@
+// @flow
+
+import type {
+  IDeviceAttributeRepository,
+  IUserRepository,
+  IWebhookRepository,
+  ProtectedEntityName,
+} from '../types';
+import HttpError from '../lib/HttpError';
+import nullthrows from 'nullthrows';
+import settings from '../settings';
+import { generateRandomToken } from '../lib/utils';
+
+const MAX_TIMESTAMP = 8640000000000000;
+
+class PermissionManager {
+  _userRepository: IUserRepository;
+  _repositoriesByEntityName: Map = new Map();
+
+  constructor(
+    deviceAttributeRepository: IDeviceAttributeRepository,
+    userRepository: IUserRepository,
+    webhookRepository: IWebhookRepository,
+  ) {
+    this._userRepository = userRepository;
+    this._repositoriesByEntityName.set('deviceAttributes', deviceAttributeRepository);
+    this._repositoriesByEntityName.set('webhook', webhookRepository);
+
+    (async (): Promise => await this._init())();
+  }
+
+  checkPermissionsForEntityByID = async (
+    entityName: ProtectedEntityName,
+    id: string,
+  ): Promise => !!(await this.getEntityByID(entityName, id));
+
+  getAllEntitiesForCurrentUser = async (entityName: ProtectedEntityName): Promise<*> => {
+    const currentUser = this._userRepository.getCurrentUser();
+    return await nullthrows(this._repositoriesByEntityName.get(entityName))
+      .getAll(currentUser.id);
+  }
+
+  getEntityByID = async (
+    entityName: ProtectedEntityName,
+    id: string,
+  ): Promise<*> => {
+    const entity = await nullthrows(this._repositoriesByEntityName.get(entityName)).getById(id);
+    if (!entity) {
+      return null;
+    }
+    if (!this._doesUserHaveAccess(entity.ownerID)) {
+      throw new HttpError('User doesn\'t have access', 403);
+    }
+    return entity;
+  }
+
+  _createDefaultAdminUser = async (): Promise => {
+    const defaultAdminUser = await this._userRepository.createWithCredentials(
+      {
+        password: settings.DEFAULT_ADMIN_PASSWORD,
+        username: settings.DEFAULT_ADMIN_USERNAME,
+      },
+      'administrator',
+    );
+
+    const accessTokenObject = {
+      accessToken: await generateRandomToken(),
+      accessTokenExpiresAt: new Date(MAX_TIMESTAMP),
+    };
+
+    const userToUpdate = {
+      ...defaultAdminUser,
+      accessTokens: [accessTokenObject],
+    };
+
+    await this._userRepository.update(userToUpdate);
+    console.log(
+      `New default admin user created with token ${accessTokenObject.accessToken}`,
+    );
+  };
+
+  _doesUserHaveAccess = (ownerID: ?string): boolean => {
+    const currentUser = this._userRepository.getCurrentUser();
+    return currentUser.role === 'administrator' || currentUser.id === ownerID;
+  }
+
+  _init = async (): Promise => {
+    const defaultAdminUser =
+      await this._userRepository.getByUsername(settings.DEFAULT_ADMIN_USERNAME);
+    if (defaultAdminUser) {
+      console.log(
+        `Default admin accessToken: ${defaultAdminUser.accessTokens[0].accessToken}`,
+      );
+    } else {
+      await this._createDefaultAdminUser();
+    }
+  };
+}
+
+export default PermissionManager;
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 325288fc..cff9c211 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -1,5 +1,7 @@
 // @flow
 
+import type { EventPublisher } from 'spark-protocol';
+import type PermissionManager from './PermissionManager';
 import type {
   Event,
   IWebhookLogger,
@@ -9,7 +11,6 @@ import type {
   Webhook,
   WebhookMutator,
 } from '../types';
-import type { EventPublisher } from 'spark-protocol';
 
 import hogan from 'hogan.js';
 import HttpError from '../lib/HttpError';
@@ -69,15 +70,18 @@ class WebhookManager {
   _errorsCountByWebhookID: Map = new Map();
   _webhookRepository: IWebhookRepository;
   _webhookLogger: IWebhookLogger;
+  _permissonManager: PermissionManager;
 
   constructor(
-    webhookRepository: IWebhookRepository,
     eventPublisher: EventPublisher,
+    permissionManager: PermissionManager,
     webhookLogger: IWebhookLogger,
+    webhookRepository: IWebhookRepository,
   ) {
-    this._webhookRepository = webhookRepository;
     this._eventPublisher = eventPublisher;
+    this._permissonManager = permissionManager;
     this._webhookLogger = webhookLogger;
+    this._webhookRepository = webhookRepository;
 
     (async (): Promise => await this._init())();
   }
@@ -91,23 +95,21 @@ class WebhookManager {
     return webhook;
   };
 
-  deleteByID = async (
-    webhookID: string,
-    userID: string,
-  ): Promise => {
-    const webhook = await this._webhookRepository.getById(webhookID, userID);
+  deleteByID = async (webhookID: string): Promise => {
+    const webhook = await this._permissonManager.getEntityByID('webhook', webhookID);
     if (!webhook) {
       throw new HttpError('no webhook found', 404);
     }
+
     await this._webhookRepository.deleteById(webhookID);
     this._unsubscribeWebhookByID(webhookID);
   };
 
-  getAll = async (userID: string): Promise> =>
-    await this._webhookRepository.getAll(userID);
+  getAll = async (): Promise> =>
+    await this._permissonManager.getAllEntitiesForCurrentUser('webhook');
 
-  getByID = async (webhookID: string, userID: string): Promise => {
-    const webhook = await this._webhookRepository.getById(webhookID, userID);
+  getByID = async (webhookID: string): Promise => {
+    const webhook = await this._permissonManager.getEntityByID('webhook', webhookID);
     if (!webhook) {
       throw new HttpError('no webhook found', 404);
     }
diff --git a/src/repository/BaseMongoDb.js b/src/repository/BaseMongoDb.js
index 3d723724..2be9764d 100644
--- a/src/repository/BaseMongoDb.js
+++ b/src/repository/BaseMongoDb.js
@@ -88,6 +88,9 @@ class BaseMongoDb implements IBaseDatabase {
       await collection.remove(this.__translateQuery({ _id: id })),
   );
 
+  // eslint-disable-next-line no-unused-vars
+  __filterID = ({ id, ...otherProps }: Object): Object => ({ ...otherProps });
+
   __runForCollection = async (
     collectionName: string,
     callback: (collection: Object) => Promise<*>,
@@ -95,7 +98,8 @@ class BaseMongoDb implements IBaseDatabase {
     throw new Error(`Not implemented ${callback.toString()}`);
   };
 
-  __translateQuery = (query: Object): Object => deepToObjectIdCast(query);
+  __translateQuery = (query: Object): Object =>
+    this.__filterID(deepToObjectIdCast(query));
 
   __translateResultItem = (item: ?Object): ?Object => {
     if (!item) {
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index d103e052..09ee5bab 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -9,9 +9,11 @@ import type {
 class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
   _database: IBaseDatabase;
   _collectionName: string = 'deviceAttributes';
+  _permissionManager: Object;
 
-  constructor(database: IBaseDatabase) {
+  constructor(database: IBaseDatabase, permissionManager: Object) {
     this._database = database;
+    this._permissionManager = permissionManager;
   }
 
   create = async (): Promise => {
@@ -21,12 +23,6 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
   deleteById = async (id: string): Promise =>
     await this._database.remove(this._collectionName, id);
 
-  doesUserHaveAccess = async (id: string, userID: string): Promise =>
-    !!(await this._database.findOne(
-      this._collectionName,
-      { _id: id, ownerID: userID },
-    ));
-
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
     return await this._database.find(
@@ -36,13 +32,8 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     );
   };
 
-  getById = async (
-    id: string,
-    userID: ?string = null,
-  ): Promise => {
-    const query = userID ? { _id: id, ownerID: userID } : { _id: id };
-    return await this._database.findOne(this._collectionName, query);
-  };
+  getById = async (id: string): Promise =>
+    await this._database.findOne(this._collectionName, { _id: id });
 
   update = async (model: DeviceAttributes): Promise =>
     await this._database.findAndModify(
@@ -53,5 +44,4 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
       { new: true, upsert: true },
     );
 }
-
 export default DeviceAttributeDatabaseRepository;
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index c4baa190..1f2b1f03 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -6,6 +6,7 @@ import type {
   TokenObject,
   User,
   UserCredentials,
+  UserRole,
 } from '../types';
 
 import PasswordHasher from '../lib/PasswordHasher';
@@ -14,17 +15,23 @@ import HttpError from '../lib/HttpError';
 class UserDatabaseRepository implements IUserRepository {
   _database: IBaseDatabase;
   _collectionName: string = 'users';
+  _currentUser: User;
 
   constructor(database: IBaseDatabase) {
     this._database = database;
   }
 
   // eslint-disable-next-line no-unused-vars
-  create = async (user: $Shape): Promise => {
-    throw new Error('The method is not implemented');
-  };
+  create = async (user: $Shape): Promise =>
+    await this._database.insertOne(
+      this._collectionName,
+      user,
+    );
 
-  createWithCredentials = async (userCredentials: UserCredentials): Promise => {
+  createWithCredentials = async (
+    userCredentials: UserCredentials,
+    userRole: ?UserRole = null,
+  ): Promise => {
     const { username, password } = userCredentials;
 
     const salt = await PasswordHasher.generateSalt();
@@ -32,8 +39,8 @@ class UserDatabaseRepository implements IUserRepository {
     const modelToSave = {
       accessTokens: [],
       created_at: new Date(),
-      created_by: null,
       passwordHash,
+      role: userRole,
       salt,
       username,
     };
@@ -88,6 +95,8 @@ class UserDatabaseRepository implements IUserRepository {
       { username },
     );
 
+  getCurrentUser = (): User => this._currentUser;
+
   isUserNameInUse = async (username: string): Promise =>
     !!(await this.getByUsername(username));
 
@@ -102,10 +111,18 @@ class UserDatabaseRepository implements IUserRepository {
     { new: true },
   );
 
-  // eslint-disable-next-line no-unused-vars
-  update = async (model: User): Promise => {
-    throw new Error('The method is not implemented');
-  };
+  setCurrentUser = (user: User) => {
+    this._currentUser = user;
+  }
+
+  update = async (model: User): Promise =>
+    await this._database.findAndModify(
+      this._collectionName,
+      { _id: model.id },
+      null,
+      { $set: { ...model, timeStamp: new Date() } },
+      { new: true, upsert: true },
+    );
 
   validateLogin = async (username: string, password: string): Promise => {
     try {
diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js
index b8a8740c..541965b8 100644
--- a/src/repository/UserFileRepository.js
+++ b/src/repository/UserFileRepository.js
@@ -1,6 +1,12 @@
 // @flow
 
-import type { IUserRepository, TokenObject, User, UserCredentials } from '../types';
+import type {
+  IUserRepository,
+  TokenObject,
+  User,
+  UserCredentials,
+  UserRole,
+} from '../types';
 
 import uuid from 'uuid';
 import { JSONFileManager, memoizeGet, memoizeSet } from 'spark-protocol';
@@ -9,6 +15,7 @@ import HttpError from '../lib/HttpError';
 
 class UserFileRepository implements IUserRepository {
   _fileManager: JSONFileManager;
+  _currentUser: User;
 
   constructor(path: string) {
     this._fileManager = new JSONFileManager(path);
@@ -16,6 +23,7 @@ class UserFileRepository implements IUserRepository {
 
   createWithCredentials = async (
     userCredentials: UserCredentials,
+    userRole: ?UserRole = null,
   ): Promise => {
     const { username, password } = userCredentials;
 
@@ -24,6 +32,7 @@ class UserFileRepository implements IUserRepository {
     const modelToSave = {
       accessTokens: [],
       passwordHash,
+      role: userRole,
       salt,
       username,
     };
@@ -97,30 +106,7 @@ class UserFileRepository implements IUserRepository {
     );
   }
 
-  @memoizeSet()
-  async update(model: User): Promise {
-    this._fileManager.writeFile(`${model.id}.json`, model);
-    return model;
-  }
-
-  validateLogin = async (username: string, password: string): Promise => {
-    try {
-      const user = await this.getByUsername(username);
-
-      if (!user) {
-        throw new Error('User doesn\'t exist');
-      }
-
-      const hash = await PasswordHasher.hash(password, user.salt);
-      if (hash !== user.passwordHash) {
-        throw new Error('Wrong password');
-      }
-
-      return user;
-    } catch (error) {
-      throw error;
-    }
-  };
+  getCurrentUser = (): User => this._currentUser;
 
   @memoizeGet(['username'])
   async isUserNameInUse(username: string): Promise {
@@ -146,6 +132,35 @@ class UserFileRepository implements IUserRepository {
 
     return await this.update(userToSave);
   }
+
+  setCurrentUser = (user: User) => {
+    this._currentUser = user;
+  }
+
+  @memoizeSet()
+  async update(model: User): Promise {
+    this._fileManager.writeFile(`${model.id}.json`, model);
+    return model;
+  }
+
+  validateLogin = async (username: string, password: string): Promise => {
+    try {
+      const user = await this.getByUsername(username);
+
+      if (!user) {
+        throw new Error('User doesn\'t exist');
+      }
+
+      const hash = await PasswordHasher.hash(password, user.salt);
+      if (hash !== user.passwordHash) {
+        throw new Error('Wrong password');
+      }
+
+      return user;
+    } catch (error) {
+      throw error;
+    }
+  };
 }
 
 export default UserFileRepository;
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index c6cb0bc9..ff272d68 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -31,10 +31,8 @@ class WebhookDatabaseRepository implements IWebhookRepository {
     );
   };
 
-  getById = async (id: string, userID: ?string = null): Promise => {
-    const query = userID ? { _id: id, ownerID: userID } : { _id: id };
-    return await this._database.findOne(this._collectionName, query);
-  };
+  getById = async (id: string): Promise =>
+    await this._database.findOne(this._collectionName, { _id: id });
 
   update = async (): Promise => {
     throw new Error('The method is not implemented');
diff --git a/src/settings.js b/src/settings.js
index 1f1c7e4b..8b9b2a4b 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -24,6 +24,8 @@ import path from 'path';
 /* eslint-disable sorting/sort-object-props */
 export default {
   BUILD_DIRECTORY: path.join(__dirname, '../data/build'),
+  DEFAULT_ADMIN_PASSWORD: 'adminPassword',
+  DEFAULT_ADMIN_USERNAME: '__admin__',
   DEVICE_DIRECTORY: path.join(__dirname, '../data/deviceKeys'),
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: path.join(__dirname, '../data/knownApps'),
diff --git a/src/types.js b/src/types.js
index 41241c80..348c9f78 100644
--- a/src/types.js
+++ b/src/types.js
@@ -101,9 +101,9 @@ export type GrantType =
 export type TokenObject = {
   accessToken: string,
   accessTokenExpiresAt: Date,
-  refreshToken: string,
-  refreshTokenExpiresAt: Date,
-  scope: string,
+  refreshToken?: string,
+  refreshTokenExpiresAt?: Date,
+  scope?: string,
 };
 
 export type User = {
@@ -111,6 +111,7 @@ export type User = {
   created_at: Date,
   id: string,
   passwordHash: string,
+  role: ?UserRole,
   salt: string,
   username: string,
 };
@@ -120,6 +121,10 @@ export type UserCredentials = {
   password: string,
 };
 
+export type UserRole = 'administrator';
+
+export type ProtectedEntityName = 'deviceAttributes' | 'webhook';
+
 export type Device = DeviceAttributes & {
   connected: boolean,
   functions?: ?Array,
@@ -155,6 +160,8 @@ export type Settings = {
     PATH: ?string,
     URL: ?string,
   },
+  DEFAULT_ADMIN_PASSWORD: string,
+  DEFAULT_ADMIN_USERNAME: string,
   DEVICE_DIRECTORY: string,
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: boolean,
   EXPRESS_SERVER_CONFIG: {
@@ -184,21 +191,20 @@ export type DeviceAttributeRepository = Repository & {
 export type DeviceManager = {
   callFunction(
     deviceID: string,
-    userID: string,
     functionName: string,
     functionArguments: {[key: string]: string},
   ): Promise<*>,
   claimDevice(deviceID: string, userID: string): Promise,
   flashBinary(deviceID: string, files: File): Promise<*>,
-  flashKnownApp(deviceID: string, userID: string, app: string): Promise<*>,
-  getAll(userID: string): Promise>,
-  getByID(deviceID: string, userID: string): Promise,
-  getDetailsByID(deviceID: string, userID: string): Promise<*>,
-  getVariableValue(deviceID: string, userID: string, varName: string): Promise,
-  provision(deviceID: string, userID: string, publicKey: string): Promise<*>,
-  raiseYourHand(deviceID: string, userID: string, shouldShowSignal: boolean): Promise,
-  renameDevice(deviceID: string, userID: string, name: string): Promise,
-  unclaimDevice(deviceID: string, userID: string): Promise,
+  flashKnownApp(deviceID: string, app: string): Promise<*>,
+  getAll(): Promise>,
+  getByID(deviceID: string): Promise,
+  getDetailsByID(deviceID: string): Promise<*>,
+  getVariableValue(deviceID: string, varName: string): Promise,
+  provision(deviceID: string, publicKey: string): Promise<*>,
+  raiseYourHand(deviceID: string, shouldShowSignal: boolean): Promise,
+  renameDevice(deviceID: string, name: string): Promise,
+  unclaimDevice(deviceID: string): Promise,
 };
 
 export type RequestOptions = {
@@ -236,9 +242,7 @@ export interface IBaseRepository {
 
 export interface IWebhookRepository extends IBaseRepository {}
 
-export interface IDeviceAttributeRepository extends IBaseRepository {
-  doesUserHaveAccess(id: string, userID: string): Promise;
-}
+export interface IDeviceAttributeRepository extends IBaseRepository {}
 
 export interface IDeviceKeyRepository extends IBaseRepository {}
 
@@ -247,8 +251,10 @@ export interface IUserRepository extends IBaseRepository {
   deleteAccessToken(userID: string, accessToken: string): Promise;
   getByAccessToken(accessToken: string): Promise;
   getByUsername(username: string): Promise;
+  getCurrentUser(): User;
   isUserNameInUse(username: string): Promise;
   saveAccessToken(userID: string, tokenObject: TokenObject): Promise;
+  setCurrentUser(user: User): void;
   validateLogin(username: string, password: string): Promise;
 }
 
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index 4827f38c..18ec910c 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -172,7 +172,7 @@ test.serial('should unclaim device', async t => {
     .get(`/v1/devices/${DEVICE_ID}`)
     .query({ access_token: userToken });
 
-  t.is(getDeviceResponse.status, 404);
+  t.is(getDeviceResponse.status, 403);
 });
 
 test.serial('should claim device', async t => {
diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js
index e6561cd2..2ce6c02f 100644
--- a/test/WebhookManager.test.js
+++ b/test/WebhookManager.test.js
@@ -45,7 +45,7 @@ test(
   'should run basic request',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = 'testData';
     const event = getEvent(data);
     const defaultRequestData = getDefaultRequestData(event);
@@ -72,7 +72,7 @@ test(
   'should run basic request without default data',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const webhook = {
       ...WEBHOOK_BASE,
       noDefaults: true,
@@ -102,7 +102,7 @@ test(
   'should compile json body',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = '{"t":"123"}';
     const event = getEvent(data);
     const webhook = {
@@ -138,7 +138,7 @@ test(
   'should compile form body',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = '{"t":"123","g": "foo bar"}';
     const event = getEvent(data);
     const webhook = {
@@ -179,7 +179,7 @@ test(
   'should compile request auth header',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = `{"username":"123","password": "foobar"}`;
     const event = getEvent(data);
     const webhook = {
@@ -218,7 +218,7 @@ test(
   'should compile request headers',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = `{"t":"123","g": "foobar"}`;
     const event = getEvent(data);
     const webhook = {
@@ -257,7 +257,7 @@ test(
   'should compile request url',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = '{"t":"123","g": "foobar"}';
     const event = getEvent(data);
     const webhook = {
@@ -288,7 +288,7 @@ test(
   'should compile request query',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = '{"t":"123","g": "foobar"}';
     const event = getEvent(data);
     const webhook = {
@@ -325,7 +325,7 @@ test(
   'should compile requestType',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const data = `{"t":"123","requestType": "post"}`;
     const event = getEvent(data);
     const webhook = {
@@ -355,7 +355,7 @@ test(
   'should throw an error if wrong requestType is provided',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const testRequestType = 'wrongRequestType';
     const data = `{"t":"123","requestType": "${testRequestType}"}`;
     const event = getEvent(data);
@@ -379,7 +379,7 @@ test(
   'should publish sent event',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const event = getEvent();
 
     t.context.eventPublisher.publish = sinon.spy(({
@@ -400,7 +400,7 @@ test(
   'should publish default topic',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const event = getEvent();
     manager._callWebhook = sinon.stub().returns('data');
 
@@ -422,7 +422,7 @@ test(
   'should compile response topic and publish',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const event = getEvent();
     const webhook = {
       ...WEBHOOK_BASE,
@@ -448,7 +448,7 @@ test(
   'should compile response body and publish',
   async t => {
     const manager =
-      new WebhookManager(t.context.repository, t.context.eventPublisher);
+      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
     const event = getEvent();
     const webhook = {
       ...WEBHOOK_BASE,
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 5b0a5a99..92fae39c 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -6,6 +6,8 @@ import path from 'path';
 export default {
   BUILD_DIRECTORY: path.join(__dirname, '../__test_data__/build'),
   CUSTOM_FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__'),
+  DEFAULT_ADMIN_PASSWORD: 'adminPassword',
+  DEFAULT_ADMIN_USERNAME: '__admin__',
   DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'),
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'),

From cd6740e5b78a312d21ae2fff8624d23395ac7574 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 6 Jun 2017 03:23:49 +0200
Subject: [PATCH 385/504] get token with Ouath hacky code.

---
 package.json                      |  2 +-
 src/OAuthModel.js                 |  4 --
 src/RouteConfig.js                |  2 +
 src/defaultBindings.js            |  1 +
 src/lib/utils.js                  |  9 ----
 src/managers/PermissionManager.js | 84 ++++++++++++++++++++-----------
 src/oauthClients.json             | 25 +++++----
 7 files changed, 75 insertions(+), 52 deletions(-)
 delete mode 100644 src/lib/utils.js

diff --git a/package.json b/package.json
index f7cabb30..43ca84b2 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
     "chalk": "^1.1.3",
     "constitute": "^1.6.2",
     "express": "^4.14.0",
-    "express-oauth-server": "^2.0.0-b1",
+    "express-oauth-server": "^2.0.0-b3",
     "hogan.js": "^3.0.2",
     "lodash": "^4.17.4",
     "mkdirp": "^0.5.1",
diff --git a/src/OAuthModel.js b/src/OAuthModel.js
index 93caa3b7..bfc05431 100644
--- a/src/OAuthModel.js
+++ b/src/OAuthModel.js
@@ -10,7 +10,6 @@ import type {
 } from './types';
 
 const OAUTH_CLIENTS = oauthClients;
-import { generateRandomToken } from './lib/utils';
 
 class OauthModel {
   _userRepository: UserRepository;
@@ -19,9 +18,6 @@ class OauthModel {
     this._userRepository = userRepository;
   }
 
-  generateAccessToken = async (): Promise =>
-    await generateRandomToken();
-
   getAccessToken = async (bearerToken: string): ?Object => {
     const user = await this._userRepository.getByAccessToken(bearerToken);
     if (!user) {
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 7bf69805..042cdea8 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -69,6 +69,8 @@ export default (
     model: new OAuthModel(container.constitute('UserRepository')),
   });
 
+  container.bindValue('OauthServer', oauth);
+
   const filesMiddleware = (allowedUploads: ?Array<{
     maxCount: number,
     name: string,
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index d479a314..6b7b8975 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -84,6 +84,7 @@ export default (container: Container, newSettings: Settings) => {
       'DeviceAttributeRepository',
       'UserRepository',
       'WebhookRepository',
+      'OauthServer',
     ],
   );
   container.bindClass(
diff --git a/src/lib/utils.js b/src/lib/utils.js
deleted file mode 100644
index 8637dd3a..00000000
--- a/src/lib/utils.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// @flow
-
-import crypto from 'crypto';
-import { promisify } from './promisify';
-
-export const generateRandomToken = async (): Promise => {
-  const randomBytesBuffer = await promisify(crypto, 'randomBytes', 256);
-  return crypto.createHash('sha1').update(randomBytesBuffer).digest('hex');
-};
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index ffbb0869..2ab39e3d 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -6,25 +6,28 @@ import type {
   IWebhookRepository,
   ProtectedEntityName,
 } from '../types';
-import HttpError from '../lib/HttpError';
+
 import nullthrows from 'nullthrows';
+import { Request, Response } from 'oauth2-server';
+import HttpError from '../lib/HttpError';
+import logger from '../lib/logger';
 import settings from '../settings';
-import { generateRandomToken } from '../lib/utils';
-
-const MAX_TIMESTAMP = 8640000000000000;
 
 class PermissionManager {
   _userRepository: IUserRepository;
   _repositoriesByEntityName: Map = new Map();
+  _oauthServer: Object;
 
   constructor(
     deviceAttributeRepository: IDeviceAttributeRepository,
     userRepository: IUserRepository,
     webhookRepository: IWebhookRepository,
+    oauthServer: Object,
   ) {
     this._userRepository = userRepository;
     this._repositoriesByEntityName.set('deviceAttributes', deviceAttributeRepository);
     this._repositoriesByEntityName.set('webhook', webhookRepository);
+    this._oauthServer = oauthServer;
 
     (async (): Promise => await this._init())();
   }
@@ -38,7 +41,7 @@ class PermissionManager {
     const currentUser = this._userRepository.getCurrentUser();
     return await nullthrows(this._repositoriesByEntityName.get(entityName))
       .getAll(currentUser.id);
-  }
+  };
 
   getEntityByID = async (
     entityName: ProtectedEntityName,
@@ -52,43 +55,68 @@ class PermissionManager {
       throw new HttpError('User doesn\'t have access', 403);
     }
     return entity;
-  }
+  };
 
   _createDefaultAdminUser = async (): Promise => {
-    const defaultAdminUser = await this._userRepository.createWithCredentials(
-      {
-        password: settings.DEFAULT_ADMIN_PASSWORD,
-        username: settings.DEFAULT_ADMIN_USERNAME,
-      },
-      'administrator',
-    );
-
-    const accessTokenObject = {
-      accessToken: await generateRandomToken(),
-      accessTokenExpiresAt: new Date(MAX_TIMESTAMP),
-    };
+    try {
+      await this._userRepository.createWithCredentials(
+        {
+          password: settings.DEFAULT_ADMIN_PASSWORD,
+          username: settings.DEFAULT_ADMIN_USERNAME,
+        },
+        'administrator',
+      );
 
-    const userToUpdate = {
-      ...defaultAdminUser,
-      accessTokens: [accessTokenObject],
-    };
+      const token = await this._generateAdminToken();
 
-    await this._userRepository.update(userToUpdate);
-    console.log(
-      `New default admin user created with token ${accessTokenObject.accessToken}`,
-    );
+      logger.info(
+        `New default admin user created with token: ${token}`,
+      );
+    } catch (error) {
+      logger.error(`Error during default admin user creating: ${error}`);
+    }
   };
 
   _doesUserHaveAccess = (ownerID: ?string): boolean => {
     const currentUser = this._userRepository.getCurrentUser();
     return currentUser.role === 'administrator' || currentUser.id === ownerID;
-  }
+  };
+
+  _generateAdminToken = async (): Promise => {
+    const request = new Request({
+      body: {
+        client_id: 'spark-server',
+        client_secret: 'spark-server',
+        grant_type: 'password',
+        password: settings.DEFAULT_ADMIN_PASSWORD,
+        username: settings.DEFAULT_ADMIN_USERNAME,
+      },
+      headers: {
+        'content-type': 'application/x-www-form-urlencoded',
+        'transfer-encoding': 'chunked',
+      },
+      method: 'POST',
+      query: {},
+    });
+
+    const response = new Response({ body: {}, headers: {} });
+
+    const tokenPayload = await this._oauthServer.server.token(
+      request,
+      response,
+      // oauth server doesn't allow us to use infinite access token
+      // so we pass some big value here
+      { accessTokenLifetime: 9999999999 },
+    );
+
+    return tokenPayload.accessToken;
+  };
 
   _init = async (): Promise => {
     const defaultAdminUser =
       await this._userRepository.getByUsername(settings.DEFAULT_ADMIN_USERNAME);
     if (defaultAdminUser) {
-      console.log(
+      logger.info(
         `Default admin accessToken: ${defaultAdminUser.accessTokens[0].accessToken}`,
       );
     } else {
diff --git a/src/oauthClients.json b/src/oauthClients.json
index 048381d1..c5329c20 100644
--- a/src/oauthClients.json
+++ b/src/oauthClients.json
@@ -1,10 +1,15 @@
-[{
-  "clientId": "CLI2",
-  "clientSecret": "client_secret_here",
-  "grants": ["password"]
-},
-{
-  "clientId": "particle-collider",
-  "clientSecret": "particle-collider",
-  "grants": ["password"]
-}]
+[{
+  "clientId": "CLI2",
+  "clientSecret": "client_secret_here",
+  "grants": ["password"]
+},
+{
+  "clientId": "particle-collider",
+  "clientSecret": "particle-collider",
+  "grants": ["password"]
+},
+{
+  "clientId": "spark-server",
+  "clientSecret": "spark-server",
+  "grants": ["password"]
+}]

From 10ebd62a7d7aa627a57d96e26825e3cd641af19d Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 6 Jun 2017 18:10:23 +0200
Subject: [PATCH 386/504] move oauthServer initialization from RouteConfig to
 defaultBindings

---
 src/RouteConfig.js     | 12 ++----------
 src/defaultBindings.js | 25 ++++++++++++++++++++++++-
 2 files changed, 26 insertions(+), 11 deletions(-)

diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 042cdea8..97875188 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -10,10 +10,8 @@ import type {
 import type { Container } from 'constitute';
 import type { Settings } from './types';
 
-import OAuthServer from 'express-oauth-server';
 import nullthrows from 'nullthrows';
 import multer from 'multer';
-import OAuthModel from './OAuthModel';
 import HttpError from './lib/HttpError';
 
 const maybe = (middleware: Middleware, condition: boolean): Middleware =>
@@ -63,14 +61,6 @@ export default (
   controllers: Array,
   settings: Settings,
 ) => {
-  const oauth = new OAuthServer({
-    accessTokenLifetime: settings.ACCESS_TOKEN_LIFETIME,
-    allowBearerTokensInQueryString: true,
-    model: new OAuthModel(container.constitute('UserRepository')),
-  });
-
-  container.bindValue('OauthServer', oauth);
-
   const filesMiddleware = (allowedUploads: ?Array<{
     maxCount: number,
     name: string,
@@ -78,6 +68,8 @@ export default (
     ? multer().fields(allowedUploads)
     : multer().any();
 
+  const oauth = container.constitute('OAuthServer');
+
   app.post(settings.LOGIN_ROUTE, oauth.token());
 
   controllers.forEach((controllerName: string) => {
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 6b7b8975..c7bdf90f 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -3,6 +3,8 @@
 import type { Container } from 'constitute';
 import type { Settings } from './types';
 
+import OAuthServer from 'express-oauth-server';
+import OAuthModel from './OAuthModel';
 import { defaultBindings } from 'spark-protocol';
 import DeviceClaimsController from './controllers/DeviceClaimsController';
 import DevicesController from './controllers/DevicesController';
@@ -44,6 +46,27 @@ export default (container: Container, newSettings: Settings) => {
   container.bindValue('SERVER_KEYS_DIRECTORY', settings.SERVER_KEYS_DIRECTORY);
   container.bindValue('USERS_DIRECTORY', settings.USERS_DIRECTORY);
   container.bindValue('WEBHOOKS_DIRECTORY', settings.WEBHOOKS_DIRECTORY);
+  container.bindMethod(
+    'OAUTH_SETTINGS',
+    (oauthModel: OAuthModel): Object => ({
+      accessTokenLifetime: settings.ACCESS_TOKEN_LIFETIME,
+      allowBearerTokensInQueryString: true,
+      model: oauthModel,
+    }),
+    ['OAuthModel'],
+  );
+
+  container.bindClass(
+    'OAuthModel',
+    OAuthModel,
+    ['UserRepository'],
+  );
+
+  container.bindClass(
+    'OAuthServer',
+    OAuthServer,
+    ['OAUTH_SETTINGS'],
+  );
 
   container.bindClass(
     'Database',
@@ -84,7 +107,7 @@ export default (container: Container, newSettings: Settings) => {
       'DeviceAttributeRepository',
       'UserRepository',
       'WebhookRepository',
-      'OauthServer',
+      'OAuthServer',
     ],
   );
   container.bindClass(

From cbbf74982ffe400351db74926bea2c799de91af1 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 6 Jun 2017 18:47:51 +0200
Subject: [PATCH 387/504] rebuild

---
 dist/RouteConfig.js                           |  24 +-
 dist/controllers/DeviceClaimsController.js    |   2 +-
 dist/controllers/DevicesController.js         |  18 +-
 dist/controllers/WebhooksController.js        |  26 +-
 dist/defaultBindings.js                       |  30 ++-
 dist/managers/DeviceManager.js                | 245 ++++++++----------
 dist/managers/WebhookManager.js               |  55 ++--
 dist/oauthClients.json                        |  25 +-
 dist/repository/BaseMongoDb.js                |  17 +-
 .../DeviceAttributeDatabaseRepository.js      |  81 ++----
 dist/repository/UserDatabaseRepository.js     |  52 ++--
 dist/repository/UserFileRepository.js         | 158 +++++------
 dist/repository/WebhookDatabaseRepository.js  |  11 +-
 dist/settings.js                              |   2 +
 14 files changed, 366 insertions(+), 380 deletions(-)

diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js
index a3e7194c..d8ed0af9 100644
--- a/dist/RouteConfig.js
+++ b/dist/RouteConfig.js
@@ -36,10 +36,6 @@ var _getOwnPropertyNames = require('babel-runtime/core-js/object/get-own-propert
 
 var _getOwnPropertyNames2 = _interopRequireDefault(_getOwnPropertyNames);
 
-var _expressOauthServer = require('express-oauth-server');
-
-var _expressOauthServer2 = _interopRequireDefault(_expressOauthServer);
-
 var _nullthrows = require('nullthrows');
 
 var _nullthrows2 = _interopRequireDefault(_nullthrows);
@@ -48,10 +44,6 @@ var _multer = require('multer');
 
 var _multer2 = _interopRequireDefault(_multer);
 
-var _OAuthModel = require('./OAuthModel');
-
-var _OAuthModel2 = _interopRequireDefault(_OAuthModel);
-
 var _HttpError = require('./lib/HttpError');
 
 var _HttpError2 = _interopRequireDefault(_HttpError);
@@ -68,13 +60,15 @@ var maybe = function maybe(middleware, condition) {
   };
 };
 
-var injectUserMiddleware = function injectUserMiddleware() {
+var injectUserMiddleware = function injectUserMiddleware(container) {
   return function (request, response, next) {
     var oauthInfo = response.locals.oauth;
     if (oauthInfo) {
       var token = oauthInfo.token;
+      var user = token && token.user;
       // eslint-disable-next-line no-param-reassign
-      request.user = token && token.user;
+      request.user = user;
+      container.constitute('UserRepository').setCurrentUser(user);
     }
     next();
   };
@@ -98,17 +92,13 @@ var serverSentEventsMiddleware = function serverSentEventsMiddleware() {
 };
 
 exports.default = function (app, container, controllers, settings) {
-  var oauth = new _expressOauthServer2.default({
-    accessTokenLifetime: settings.ACCESS_TOKEN_LIFETIME,
-    allowBearerTokensInQueryString: true,
-    model: new _OAuthModel2.default(container.constitute('UserRepository'))
-  });
-
   var filesMiddleware = function filesMiddleware() {
     var allowedUploads = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
     return (0, _nullthrows2.default)(allowedUploads).length ? (0, _multer2.default)().fields(allowedUploads) : (0, _multer2.default)().any();
   };
 
+  var oauth = container.constitute('OAuthServer');
+
   app.post(settings.LOGIN_ROUTE, oauth.token());
 
   controllers.forEach(function (controllerName) {
@@ -125,7 +115,7 @@ exports.default = function (app, container, controllers, settings) {
       if (!httpVerb) {
         return;
       }
-      app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(), maybe(filesMiddleware(allowedUploads), allowedUploads), function () {
+      app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(container), maybe(filesMiddleware(allowedUploads), allowedUploads), function () {
         var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) {
           var argumentNames, values, controllerInstance, _request$body, access_token, body, functionResult, result, httpError;
 
diff --git a/dist/controllers/DeviceClaimsController.js b/dist/controllers/DeviceClaimsController.js
index 1789c62b..bf93e676 100644
--- a/dist/controllers/DeviceClaimsController.js
+++ b/dist/controllers/DeviceClaimsController.js
@@ -105,7 +105,7 @@ var DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
               case 0:
                 claimCode = this._claimCodeManager.createClaimCode(this.user.id);
                 _context.next = 3;
-                return this._deviceManager.getAll(this.user.id);
+                return this._deviceManager.getAll();
 
               case 3:
                 devices = _context.sent;
diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js
index 43223ede..79d17924 100644
--- a/dist/controllers/DevicesController.js
+++ b/dist/controllers/DevicesController.js
@@ -222,7 +222,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
             switch (_context4.prev = _context4.next) {
               case 0:
                 _context4.next = 2;
-                return this._deviceManager.unclaimDevice(deviceID, this.user.id);
+                return this._deviceManager.unclaimDevice(deviceID);
 
               case 2:
                 return _context4.abrupt('return', this.ok({ ok: true }));
@@ -252,7 +252,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
               case 0:
                 _context5.prev = 0;
                 _context5.next = 3;
-                return this._deviceManager.getAll(this.user.id);
+                return this._deviceManager.getAll();
 
               case 3:
                 devices = _context5.sent;
@@ -289,7 +289,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
             switch (_context6.prev = _context6.next) {
               case 0:
                 _context6.next = 2;
-                return this._deviceManager.getDetailsByID(deviceID, this.user.id);
+                return this._deviceManager.getDetailsByID(deviceID);
 
               case 2:
                 device = _context6.sent;
@@ -320,7 +320,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
               case 0:
                 _context7.prev = 0;
                 _context7.next = 3;
-                return this._deviceManager.getVariableValue(deviceID, this.user.id, varName);
+                return this._deviceManager.getVariableValue(deviceID, varName);
 
               case 3:
                 varValue = _context7.sent;
@@ -371,7 +371,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
                 }
 
                 _context8.next = 3;
-                return this._deviceManager.renameDevice(deviceID, this.user.id, postBody.name);
+                return this._deviceManager.renameDevice(deviceID, postBody.name);
 
               case 3:
                 updatedAttributes = _context8.sent;
@@ -384,7 +384,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
                 }
 
                 _context8.next = 8;
-                return this._deviceManager.flashKnownApp(deviceID, this.user.id, postBody.app_id);
+                return this._deviceManager.flashKnownApp(deviceID, postBody.app_id);
 
               case 8:
                 flashStatus = _context8.sent;
@@ -428,7 +428,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
 
               case 21:
                 _context8.next = 23;
-                return this._deviceManager.raiseYourHand(deviceID, this.user.id, !!parseInt(postBody.signal, 10));
+                return this._deviceManager.raiseYourHand(deviceID, !!parseInt(postBody.signal, 10));
 
               case 23:
                 return _context8.abrupt('return', this.ok({ id: deviceID, ok: true }));
@@ -461,12 +461,12 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
               case 0:
                 _context9.prev = 0;
                 _context9.next = 3;
-                return this._deviceManager.callFunction(deviceID, this.user.id, functionName, postBody);
+                return this._deviceManager.callFunction(deviceID, functionName, postBody);
 
               case 3:
                 result = _context9.sent;
                 _context9.next = 6;
-                return this._deviceManager.getByID(deviceID, this.user.id);
+                return this._deviceManager.getByID(deviceID);
 
               case 6:
                 device = _context9.sent;
diff --git a/dist/controllers/WebhooksController.js b/dist/controllers/WebhooksController.js
index ebcc9292..d04540cc 100644
--- a/dist/controllers/WebhooksController.js
+++ b/dist/controllers/WebhooksController.js
@@ -103,7 +103,7 @@ var validateWebhookMutator = function validateWebhookMutator(webhookMutator) {
   return null;
 };
 
-var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookId'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookId'), (_class = function (_Controller) {
+var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/webhooks'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/webhooks/:webhookID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/webhooks'), _dec7 = (0, _httpVerb2.default)('delete'), _dec8 = (0, _route2.default)('/v1/webhooks/:webhookID'), (_class = function (_Controller) {
   (0, _inherits3.default)(WebhooksController, _Controller);
 
   function WebhooksController(webhookManager) {
@@ -125,7 +125,7 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
               case 0:
                 _context.t0 = this;
                 _context.next = 3;
-                return this._webhookManager.getAll(this.user.id);
+                return this._webhookManager.getAll();
 
               case 3:
                 _context.t1 = _context.sent;
@@ -146,16 +146,16 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
       return getAll;
     }()
   }, {
-    key: 'getById',
+    key: 'getByID',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookId) {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
                 _context2.t0 = this;
                 _context2.next = 3;
-                return this._webhookManager.getByID(webhookId, this.user.id);
+                return this._webhookManager.getByID(webhookID);
 
               case 3:
                 _context2.t1 = _context2.sent;
@@ -169,11 +169,11 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee2, this);
       }));
 
-      function getById(_x) {
+      function getByID(_x) {
         return _ref2.apply(this, arguments);
       }
 
-      return getById;
+      return getByID;
     }()
   }, {
     key: 'create',
@@ -224,15 +224,15 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
       return create;
     }()
   }, {
-    key: 'deleteById',
+    key: 'deleteByID',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookId) {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
                 _context4.next = 2;
-                return this._webhookManager.deleteByID(webhookId, this.user.id);
+                return this._webhookManager.deleteByID(webhookID);
 
               case 2:
                 return _context4.abrupt('return', this.ok({ ok: true }));
@@ -245,13 +245,13 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee4, this);
       }));
 
-      function deleteById(_x3) {
+      function deleteByID(_x3) {
         return _ref4.apply(this, arguments);
       }
 
-      return deleteById;
+      return deleteByID;
     }()
   }]);
   return WebhooksController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByID', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'create', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype)), _class));
 exports.default = WebhooksController;
\ No newline at end of file
diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index a3854e21..e1c4c11b 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -8,6 +8,14 @@ var _keys = require('babel-runtime/core-js/object/keys');
 
 var _keys2 = _interopRequireDefault(_keys);
 
+var _expressOauthServer = require('express-oauth-server');
+
+var _expressOauthServer2 = _interopRequireDefault(_expressOauthServer);
+
+var _OAuthModel = require('./OAuthModel');
+
+var _OAuthModel2 = _interopRequireDefault(_OAuthModel);
+
 var _sparkProtocol = require('spark-protocol');
 
 var _DeviceClaimsController = require('./controllers/DeviceClaimsController');
@@ -58,6 +66,10 @@ var _EventManager = require('./managers/EventManager');
 
 var _EventManager2 = _interopRequireDefault(_EventManager);
 
+var _PermissionManager = require('./managers/PermissionManager');
+
+var _PermissionManager2 = _interopRequireDefault(_PermissionManager);
+
 var _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepository');
 
 var _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository);
@@ -106,16 +118,28 @@ exports.default = function (container, newSettings) {
   container.bindValue('SERVER_KEYS_DIRECTORY', _settings2.default.SERVER_KEYS_DIRECTORY);
   container.bindValue('USERS_DIRECTORY', _settings2.default.USERS_DIRECTORY);
   container.bindValue('WEBHOOKS_DIRECTORY', _settings2.default.WEBHOOKS_DIRECTORY);
+  container.bindMethod('OAUTH_SETTINGS', function (oauthModel) {
+    return {
+      accessTokenLifetime: _settings2.default.ACCESS_TOKEN_LIFETIME,
+      allowBearerTokensInQueryString: true,
+      model: oauthModel
+    };
+  }, ['OAuthModel']);
+
+  container.bindClass('OAuthModel', _OAuthModel2.default, ['UserRepository']);
+
+  container.bindClass('OAuthServer', _expressOauthServer2.default, ['OAUTH_SETTINGS']);
 
   container.bindClass('Database', _TingoDb2.default, ['DATABASE_PATH', 'DATABASE_OPTIONS']);
 
   // lib
-  container.bindClass('IWebhookLogger', _WebhookLogger2.default, []);
+  container.bindClass('WebhookLogger', _WebhookLogger2.default, []);
 
   // controllers
   container.bindClass('DeviceClaimsController', _DeviceClaimsController2.default, ['DeviceManager', 'ClaimCodeManager']);
   container.bindClass('DevicesController', _DevicesController2.default, ['DeviceManager']);
   container.bindClass('EventsController', _EventsController2.default, ['EventManager']);
+  container.bindClass('PermissionManager', _PermissionManager2.default, ['DeviceAttributeRepository', 'UserRepository', 'WebhookRepository', 'OAuthServer']);
   container.bindClass('OauthClientsController', _OauthClientsController2.default, []);
   container.bindClass('ProductsController', _ProductsController2.default, []);
   container.bindClass('ProvisioningController', _ProvisioningController2.default, ['DeviceManager']);
@@ -123,9 +147,9 @@ exports.default = function (container, newSettings) {
   container.bindClass('WebhooksController', _WebhooksController2.default, ['WebhookManager']);
 
   // managers
-  container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'DeviceServer']);
+  container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'DeviceServer', 'PermissionManager']);
   container.bindClass('EventManager', _EventManager2.default, ['EventPublisher']);
-  container.bindClass('WebhookManager', _WebhookManager2.default, ['WebhookRepository', 'EventPublisher', 'IWebhookLogger']);
+  container.bindClass('WebhookManager', _WebhookManager2.default, ['EventPublisher', 'PermissionManager', 'WebhookLogger', 'WebhookRepository']);
 
   // Repositories
   container.bindClass('DeviceAttributeRepository', _DeviceAttributeDatabaseRepository2.default, ['Database']);
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 2fedd25b..866afbe3 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -38,7 +38,7 @@ var _HttpError2 = _interopRequireDefault(_HttpError);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer) {
+var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer, permissionManager) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, DeviceManager);
@@ -103,14 +103,14 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.unclaimDevice = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID, userID) {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
       var deviceAttributes, attributesToSave;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._deviceAttributeRepository.getById(deviceID, userID);
+              return _this._permissionManager.getEntityByID('deviceAttributes', deviceID);
 
             case 2:
               deviceAttributes = _context2.sent;
@@ -140,20 +140,20 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee2, _this);
     }));
 
-    return function (_x3, _x4) {
+    return function (_x3) {
       return _ref2.apply(this, arguments);
     };
   }();
 
   this.getByID = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, userID) {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID) {
       var attributes, device;
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
             case 0:
               _context3.next = 2;
-              return _this._deviceAttributeRepository.getById(deviceID, userID);
+              return _this._permissionManager.getEntityByID('deviceAttributes', deviceID);
 
             case 2:
               attributes = _context3.sent;
@@ -181,13 +181,13 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee3, _this);
     }));
 
-    return function (_x5, _x6) {
+    return function (_x4) {
       return _ref3.apply(this, arguments);
     };
   }();
 
   this.getDetailsByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, userID) {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
       var device, _ref5, _ref6, attributes, description;
 
       return _regenerator2.default.wrap(function _callee4$(_context4) {
@@ -196,7 +196,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
             case 0:
               device = _this._deviceServer.getDevice(deviceID);
               _context4.next = 3;
-              return _promise2.default.all([_this._deviceAttributeRepository.getById(deviceID, userID), device && device.getDescription()]);
+              return _promise2.default.all([_this._permissionManager.getEntityByID('deviceAttributes', deviceID), device && device.getDescription()]);
 
             case 3:
               _ref5 = _context4.sent;
@@ -228,102 +228,86 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee4, _this);
     }));
 
-    return function (_x7, _x8) {
+    return function (_x5) {
       return _ref4.apply(this, arguments);
     };
   }();
 
-  this.getAll = function () {
-    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(userID) {
-      var devicesAttributes, devicePromises;
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
-        while (1) {
-          switch (_context6.prev = _context6.next) {
-            case 0:
-              _context6.next = 2;
-              return _this._deviceAttributeRepository.getAll(userID);
-
-            case 2:
-              devicesAttributes = _context6.sent;
-              devicePromises = devicesAttributes.map(function () {
-                var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) {
-                  var device;
-                  return _regenerator2.default.wrap(function _callee5$(_context5) {
-                    while (1) {
-                      switch (_context5.prev = _context5.next) {
-                        case 0:
-                          device = _this._deviceServer.getDevice(attributes.deviceID);
-                          return _context5.abrupt('return', (0, _extends3.default)({}, attributes, {
-                            connected: device && device.ping().connected || false,
-                            lastFlashedAppName: null,
-                            lastHeard: device && device.ping().lastPing || attributes.lastHeard
-                          }));
-
-                        case 2:
-                        case 'end':
-                          return _context5.stop();
-                      }
+  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+    var devicesAttributes, devicePromises;
+    return _regenerator2.default.wrap(function _callee6$(_context6) {
+      while (1) {
+        switch (_context6.prev = _context6.next) {
+          case 0:
+            _context6.next = 2;
+            return _this._permissionManager.getAllEntitiesForCurrentUser('deviceAttributes');
+
+          case 2:
+            devicesAttributes = _context6.sent;
+            devicePromises = devicesAttributes.map(function () {
+              var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) {
+                var device;
+                return _regenerator2.default.wrap(function _callee5$(_context5) {
+                  while (1) {
+                    switch (_context5.prev = _context5.next) {
+                      case 0:
+                        device = _this._deviceServer.getDevice(attributes.deviceID);
+                        return _context5.abrupt('return', (0, _extends3.default)({}, attributes, {
+                          connected: device && device.ping().connected || false,
+                          lastFlashedAppName: null,
+                          lastHeard: device && device.ping().lastPing || attributes.lastHeard
+                        }));
+
+                      case 2:
+                      case 'end':
+                        return _context5.stop();
                     }
-                  }, _callee5, _this);
-                }));
+                  }
+                }, _callee5, _this);
+              }));
 
-                return function (_x10) {
-                  return _ref8.apply(this, arguments);
-                };
-              }());
-              return _context6.abrupt('return', _promise2.default.all(devicePromises));
+              return function (_x6) {
+                return _ref8.apply(this, arguments);
+              };
+            }());
+            return _context6.abrupt('return', _promise2.default.all(devicePromises));
 
-            case 5:
-            case 'end':
-              return _context6.stop();
-          }
+          case 5:
+          case 'end':
+            return _context6.stop();
         }
-      }, _callee6, _this);
-    }));
-
-    return function (_x9) {
-      return _ref7.apply(this, arguments);
-    };
-  }();
+      }
+    }, _callee6, _this);
+  }));
 
   this.callFunction = function () {
-    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, userID, functionName, functionArguments) {
-      var doesUserHaveAccess, device;
+    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, functionName, functionArguments) {
+      var device;
       return _regenerator2.default.wrap(function _callee7$(_context7) {
         while (1) {
           switch (_context7.prev = _context7.next) {
             case 0:
               _context7.next = 2;
-              return _this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID);
+              return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              doesUserHaveAccess = _context7.sent;
-
-              if (doesUserHaveAccess) {
-                _context7.next = 5;
-                break;
-              }
-
-              throw new _HttpError2.default('No device found', 404);
-
-            case 5:
               device = _this._deviceServer.getDevice(deviceID);
 
               if (device) {
-                _context7.next = 8;
+                _context7.next = 5;
                 break;
               }
 
               throw new _HttpError2.default('Could not get device for ID', 404);
 
-            case 8:
-              _context7.next = 10;
+            case 5:
+              _context7.next = 7;
               return device.callFunction(functionName, functionArguments);
 
-            case 10:
+            case 7:
               return _context7.abrupt('return', _context7.sent);
 
-            case 11:
+            case 8:
             case 'end':
               return _context7.stop();
           }
@@ -331,49 +315,39 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee7, _this);
     }));
 
-    return function (_x11, _x12, _x13, _x14) {
+    return function (_x7, _x8, _x9) {
       return _ref9.apply(this, arguments);
     };
   }();
 
   this.getVariableValue = function () {
-    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, userID, varName) {
-      var doesUserHaveAccess, device;
+    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, varName) {
+      var device;
       return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
           switch (_context8.prev = _context8.next) {
             case 0:
               _context8.next = 2;
-              return _this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID);
+              return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              doesUserHaveAccess = _context8.sent;
-
-              if (doesUserHaveAccess) {
-                _context8.next = 5;
-                break;
-              }
-
-              throw new _HttpError2.default('No device found', 404);
-
-            case 5:
               device = _this._deviceServer.getDevice(deviceID);
 
               if (device) {
-                _context8.next = 8;
+                _context8.next = 5;
                 break;
               }
 
               throw new _HttpError2.default('Could not get device for ID', 404);
 
-            case 8:
-              _context8.next = 10;
+            case 5:
+              _context8.next = 7;
               return device.getVariableValue(varName);
 
-            case 10:
+            case 7:
               return _context8.abrupt('return', _context8.sent);
 
-            case 11:
+            case 8:
             case 'end':
               return _context8.stop();
           }
@@ -381,7 +355,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee8, _this);
     }));
 
-    return function (_x15, _x16, _x17) {
+    return function (_x10, _x11) {
       return _ref10.apply(this, arguments);
     };
   }();
@@ -393,23 +367,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
         while (1) {
           switch (_context9.prev = _context9.next) {
             case 0:
+              _context9.next = 2;
+              return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
+
+            case 2:
               device = _this._deviceServer.getDevice(deviceID);
 
               if (device) {
-                _context9.next = 3;
+                _context9.next = 5;
                 break;
               }
 
               throw new _HttpError2.default('Could not get device for ID', 404);
 
-            case 3:
-              _context9.next = 5;
+            case 5:
+              _context9.next = 7;
               return device.flash(file.buffer);
 
-            case 5:
+            case 7:
               return _context9.abrupt('return', _context9.sent);
 
-            case 6:
+            case 8:
             case 'end':
               return _context9.stop();
           }
@@ -417,57 +395,49 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee9, _this);
     }));
 
-    return function (_x18, _x19) {
+    return function (_x12, _x13) {
       return _ref11.apply(this, arguments);
     };
   }();
 
   this.flashKnownApp = function () {
-    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, userID, appName) {
+    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, appName) {
       var knownFirmware, device;
       return _regenerator2.default.wrap(function _callee10$(_context10) {
         while (1) {
           switch (_context10.prev = _context10.next) {
             case 0:
               _context10.next = 2;
-              return !_this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID);
+              return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              if (!_context10.sent) {
-                _context10.next = 4;
-                break;
-              }
-
-              throw new _HttpError2.default('No device found', 404);
-
-            case 4:
               knownFirmware = _this._deviceFirmwareRepository.getByName(appName);
 
               if (knownFirmware) {
-                _context10.next = 7;
+                _context10.next = 5;
                 break;
               }
 
               throw new _HttpError2.default('No firmware ' + appName + ' found', 404);
 
-            case 7:
+            case 5:
               device = _this._deviceServer.getDevice(deviceID);
 
               if (device) {
-                _context10.next = 10;
+                _context10.next = 8;
                 break;
               }
 
               throw new _HttpError2.default('Could not get device for ID', 404);
 
-            case 10:
-              _context10.next = 12;
+            case 8:
+              _context10.next = 10;
               return device.flash(knownFirmware);
 
-            case 12:
+            case 10:
               return _context10.abrupt('return', _context10.sent);
 
-            case 13:
+            case 11:
             case 'end':
               return _context10.stop();
           }
@@ -475,7 +445,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee10, _this);
     }));
 
-    return function (_x20, _x21, _x22) {
+    return function (_x14, _x15) {
       return _ref12.apply(this, arguments);
     };
   }();
@@ -536,7 +506,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 19:
               _context11.next = 21;
-              return _this.getByID(deviceID, userID);
+              return _this.getByID(deviceID);
 
             case 21:
               return _context11.abrupt('return', _context11.sent);
@@ -549,47 +519,39 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee11, _this, [[2, 8]]);
     }));
 
-    return function (_x23, _x24, _x25, _x26) {
+    return function (_x16, _x17, _x18, _x19) {
       return _ref13.apply(this, arguments);
     };
   }();
 
   this.raiseYourHand = function () {
-    var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, userID, shouldShowSignal) {
+    var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, shouldShowSignal) {
       var device;
       return _regenerator2.default.wrap(function _callee12$(_context12) {
         while (1) {
           switch (_context12.prev = _context12.next) {
             case 0:
               _context12.next = 2;
-              return !_this._deviceAttributeRepository.doesUserHaveAccess(deviceID, userID);
+              return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              if (!_context12.sent) {
-                _context12.next = 4;
-                break;
-              }
-
-              throw new _HttpError2.default('No device found', 404);
-
-            case 4:
               device = _this._deviceServer.getDevice(deviceID);
 
               if (device) {
-                _context12.next = 7;
+                _context12.next = 5;
                 break;
               }
 
               throw new _HttpError2.default('Could not get device for ID', 404);
 
-            case 7:
-              _context12.next = 9;
+            case 5:
+              _context12.next = 7;
               return device.raiseYourHand(shouldShowSignal);
 
-            case 9:
+            case 7:
               return _context12.abrupt('return', _context12.sent);
 
-            case 10:
+            case 8:
             case 'end':
               return _context12.stop();
           }
@@ -597,20 +559,20 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee12, _this);
     }));
 
-    return function (_x27, _x28, _x29) {
+    return function (_x20, _x21) {
       return _ref14.apply(this, arguments);
     };
   }();
 
   this.renameDevice = function () {
-    var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, userID, name) {
+    var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, name) {
       var attributes, attributesToSave;
       return _regenerator2.default.wrap(function _callee13$(_context13) {
         while (1) {
           switch (_context13.prev = _context13.next) {
             case 0:
               _context13.next = 2;
-              return _this._deviceAttributeRepository.getById(deviceID, userID);
+              return _this._permissionManager.getEntityByID('deviceAttributes', deviceID);
 
             case 2:
               attributes = _context13.sent;
@@ -640,7 +602,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
       }, _callee13, _this);
     }));
 
-    return function (_x30, _x31, _x32) {
+    return function (_x22, _x23) {
       return _ref15.apply(this, arguments);
     };
   }();
@@ -649,6 +611,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   this._deviceFirmwareRepository = deviceFirmwareRepository;
   this._deviceKeyRepository = deviceKeyRepository;
   this._deviceServer = deviceServer;
+  this._permissionManager = permissionManager;
 };
 
 exports.default = DeviceManager;
\ No newline at end of file
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index fa0cd715..e617e9cf 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -98,7 +98,7 @@ var WEBHOOK_DEFAULTS = {
   rejectUnauthorized: true
 };
 
-var WebhookManager = function WebhookManager(webhookRepository, eventPublisher, webhookLogger) {
+var WebhookManager = function WebhookManager(eventPublisher, permissionManager, webhookLogger, webhookRepository) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, WebhookManager);
@@ -135,14 +135,14 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher,
   }();
 
   this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID, userID) {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID) {
       var webhook;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._webhookRepository.getById(webhookID, userID);
+              return _this._permissonManager.getEntityByID('webhook', webhookID);
 
             case 2:
               webhook = _context2.sent;
@@ -169,45 +169,39 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher,
       }, _callee2, _this);
     }));
 
-    return function (_x2, _x3) {
+    return function (_x2) {
       return _ref2.apply(this, arguments);
     };
   }();
 
-  this.getAll = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID) {
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
-        while (1) {
-          switch (_context3.prev = _context3.next) {
-            case 0:
-              _context3.next = 2;
-              return _this._webhookRepository.getAll(userID);
+  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+    return _regenerator2.default.wrap(function _callee3$(_context3) {
+      while (1) {
+        switch (_context3.prev = _context3.next) {
+          case 0:
+            _context3.next = 2;
+            return _this._permissonManager.getAllEntitiesForCurrentUser('webhook');
 
-            case 2:
-              return _context3.abrupt('return', _context3.sent);
+          case 2:
+            return _context3.abrupt('return', _context3.sent);
 
-            case 3:
-            case 'end':
-              return _context3.stop();
-          }
+          case 3:
+          case 'end':
+            return _context3.stop();
         }
-      }, _callee3, _this);
-    }));
-
-    return function (_x4) {
-      return _ref3.apply(this, arguments);
-    };
-  }();
+      }
+    }, _callee3, _this);
+  }));
 
   this.getByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID, userID) {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID) {
       var webhook;
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _this._webhookRepository.getById(webhookID, userID);
+              return _this._permissonManager.getEntityByID('webhook', webhookID);
 
             case 2:
               webhook = _context4.sent;
@@ -230,7 +224,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher,
       }, _callee4, _this);
     }));
 
-    return function (_x5, _x6) {
+    return function (_x3) {
       return _ref4.apply(this, arguments);
     };
   }();
@@ -381,7 +375,7 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher,
       }, _callee6, _this, [[0, 25]]);
     }));
 
-    return function (_x7, _x8) {
+    return function (_x4, _x5) {
       return _ref6.apply(this, arguments);
     };
   }();
@@ -486,9 +480,10 @@ var WebhookManager = function WebhookManager(webhookRepository, eventPublisher,
     _this._errorsCountByWebhookID.set(webhookID, 0);
   };
 
-  this._webhookRepository = webhookRepository;
   this._eventPublisher = eventPublisher;
+  this._permissonManager = permissionManager;
   this._webhookLogger = webhookLogger;
+  this._webhookRepository = webhookRepository;
 
   (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
     return _regenerator2.default.wrap(function _callee7$(_context7) {
diff --git a/dist/oauthClients.json b/dist/oauthClients.json
index 048381d1..c5329c20 100644
--- a/dist/oauthClients.json
+++ b/dist/oauthClients.json
@@ -1,10 +1,15 @@
-[{
-  "clientId": "CLI2",
-  "clientSecret": "client_secret_here",
-  "grants": ["password"]
-},
-{
-  "clientId": "particle-collider",
-  "clientSecret": "particle-collider",
-  "grants": ["password"]
-}]
+[{
+  "clientId": "CLI2",
+  "clientSecret": "client_secret_here",
+  "grants": ["password"]
+},
+{
+  "clientId": "particle-collider",
+  "clientSecret": "particle-collider",
+  "grants": ["password"]
+},
+{
+  "clientId": "spark-server",
+  "clientSecret": "spark-server",
+  "grants": ["password"]
+}]
diff --git a/dist/repository/BaseMongoDb.js b/dist/repository/BaseMongoDb.js
index 92ea5130..f2681159 100644
--- a/dist/repository/BaseMongoDb.js
+++ b/dist/repository/BaseMongoDb.js
@@ -310,8 +310,14 @@ var BaseMongoDb = function BaseMongoDb() {
     };
   }();
 
+  this.__filterID = function (_ref11) {
+    var id = _ref11.id,
+        otherProps = (0, _objectWithoutProperties3.default)(_ref11, ['id']);
+    return (0, _extends3.default)({}, otherProps);
+  };
+
   this.__runForCollection = function () {
-    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
+    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
@@ -327,12 +333,12 @@ var BaseMongoDb = function BaseMongoDb() {
     }));
 
     return function (_x18, _x19) {
-      return _ref11.apply(this, arguments);
+      return _ref12.apply(this, arguments);
     };
   }();
 
   this.__translateQuery = function (query) {
-    return deepToObjectIdCast(query);
+    return _this.__filterID(deepToObjectIdCast(query));
   };
 
   this.__translateResultItem = function (item) {
@@ -344,6 +350,9 @@ var BaseMongoDb = function BaseMongoDb() {
 
     return (0, _extends3.default)({}, otherProps, { id: _id.toString() });
   };
-};
+}
+
+// eslint-disable-next-line no-unused-vars
+;
 
 exports.default = BaseMongoDb;
\ No newline at end of file
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 6311cf55..f0d1fe83 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -22,7 +22,7 @@ var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseRepository(database) {
+var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseRepository(database, permissionManager) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, DeviceAttributeDatabaseRepository);
@@ -67,19 +67,22 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     };
   }();
 
-  this.doesUserHaveAccess = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(id, userID) {
+  this.getAll = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
             case 0:
-              _context3.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id, ownerID: userID });
-
-            case 2:
-              return _context3.abrupt('return', !!_context3.sent);
+              query = userID ? { ownerID: userID } : {};
+              _context3.next = 3;
+              return _this._database.find(_this._collectionName, query, { timeout: false });
 
             case 3:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 4:
             case 'end':
               return _context3.stop();
           }
@@ -87,27 +90,24 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee3, _this);
     }));
 
-    return function (_x2, _x3) {
+    return function () {
       return _ref3.apply(this, arguments);
     };
   }();
 
-  this.getAll = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-      var query;
+  this.getById = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
-              query = userID ? { ownerID: userID } : {};
-              _context4.next = 3;
-              return _this._database.find(_this._collectionName, query, { timeout: false });
+              _context4.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: id });
 
-            case 3:
+            case 2:
               return _context4.abrupt('return', _context4.sent);
 
-            case 4:
+            case 3:
             case 'end':
               return _context4.stop();
           }
@@ -115,27 +115,24 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee4, _this);
     }));
 
-    return function () {
+    return function (_x3) {
       return _ref4.apply(this, arguments);
     };
   }();
 
-  this.getById = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
-      var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-      var query;
+  this.update = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(model) {
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
-              query = userID ? { _id: id, ownerID: userID } : { _id: id };
-              _context5.next = 3;
-              return _this._database.findOne(_this._collectionName, query);
+              _context5.next = 2;
+              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
 
-            case 3:
+            case 2:
               return _context5.abrupt('return', _context5.sent);
 
-            case 4:
+            case 3:
             case 'end':
               return _context5.stop();
           }
@@ -143,37 +140,13 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee5, _this);
     }));
 
-    return function (_x5) {
+    return function (_x4) {
       return _ref5.apply(this, arguments);
     };
   }();
 
-  this.update = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(model) {
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
-        while (1) {
-          switch (_context6.prev = _context6.next) {
-            case 0:
-              _context6.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
-
-            case 2:
-              return _context6.abrupt('return', _context6.sent);
-
-            case 3:
-            case 'end':
-              return _context6.stop();
-          }
-        }
-      }, _callee6, _this);
-    }));
-
-    return function (_x7) {
-      return _ref6.apply(this, arguments);
-    };
-  }();
-
   this._database = database;
+  this._permissionManager = permissionManager;
 };
 
 exports.default = DeviceAttributeDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 33104bce..b37345de 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -4,6 +4,10 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -38,9 +42,13 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
         while (1) {
           switch (_context.prev = _context.next) {
             case 0:
-              throw new Error('The method is not implemented');
+              _context.next = 2;
+              return _this._database.insertOne(_this._collectionName, user);
 
-            case 1:
+            case 2:
+              return _context.abrupt('return', _context.sent);
+
+            case 3:
             case 'end':
               return _context.stop();
           }
@@ -55,6 +63,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
 
   this.createWithCredentials = function () {
     var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
+      var userRole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
       var username, password, salt, passwordHash, modelToSave;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
@@ -74,8 +83,8 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
               modelToSave = {
                 accessTokens: [],
                 created_at: new Date(),
-                created_by: null,
                 passwordHash: passwordHash,
+                role: userRole,
                 salt: salt,
                 username: username
               };
@@ -118,7 +127,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee3, _this);
     }));
 
-    return function (_x3, _x4) {
+    return function (_x4, _x5) {
       return _ref3.apply(this, arguments);
     };
   }();
@@ -143,7 +152,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee4, _this);
     }));
 
-    return function (_x5) {
+    return function (_x6) {
       return _ref4.apply(this, arguments);
     };
   }();
@@ -198,7 +207,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee6, _this);
     }));
 
-    return function (_x6) {
+    return function (_x7) {
       return _ref6.apply(this, arguments);
     };
   }();
@@ -219,7 +228,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee7, _this);
     }));
 
-    return function (_x7) {
+    return function (_x8) {
       return _ref7.apply(this, arguments);
     };
   }();
@@ -244,11 +253,15 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee8, _this);
     }));
 
-    return function (_x8) {
+    return function (_x9) {
       return _ref8.apply(this, arguments);
     };
   }();
 
+  this.getCurrentUser = function () {
+    return _this._currentUser;
+  };
+
   this.isUserNameInUse = function () {
     var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(username) {
       return _regenerator2.default.wrap(function _callee9$(_context9) {
@@ -269,7 +282,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee9, _this);
     }));
 
-    return function (_x9) {
+    return function (_x10) {
       return _ref9.apply(this, arguments);
     };
   }();
@@ -294,20 +307,28 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee10, _this);
     }));
 
-    return function (_x10, _x11) {
+    return function (_x11, _x12) {
       return _ref10.apply(this, arguments);
     };
   }();
 
+  this.setCurrentUser = function (user) {
+    _this._currentUser = user;
+  };
+
   this.update = function () {
     var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(model) {
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
             case 0:
-              throw new Error('The method is not implemented');
+              _context11.next = 2;
+              return _this._database.findAndModify(_this._collectionName, { _id: model.id }, null, { $set: (0, _extends3.default)({}, model, { timeStamp: new Date() }) }, { new: true, upsert: true });
 
-            case 1:
+            case 2:
+              return _context11.abrupt('return', _context11.sent);
+
+            case 3:
             case 'end':
               return _context11.stop();
           }
@@ -315,7 +336,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee11, _this);
     }));
 
-    return function (_x12) {
+    return function (_x13) {
       return _ref11.apply(this, arguments);
     };
   }();
@@ -371,7 +392,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee12, _this, [[0, 14]]);
     }));
 
-    return function (_x13, _x14) {
+    return function (_x14, _x15) {
       return _ref12.apply(this, arguments);
     };
   }();
@@ -382,9 +403,6 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
 // eslint-disable-next-line no-unused-vars
 
 
-// eslint-disable-next-line no-unused-vars
-
-
 // eslint-disable-next-line no-unused-vars
 ;
 
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index e79e215e..d013c74a 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -79,7 +79,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeSet)(), _dec7 = (0, _sparkProtocol.memoizeGet)(['username']), (_class = function () {
+var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), _dec5 = (0, _sparkProtocol.memoizeGet)(['username']), _dec6 = (0, _sparkProtocol.memoizeGet)(['username']), _dec7 = (0, _sparkProtocol.memoizeSet)(), (_class = function () {
   function UserFileRepository(path) {
     var _this = this;
 
@@ -87,6 +87,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
     this.createWithCredentials = function () {
       var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) {
+        var userRole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
         var username, password, salt, passwordHash, modelToSave;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
@@ -106,6 +107,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                 modelToSave = {
                   accessTokens: [],
                   passwordHash: passwordHash,
+                  role: userRole,
                   salt: salt,
                   username: username
                 };
@@ -165,7 +167,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee2, _this);
       }));
 
-      return function (_x2, _x3) {
+      return function (_x3, _x4) {
         return _ref2.apply(this, arguments);
       };
     }();
@@ -196,106 +198,114 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee3, _this);
       }));
 
-      return function (_x4) {
+      return function (_x5) {
         return _ref3.apply(this, arguments);
       };
     }();
 
-    this.validateLogin = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(username, password) {
-        var user, hash;
+    this.getCurrentUser = function () {
+      return _this._currentUser;
+    };
+
+    this.saveAccessToken = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, tokenObject) {
+        var user, userToSave;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.prev = 0;
-                _context4.next = 3;
-                return _this.getByUsername(username);
+                _context4.next = 2;
+                return _this.getById(userID);
 
-              case 3:
+              case 2:
                 user = _context4.sent;
 
                 if (user) {
-                  _context4.next = 6;
+                  _context4.next = 5;
                   break;
                 }
 
-                throw new Error('User doesn\'t exist');
+                throw new _HttpError2.default('Could not find user for user ID');
 
-              case 6:
+              case 5:
+                userToSave = (0, _extends3.default)({}, user, {
+                  accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject])
+                });
                 _context4.next = 8;
-                return _PasswordHasher2.default.hash(password, user.salt);
+                return _this.update(userToSave);
 
               case 8:
-                hash = _context4.sent;
-
-                if (!(hash !== user.passwordHash)) {
-                  _context4.next = 11;
-                  break;
-                }
-
-                throw new Error('Wrong password');
+                return _context4.abrupt('return', _context4.sent);
 
-              case 11:
-                return _context4.abrupt('return', user);
-
-              case 14:
-                _context4.prev = 14;
-                _context4.t0 = _context4['catch'](0);
-                throw _context4.t0;
-
-              case 17:
+              case 9:
               case 'end':
                 return _context4.stop();
             }
           }
-        }, _callee4, _this, [[0, 14]]);
+        }, _callee4, _this);
       }));
 
-      return function (_x5, _x6) {
+      return function (_x6, _x7) {
         return _ref4.apply(this, arguments);
       };
     }();
 
-    this.saveAccessToken = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) {
-        var user, userToSave;
+    this.setCurrentUser = function (user) {
+      _this._currentUser = user;
+    };
+
+    this.validateLogin = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(username, password) {
+        var user, hash;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                _context5.next = 2;
-                return _this.getById(userID);
+                _context5.prev = 0;
+                _context5.next = 3;
+                return _this.getByUsername(username);
 
-              case 2:
+              case 3:
                 user = _context5.sent;
 
                 if (user) {
-                  _context5.next = 5;
+                  _context5.next = 6;
                   break;
                 }
 
-                throw new _HttpError2.default('Could not find user for user ID');
+                throw new Error('User doesn\'t exist');
 
-              case 5:
-                userToSave = (0, _extends3.default)({}, user, {
-                  accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject])
-                });
+              case 6:
                 _context5.next = 8;
-                return _this.update(userToSave);
+                return _PasswordHasher2.default.hash(password, user.salt);
 
               case 8:
-                return _context5.abrupt('return', _context5.sent);
+                hash = _context5.sent;
 
-              case 9:
+                if (!(hash !== user.passwordHash)) {
+                  _context5.next = 11;
+                  break;
+                }
+
+                throw new Error('Wrong password');
+
+              case 11:
+                return _context5.abrupt('return', user);
+
+              case 14:
+                _context5.prev = 14;
+                _context5.t0 = _context5['catch'](0);
+                throw _context5.t0;
+
+              case 17:
               case 'end':
                 return _context5.stop();
             }
           }
-        }, _callee5, _this);
+        }, _callee5, _this, [[0, 14]]);
       }));
 
-      return function (_x7, _x8) {
+      return function (_x8, _x9) {
         return _ref5.apply(this, arguments);
       };
     }();
@@ -347,7 +357,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee6, this);
       }));
 
-      function create(_x9) {
+      function create(_x10) {
         return _ref6.apply(this, arguments);
       }
 
@@ -371,7 +381,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee7, this);
       }));
 
-      function deleteById(_x10) {
+      function deleteById(_x11) {
         return _ref7.apply(this, arguments);
       }
 
@@ -423,7 +433,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee9, this);
       }));
 
-      function getById(_x11) {
+      function getById(_x12) {
         return _ref9.apply(this, arguments);
       }
 
@@ -455,24 +465,31 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee10, this);
       }));
 
-      function getByUsername(_x12) {
+      function getByUsername(_x13) {
         return _ref10.apply(this, arguments);
       }
 
       return getByUsername;
     }()
   }, {
-    key: 'update',
+    key: 'isUserNameInUse',
     value: function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(model) {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(username) {
         return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
             switch (_context11.prev = _context11.next) {
               case 0:
-                this._fileManager.writeFile(model.id + '.json', model);
-                return _context11.abrupt('return', model);
+                _context11.next = 2;
+                return this.getAll();
 
               case 2:
+                _context11.t0 = function (user) {
+                  return user.username === username;
+                };
+
+                return _context11.abrupt('return', _context11.sent.some(_context11.t0));
+
+              case 4:
               case 'end':
                 return _context11.stop();
             }
@@ -480,31 +497,24 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee11, this);
       }));
 
-      function update(_x13) {
+      function isUserNameInUse(_x14) {
         return _ref11.apply(this, arguments);
       }
 
-      return update;
+      return isUserNameInUse;
     }()
   }, {
-    key: 'isUserNameInUse',
+    key: 'update',
     value: function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) {
+      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(model) {
         return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
             switch (_context12.prev = _context12.next) {
               case 0:
-                _context12.next = 2;
-                return this.getAll();
+                this._fileManager.writeFile(model.id + '.json', model);
+                return _context12.abrupt('return', model);
 
               case 2:
-                _context12.t0 = function (user) {
-                  return user.username === username;
-                };
-
-                return _context12.abrupt('return', _context12.sent.some(_context12.t0));
-
-              case 4:
               case 'end':
                 return _context12.stop();
             }
@@ -512,13 +522,13 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee12, this);
       }));
 
-      function isUserNameInUse(_x14) {
+      function update(_x15) {
         return _ref12.apply(this, arguments);
       }
 
-      return isUserNameInUse;
+      return update;
     }()
   }]);
   return UserFileRepository;
-}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype)), _class));
+}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype)), _class));
 exports.default = UserFileRepository;
\ No newline at end of file
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index c60a2da9..054cb2bd 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -110,20 +110,17 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
 
   this.getById = function () {
     var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
-      var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-      var query;
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
-              query = userID ? { _id: id, ownerID: userID } : { _id: id };
-              _context4.next = 3;
-              return _this._database.findOne(_this._collectionName, query);
+              _context4.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: id });
 
-            case 3:
+            case 2:
               return _context4.abrupt('return', _context4.sent);
 
-            case 4:
+            case 3:
             case 'end':
               return _context4.stop();
           }
diff --git a/dist/settings.js b/dist/settings.js
index c5b89de0..34eb5b0d 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -13,6 +13,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 /* eslint-disable sorting/sort-object-props */
 exports.default = {
   BUILD_DIRECTORY: _path2.default.join(__dirname, '../data/build'),
+  DEFAULT_ADMIN_PASSWORD: 'adminPassword',
+  DEFAULT_ADMIN_USERNAME: '__admin__',
   DEVICE_DIRECTORY: _path2.default.join(__dirname, '../data/deviceKeys'),
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: _path2.default.join(__dirname, '../data/knownApps'),

From b7928648e8598f3bcf6197bc54b7a55989dadf33 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 6 Jun 2017 21:56:02 +0200
Subject: [PATCH 388/504] change arg from id to query in remove db method. fix
 deleteById name

---
 dist/managers/WebhookManager.js                      | 2 +-
 dist/repository/BaseMongoDb.js                       | 4 ++--
 dist/repository/DeviceAttributeDatabaseRepository.js | 4 ++--
 dist/repository/DeviceKeyDatabaseRepository.js       | 6 +++---
 dist/repository/TingoDb.js                           | 4 ++--
 dist/repository/UserDatabaseRepository.js            | 4 ++--
 dist/repository/UserFileRepository.js                | 8 ++++----
 dist/repository/WebhookDatabaseRepository.js         | 4 ++--
 dist/repository/WebhookFileRepository.js             | 8 ++++----
 src/managers/WebhookManager.js                       | 2 +-
 src/repository/BaseMongoDb.js                        | 4 ++--
 src/repository/DeviceAttributeDatabaseRepository.js  | 4 ++--
 src/repository/DeviceKeyDatabaseRepository.js        | 6 +++---
 src/repository/TingoDb.js                            | 4 ++--
 src/repository/UserDatabaseRepository.js             | 4 ++--
 src/repository/UserFileRepository.js                 | 2 +-
 src/repository/WebhookDatabaseRepository.js          | 4 ++--
 src/repository/WebhookFileRepository.js              | 2 +-
 src/types.js                                         | 4 ++--
 test/DeviceClaimsController.test.js                  | 6 +++---
 test/DevicesController.test.js                       | 6 +++---
 test/ProvisioningController.test.js                  | 6 +++---
 test/UserFileRepository.test.js                      | 6 +++---
 test/UsersController.test.js                         | 2 +-
 test/WebhooksController.test.js                      | 4 ++--
 25 files changed, 55 insertions(+), 55 deletions(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index e617e9cf..4cb07028 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -156,7 +156,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
 
             case 5:
               _context2.next = 7;
-              return _this._webhookRepository.deleteById(webhookID);
+              return _this._webhookRepository.deleteByID(webhookID);
 
             case 7:
               _this._unsubscribeWebhookByID(webhookID);
diff --git a/dist/repository/BaseMongoDb.js b/dist/repository/BaseMongoDb.js
index f2681159..b2425bdd 100644
--- a/dist/repository/BaseMongoDb.js
+++ b/dist/repository/BaseMongoDb.js
@@ -263,7 +263,7 @@ var BaseMongoDb = function BaseMongoDb() {
   }();
 
   this.remove = function () {
-    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, id) {
+    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
       return _regenerator2.default.wrap(function _callee10$(_context10) {
         while (1) {
           switch (_context10.prev = _context10.next) {
@@ -276,7 +276,7 @@ var BaseMongoDb = function BaseMongoDb() {
                       switch (_context9.prev = _context9.next) {
                         case 0:
                           _context9.next = 2;
-                          return collection.remove(_this.__translateQuery({ _id: id }));
+                          return collection.remove(_this.__translateQuery(query));
 
                         case 2:
                           return _context9.abrupt('return', _context9.sent);
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index f0d1fe83..b7b185b1 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -42,14 +42,14 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     }, _callee, _this);
   }));
 
-  this.deleteById = function () {
+  this.deleteByID = function () {
     var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._database.remove(_this._collectionName, id);
+              return _this._database.remove(_this._collectionName, { _id: id });
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index 60420a44..baf7275f 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -53,14 +53,14 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
     };
   }();
 
-  this.deleteById = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
+  this.deleteByID = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._database.remove(_this._collectionName, deviceID);
+              return _this._database.remove(_this._collectionName, { _id: id });
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
diff --git a/dist/repository/TingoDb.js b/dist/repository/TingoDb.js
index a27b4e19..e6382aff 100644
--- a/dist/repository/TingoDb.js
+++ b/dist/repository/TingoDb.js
@@ -271,7 +271,7 @@ var TingoDb = function (_BaseMongoDb) {
     }();
 
     _this.remove = function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, id) {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
         return _regenerator2.default.wrap(function _callee10$(_context10) {
           while (1) {
             switch (_context10.prev = _context10.next) {
@@ -284,7 +284,7 @@ var TingoDb = function (_BaseMongoDb) {
                         switch (_context9.prev = _context9.next) {
                           case 0:
                             _context9.next = 2;
-                            return (0, _promisify.promisify)(collection, 'remove', { _id: id });
+                            return (0, _promisify.promisify)(collection, 'remove', query);
 
                           case 2:
                             return _context9.abrupt('return', _context9.sent);
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index b37345de..473591c0 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -132,14 +132,14 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
     };
   }();
 
-  this.deleteById = function () {
+  this.deleteByID = function () {
     var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _this._database.remove(_this._collectionName, id);
+              return _this._database.remove(_this._collectionName, { _id: id });
 
             case 2:
               return _context4.abrupt('return', _context4.sent);
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index d013c74a..901dc5de 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -364,7 +364,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
       return create;
     }()
   }, {
-    key: 'deleteById',
+    key: 'deleteByID',
     value: function () {
       var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
         return _regenerator2.default.wrap(function _callee7$(_context7) {
@@ -381,11 +381,11 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee7, this);
       }));
 
-      function deleteById(_x11) {
+      function deleteByID(_x11) {
         return _ref7.apply(this, arguments);
       }
 
-      return deleteById;
+      return deleteByID;
     }()
   }, {
     key: 'getAll',
@@ -530,5 +530,5 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     }()
   }]);
   return UserFileRepository;
-}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype)), _class));
+}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype)), _class));
 exports.default = UserFileRepository;
\ No newline at end of file
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index 054cb2bd..e5f37847 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -55,14 +55,14 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
     };
   }();
 
-  this.deleteById = function () {
+  this.deleteByID = function () {
     var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._database.remove(_this._collectionName, id);
+              return _this._database.remove(_this._collectionName, { _id: id });
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js
index c2a9da2c..72638c8c 100644
--- a/dist/repository/WebhookFileRepository.js
+++ b/dist/repository/WebhookFileRepository.js
@@ -227,7 +227,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
       return create;
     }()
   }, {
-    key: 'deleteById',
+    key: 'deleteByID',
     value: function () {
       var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
@@ -244,11 +244,11 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
         }, _callee5, this);
       }));
 
-      function deleteById(_x6) {
+      function deleteByID(_x6) {
         return _ref5.apply(this, arguments);
       }
 
-      return deleteById;
+      return deleteByID;
     }()
 
     // eslint-disable-next-line no-unused-vars
@@ -303,5 +303,5 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
     }()
   }]);
   return WebhookFileRepository;
-}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteById', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class));
+}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class));
 exports.default = WebhookFileRepository;
\ No newline at end of file
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index cff9c211..cc371987 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -101,7 +101,7 @@ class WebhookManager {
       throw new HttpError('no webhook found', 404);
     }
 
-    await this._webhookRepository.deleteById(webhookID);
+    await this._webhookRepository.deleteByID(webhookID);
     this._unsubscribeWebhookByID(webhookID);
   };
 
diff --git a/src/repository/BaseMongoDb.js b/src/repository/BaseMongoDb.js
index 2be9764d..ab8c7e31 100644
--- a/src/repository/BaseMongoDb.js
+++ b/src/repository/BaseMongoDb.js
@@ -81,11 +81,11 @@ class BaseMongoDb implements IBaseDatabase {
 
   remove = async (
     collectionName: string,
-    id: string,
+    query: Object,
   ): Promise<*> => await this.__runForCollection(
     collectionName,
     async (collection: Object): Promise<*> =>
-      await collection.remove(this.__translateQuery({ _id: id })),
+      await collection.remove(this.__translateQuery(query)),
   );
 
   // eslint-disable-next-line no-unused-vars
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 09ee5bab..64888fb7 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -20,8 +20,8 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     throw new Error('The method is not implemented');
   };
 
-  deleteById = async (id: string): Promise =>
-    await this._database.remove(this._collectionName, id);
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index c5b5fd6b..bd0fe378 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -16,12 +16,12 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
       { _id: model.deviceID, ...model },
     );
 
-  deleteById = async (deviceID: string): Promise =>
-    await this._database.remove(this._collectionName, deviceID);
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
 
   getAll = async (): Promise> => {
     throw new Error('The method is not implemented.');
-  }
+  };
 
   getById = async (deviceID: string): Promise =>
     await this._database.findOne(
diff --git a/src/repository/TingoDb.js b/src/repository/TingoDb.js
index 1c706959..09725b65 100644
--- a/src/repository/TingoDb.js
+++ b/src/repository/TingoDb.js
@@ -73,11 +73,11 @@ class TingoDb extends BaseMongoDb {
 
   remove = async (
     collectionName: string,
-    id: string,
+    query: Object,
   ): Promise<*> => await this.__runForCollection(
     collectionName,
     async (collection: Object): Promise<*> =>
-      await promisify(collection, 'remove', { _id: id }),
+      await promisify(collection, 'remove', query),
   );
 
   __runForCollection = async (
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index 1f2b1f03..f9694292 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -60,8 +60,8 @@ class UserDatabaseRepository implements IUserRepository {
       { new: true },
     );
 
-  deleteById = async (id: string): Promise =>
-    await this._database.remove(this._collectionName, id);
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
 
   getAll = async (): Promise> => {
     throw new Error('The method is not implemented');
diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js
index 541965b8..42e1fb0b 100644
--- a/src/repository/UserFileRepository.js
+++ b/src/repository/UserFileRepository.js
@@ -76,7 +76,7 @@ class UserFileRepository implements IUserRepository {
   };
 
   @memoizeSet(['id'])
-  async deleteById(id: string): Promise {
+  async deleteByID(id: string): Promise {
     this._fileManager.deleteFile(`${id}.json`);
   }
 
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index ff272d68..ece7c3ba 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -19,8 +19,8 @@ class WebhookDatabaseRepository implements IWebhookRepository {
       },
     );
 
-  deleteById = async (id: string): Promise =>
-    await this._database.remove(this._collectionName, id);
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js
index 06bcb614..a5f4d017 100644
--- a/src/repository/WebhookFileRepository.js
+++ b/src/repository/WebhookFileRepository.js
@@ -31,7 +31,7 @@ class WebhookFileRepository implements IWebhookRepository {
   }
 
   @memoizeSet(['id'])
-  async deleteById(id: string): Promise {
+  async deleteByID(id: string): Promise {
     this._fileManager.deleteFile(`${id}.json`);
   }
 
diff --git a/src/types.js b/src/types.js
index 348c9f78..e45fb69a 100644
--- a/src/types.js
+++ b/src/types.js
@@ -234,7 +234,7 @@ export type Product = {
 
 export interface IBaseRepository {
   create(model: TModel | $Shape): Promise;
-  deleteById(id: string): Promise;
+  deleteByID(id: string): Promise;
   getAll(): Promise>;
   getById(id: string, userID: ?string): Promise;
   update(model: TModel): Promise;
@@ -267,5 +267,5 @@ export interface IBaseDatabase {
   findAndModify(collectionName: string, ...args: Array): Promise<*>;
   findOne(collectionName: string, ...args: Array): Promise<*>;
   insertOne(collectionName: string, ...args: Array): Promise<*>;
-  remove(collectionName: string, id: string): Promise<*>;
+  remove(collectionName: string, query: Object): Promise<*>;
 }
diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js
index e7ef9b71..048e1190 100644
--- a/test/DeviceClaimsController.test.js
+++ b/test/DeviceClaimsController.test.js
@@ -70,7 +70,7 @@ test(
 );
 
 test.after.always(async (): Promise => {
-  await container.constitute('UserRepository').deleteById(testUser.id);
-  await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
+  await container.constitute('UserRepository').deleteByID(testUser.id);
+  await container.constitute('DeviceAttributeRepository').deleteByID(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteByID(DEVICE_ID);
 });
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index 18ec910c..a3fe17f9 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -582,7 +582,7 @@ test.serial(
 
 test.after.always(async (): Promise => {
   await TestData.deleteCustomFirmwareBinary(customFirmwareFilePath);
-  await container.constitute('UserRepository').deleteById(testUser.id);
-  await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
+  await container.constitute('UserRepository').deleteByID(testUser.id);
+  await container.constitute('DeviceAttributeRepository').deleteByID(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteByID(DEVICE_ID);
 });
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index e1aa8841..7d391cab 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -71,7 +71,7 @@ test('should throw an error if public key is not provided', async t => {
 });
 
 test.after.always(async (): Promise => {
-  await container.constitute('UserRepository').deleteById(testUser.id);
-  await container.constitute('DeviceAttributeRepository').deleteById(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteById(DEVICE_ID);
+  await container.constitute('UserRepository').deleteByID(testUser.id);
+  await container.constitute('DeviceAttributeRepository').deleteByID(DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteByID(DEVICE_ID);
 });
diff --git a/test/UserFileRepository.test.js b/test/UserFileRepository.test.js
index e8b2d28b..2e241d1a 100644
--- a/test/UserFileRepository.test.js
+++ b/test/UserFileRepository.test.js
@@ -14,7 +14,7 @@ test(
     const fileManager = repository._fileManager;
     const getAllSpy = sinon.spy(fileManager, 'getAllData');
     const getByIdSpy = sinon.spy(fileManager, 'getFile');
-    const deleteByIdSpy = sinon.spy(fileManager, 'deleteFile');
+    const deleteByIDSpy = sinon.spy(fileManager, 'deleteFile');
 
     async function testAllAccessors() {
       testIterator++;
@@ -40,8 +40,8 @@ test(
     await repository.update(user);
     await testAllAccessors();
 
-    await repository.deleteById(user.id);
+    await repository.deleteByID(user.id);
     await testAllAccessors();
-    t.truthy(deleteByIdSpy.callCount === 1);
+    t.truthy(deleteByIDSpy.callCount === 1);
   },
 );
diff --git a/test/UsersController.test.js b/test/UsersController.test.js
index af2a1540..9f1a4e33 100644
--- a/test/UsersController.test.js
+++ b/test/UsersController.test.js
@@ -83,5 +83,5 @@ test.serial('should delete the access token for the user', async t => {
 });
 
 test.after.always(async (): Promise => {
-  await container.constitute('UserRepository').deleteById(user.id);
+  await container.constitute('UserRepository').deleteByID(user.id);
 });
diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js
index 47e9e757..e1e5fe18 100644
--- a/test/WebhooksController.test.js
+++ b/test/WebhooksController.test.js
@@ -144,6 +144,6 @@ test.serial('should delete webhook', async t => {
 });
 
 test.after.always(async (): Promise => {
-  await container.constitute('WebhookRepository').deleteById(testWebhook.id);
-  await container.constitute('UserRepository').deleteById(testUser.id);
+  await container.constitute('WebhookRepository').deleteByID(testWebhook.id);
+  await container.constitute('UserRepository').deleteByID(testUser.id);
 });

From 0424346774c002f5ce159fdc5745d72575c3118e Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 6 Jun 2017 22:17:48 +0200
Subject: [PATCH 389/504] rename getByID

---
 dist/managers/DeviceManager.js                |   4 +-
 .../DeviceAttributeDatabaseRepository.js      |   2 +-
 .../repository/DeviceKeyDatabaseRepository.js |   2 +-
 dist/repository/UserDatabaseRepository.js     |   2 +-
 dist/repository/UserFileRepository.js         |  12 +-
 dist/repository/WebhookDatabaseRepository.js  |   2 +-
 dist/repository/WebhookFileRepository.js      | 125 ++++++------------
 src/managers/DeviceManager.js                 |   4 +-
 src/managers/PermissionManager.js             |   2 +-
 .../DeviceAttributeDatabaseRepository.js      |   2 +-
 src/repository/DeviceKeyDatabaseRepository.js |   2 +-
 src/repository/UserDatabaseRepository.js      |   2 +-
 src/repository/UserFileRepository.js          |   6 +-
 src/repository/WebhookDatabaseRepository.js   |   2 +-
 src/repository/WebhookFileRepository.js       |  25 +---
 src/types.js                                  |   2 +-
 test/DevicesController.test.js                |   2 +-
 test/UserFileRepository.test.js               |  10 +-
 18 files changed, 79 insertions(+), 129 deletions(-)

diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 866afbe3..80455800 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -51,7 +51,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
           switch (_context.prev = _context.next) {
             case 0:
               _context.next = 2;
-              return _this._deviceAttributeRepository.getById(deviceID);
+              return _this._deviceAttributeRepository.getByID(deviceID);
 
             case 2:
               deviceAttributes = _context.sent;
@@ -490,7 +490,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 13:
               _context11.next = 15;
-              return _this._deviceAttributeRepository.getById(deviceID);
+              return _this._deviceAttributeRepository.getByID(deviceID);
 
             case 15:
               existingAttributes = _context11.sent;
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index b7b185b1..4ca0bd3f 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -95,7 +95,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     };
   }();
 
-  this.getById = function () {
+  this.getByID = function () {
     var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index baf7275f..8473b1b7 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -93,7 +93,7 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
     }, _callee3, _this);
   }));
 
-  this.getById = function () {
+  this.getByID = function () {
     var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 473591c0..9ce3b597 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -212,7 +212,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
     };
   }();
 
-  this.getById = function () {
+  this.getByID = function () {
     var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
       return _regenerator2.default.wrap(function _callee7$(_context7) {
         while (1) {
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index 901dc5de..18155125 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -138,7 +138,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
             switch (_context2.prev = _context2.next) {
               case 0:
                 _context2.next = 2;
-                return _this.getById(userID);
+                return _this.getByID(userID);
 
               case 2:
                 user = _context2.sent;
@@ -215,7 +215,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
             switch (_context4.prev = _context4.next) {
               case 0:
                 _context4.next = 2;
-                return _this.getById(userID);
+                return _this.getByID(userID);
 
               case 2:
                 user = _context4.sent;
@@ -416,7 +416,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     // isn't a good way to clear the cache.
 
   }, {
-    key: 'getById',
+    key: 'getByID',
     value: function () {
       var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) {
         return _regenerator2.default.wrap(function _callee9$(_context9) {
@@ -433,11 +433,11 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee9, this);
       }));
 
-      function getById(_x12) {
+      function getByID(_x12) {
         return _ref9.apply(this, arguments);
       }
 
-      return getById;
+      return getByID;
     }()
   }, {
     key: 'getByUsername',
@@ -530,5 +530,5 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     }()
   }]);
   return UserFileRepository;
-}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getById', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getById'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype)), _class));
+}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype)), _class));
 exports.default = UserFileRepository;
\ No newline at end of file
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index e5f37847..31aa9bd2 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -108,7 +108,7 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
     };
   }();
 
-  this.getById = function () {
+  this.getByID = function () {
     var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js
index 72638c8c..2fe18d09 100644
--- a/dist/repository/WebhookFileRepository.js
+++ b/dist/repository/WebhookFileRepository.js
@@ -71,7 +71,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(), _dec4 = (0, _sparkProtocol.memoizeGet)(['id']), (_class = function () {
+var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _sparkProtocol.memoizeSet)(['id']), _dec3 = (0, _sparkProtocol.memoizeGet)(['id']), _dec4 = (0, _sparkProtocol.memoizeGet)(), (_class = function () {
   function WebhookFileRepository(path) {
     var _this = this;
 
@@ -116,31 +116,15 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
       };
     }();
 
-    this.getById = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
-        var userID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-        var webhook;
+    this.update = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(model) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._getByID(id);
-
-              case 2:
-                webhook = _context2.sent;
-
-                if (!(!webhook || webhook.ownerID !== userID)) {
-                  _context2.next = 5;
-                  break;
-                }
-
-                return _context2.abrupt('return', null);
-
-              case 5:
-                return _context2.abrupt('return', webhook);
+                throw new _HttpError2.default('Not implemented');
 
-              case 6:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -153,53 +137,32 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
       };
     }();
 
-    this.update = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) {
-        return _regenerator2.default.wrap(function _callee3$(_context3) {
-          while (1) {
-            switch (_context3.prev = _context3.next) {
-              case 0:
-                throw new _HttpError2.default('Not implemented');
-
-              case 1:
-              case 'end':
-                return _context3.stop();
-            }
-          }
-        }, _callee3, _this);
-      }));
-
-      return function (_x4) {
-        return _ref3.apply(this, arguments);
-      };
-    }();
-
     this._fileManager = new _sparkProtocol.JSONFileManager(path);
   }
 
   (0, _createClass3.default)(WebhookFileRepository, [{
     key: 'create',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) {
         var id, modelToSave;
-        return _regenerator2.default.wrap(function _callee4$(_context4) {
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
-            switch (_context4.prev = _context4.next) {
+            switch (_context3.prev = _context3.next) {
               case 0:
                 id = (0, _uuid2.default)();
 
               case 1:
-                _context4.next = 3;
+                _context3.next = 3;
                 return this._fileManager.hasFile(id + '.json');
 
               case 3:
-                if (!_context4.sent) {
-                  _context4.next = 7;
+                if (!_context3.sent) {
+                  _context3.next = 7;
                   break;
                 }
 
                 id = (0, _uuid2.default)();
-                _context4.next = 1;
+                _context3.next = 1;
                 break;
 
               case 7:
@@ -210,9 +173,33 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
 
 
                 this._fileManager.createFile(modelToSave.id + '.json', modelToSave);
-                return _context4.abrupt('return', modelToSave);
+                return _context3.abrupt('return', modelToSave);
 
               case 10:
+              case 'end':
+                return _context3.stop();
+            }
+          }
+        }, _callee3, this);
+      }));
+
+      function create(_x3) {
+        return _ref3.apply(this, arguments);
+      }
+
+      return create;
+    }()
+  }, {
+    key: 'deleteByID',
+    value: function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                this._fileManager.deleteFile(id + '.json');
+
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -220,21 +207,21 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
         }, _callee4, this);
       }));
 
-      function create(_x5) {
+      function deleteByID(_x4) {
         return _ref4.apply(this, arguments);
       }
 
-      return create;
+      return deleteByID;
     }()
   }, {
-    key: 'deleteByID',
+    key: 'getByID',
     value: function () {
       var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                this._fileManager.deleteFile(id + '.json');
+                return _context5.abrupt('return', this._fileManager.getFile(id + '.json'));
 
               case 1:
               case 'end':
@@ -244,11 +231,11 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
         }, _callee5, this);
       }));
 
-      function deleteByID(_x6) {
+      function getByID(_x5) {
         return _ref5.apply(this, arguments);
       }
 
-      return deleteByID;
+      return getByID;
     }()
 
     // eslint-disable-next-line no-unused-vars
@@ -277,31 +264,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
 
       return _getAll;
     }()
-  }, {
-    key: '_getByID',
-    value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
-        return _regenerator2.default.wrap(function _callee7$(_context7) {
-          while (1) {
-            switch (_context7.prev = _context7.next) {
-              case 0:
-                return _context7.abrupt('return', this._fileManager.getFile(id + '.json'));
-
-              case 1:
-              case 'end':
-                return _context7.stop();
-            }
-          }
-        }, _callee7, this);
-      }));
-
-      function _getByID(_x7) {
-        return _ref7.apply(this, arguments);
-      }
-
-      return _getByID;
-    }()
   }]);
   return WebhookFileRepository;
-}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getByID'), _class.prototype)), _class));
+}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByID', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, '_getAll', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, '_getAll'), _class.prototype)), _class));
 exports.default = WebhookFileRepository;
\ No newline at end of file
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index c1c6ced0..f3b58b67 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -40,7 +40,7 @@ class DeviceManager {
     userID: string,
   ): Promise => {
     const deviceAttributes =
-      await this._deviceAttributeRepository.getById(deviceID);
+      await this._deviceAttributeRepository.getByID(deviceID);
 
     if (!deviceAttributes) {
       throw new HttpError('No device found', 404);
@@ -235,7 +235,7 @@ class DeviceManager {
     }
 
     await this._deviceKeyRepository.update({ deviceID, key: publicKey });
-    const existingAttributes = await this._deviceAttributeRepository.getById(
+    const existingAttributes = await this._deviceAttributeRepository.getByID(
       deviceID,
     );
     const attributes = {
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index 2ab39e3d..548c853c 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -47,7 +47,7 @@ class PermissionManager {
     entityName: ProtectedEntityName,
     id: string,
   ): Promise<*> => {
-    const entity = await nullthrows(this._repositoriesByEntityName.get(entityName)).getById(id);
+    const entity = await nullthrows(this._repositoriesByEntityName.get(entityName)).getByID(id);
     if (!entity) {
       return null;
     }
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 64888fb7..288d0e1c 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -32,7 +32,7 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     );
   };
 
-  getById = async (id: string): Promise =>
+  getByID = async (id: string): Promise =>
     await this._database.findOne(this._collectionName, { _id: id });
 
   update = async (model: DeviceAttributes): Promise =>
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index bd0fe378..685a0517 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -23,7 +23,7 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
     throw new Error('The method is not implemented.');
   };
 
-  getById = async (deviceID: string): Promise =>
+  getByID = async (deviceID: string): Promise =>
     await this._database.findOne(
       this._collectionName,
       { _id: deviceID },
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index f9694292..51909dd2 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -85,7 +85,7 @@ class UserDatabaseRepository implements IUserRepository {
   };
 
   // eslint-disable-next-line no-unused-vars
-  getById = async (id: string): Promise => {
+  getByID = async (id: string): Promise => {
     throw new Error('The method is not implemented');
   };
 
diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js
index 42e1fb0b..106364e2 100644
--- a/src/repository/UserFileRepository.js
+++ b/src/repository/UserFileRepository.js
@@ -59,7 +59,7 @@ class UserFileRepository implements IUserRepository {
   }
 
   deleteAccessToken = async (userID: string, token: string): Promise<*> => {
-    const user = await this.getById(userID);
+    const user = await this.getByID(userID);
     if (!user) {
       throw new Error('User doesn\'t exist');
     }
@@ -95,7 +95,7 @@ class UserFileRepository implements IUserRepository {
     );
 
   @memoizeGet(['id'])
-  async getById(id: string): Promise {
+  async getByID(id: string): Promise {
     return this._fileManager.getFile(`${id}.json`);
   }
 
@@ -119,7 +119,7 @@ class UserFileRepository implements IUserRepository {
     userID: string,
     tokenObject: TokenObject,
   ): Promise<*> => {
-    const user = await this.getById(userID);
+    const user = await this.getByID(userID);
 
     if (!user) {
       throw new HttpError('Could not find user for user ID');
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index ece7c3ba..9652eb7a 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -31,7 +31,7 @@ class WebhookDatabaseRepository implements IWebhookRepository {
     );
   };
 
-  getById = async (id: string): Promise =>
+  getByID = async (id: string): Promise =>
     await this._database.findOne(this._collectionName, { _id: id });
 
   update = async (): Promise => {
diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js
index a5f4d017..5c07be00 100644
--- a/src/repository/WebhookFileRepository.js
+++ b/src/repository/WebhookFileRepository.js
@@ -47,18 +47,12 @@ class WebhookFileRepository implements IWebhookRepository {
     return allData;
   };
 
-  getById = async (id: string, userID: ?string = null): Promise => {
-    const webhook = await this._getByID(id);
-
-    if (
-      !webhook ||
-      webhook.ownerID !== userID
-    ) {
-      return null;
-    }
-
-    return webhook;
-  };
+  @memoizeGet(['id'])
+  async getByID(
+    id: string,
+  ): Promise {
+    return this._fileManager.getFile(`${id}.json`);
+  }
 
   // eslint-disable-next-line no-unused-vars
   update = async (model: WebhookMutator): Promise => {
@@ -69,13 +63,6 @@ class WebhookFileRepository implements IWebhookRepository {
   async _getAll(): Promise> {
     return this._fileManager.getAllData();
   }
-
-  @memoizeGet(['id'])
-  async _getByID(
-    id: string,
-  ): Promise {
-    return this._fileManager.getFile(`${id}.json`);
-  }
 }
 
 export default WebhookFileRepository;
diff --git a/src/types.js b/src/types.js
index e45fb69a..52ad6d81 100644
--- a/src/types.js
+++ b/src/types.js
@@ -236,7 +236,7 @@ export interface IBaseRepository {
   create(model: TModel | $Shape): Promise;
   deleteByID(id: string): Promise;
   getAll(): Promise>;
-  getById(id: string, userID: ?string): Promise;
+  getByID(id: string): Promise;
   update(model: TModel): Promise;
 }
 
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index a3fe17f9..0ef3082b 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -215,7 +215,7 @@ test.serial(
   async t => {
     const deviceAttributesStub = sinon.stub(
       container.constitute('DeviceAttributeRepository'),
-      'getById',
+      'getByID',
     ).returns({ ownerID: TestData.getID()});
 
     const claimDeviceResponse = await request(app)
diff --git a/test/UserFileRepository.test.js b/test/UserFileRepository.test.js
index 2e241d1a..c012010b 100644
--- a/test/UserFileRepository.test.js
+++ b/test/UserFileRepository.test.js
@@ -13,7 +13,7 @@ test(
     const user = await repository.createWithCredentials(TestData.getUser());
     const fileManager = repository._fileManager;
     const getAllSpy = sinon.spy(fileManager, 'getAllData');
-    const getByIdSpy = sinon.spy(fileManager, 'getFile');
+    const getByIDSpy = sinon.spy(fileManager, 'getFile');
     const deleteByIDSpy = sinon.spy(fileManager, 'deleteFile');
 
     async function testAllAccessors() {
@@ -24,10 +24,10 @@ test(
       await repository.getAll();
       t.truthy(getAllSpy.callCount === testIterator);
 
-      await repository.getById(user.id);
-      t.truthy(getByIdSpy.callCount === testIterator);
-      await repository.getById(user.id);
-      t.truthy(getByIdSpy.callCount === testIterator);
+      await repository.getByID(user.id);
+      t.truthy(getByIDSpy.callCount === testIterator);
+      await repository.getByID(user.id);
+      t.truthy(getByIDSpy.callCount === testIterator);
 
       await repository.getByUsername(user.username);
       t.truthy(getAllSpy.callCount === testIterator);

From bb0a20bb917feb7c94104c0752b618fb9d476d0d Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 7 Jun 2017 00:32:53 +0200
Subject: [PATCH 390/504] fix OauthModel and `accessTokenExpiresAt` must be a
 Date instance' error.

---
 dist/OAuthModel.js | 9 ++++++---
 src/OAuthModel.js  | 2 +-
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/dist/OAuthModel.js b/dist/OAuthModel.js
index ff79d906..045c8ddb 100644
--- a/dist/OAuthModel.js
+++ b/dist/OAuthModel.js
@@ -8,6 +8,10 @@ var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
@@ -62,10 +66,9 @@ var OauthModel = function OauthModel(userRepository) {
               return _context.abrupt('return', null);
 
             case 8:
-              return _context.abrupt('return', {
-                accessToken: userTokenObject.accessToken,
+              return _context.abrupt('return', (0, _extends3.default)({}, userTokenObject, {
                 user: user
-              });
+              }));
 
             case 9:
             case 'end':
diff --git a/src/OAuthModel.js b/src/OAuthModel.js
index bfc05431..31e75f0a 100644
--- a/src/OAuthModel.js
+++ b/src/OAuthModel.js
@@ -34,7 +34,7 @@ class OauthModel {
     }
 
     return {
-      accessToken: userTokenObject.accessToken,
+      ...userTokenObject,
       user,
     };
   };

From f4c1b7664dbde481f20ad0da6f00791e3b4f202b Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 7 Jun 2017 00:53:34 +0200
Subject: [PATCH 391/504] don't replace _id with deviceID in database repos,
 but use deviceID as query.

---
 dist/managers/PermissionManager.js            | 286 ++++++++++++++++++
 .../DeviceAttributeDatabaseRepository.js      |  11 +-
 .../repository/DeviceKeyDatabaseRepository.js |   9 +-
 .../DeviceAttributeDatabaseRepository.js      |  13 +-
 src/repository/DeviceKeyDatabaseRepository.js |  10 +-
 5 files changed, 310 insertions(+), 19 deletions(-)
 create mode 100644 dist/managers/PermissionManager.js

diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
new file mode 100644
index 00000000..8e9c1721
--- /dev/null
+++ b/dist/managers/PermissionManager.js
@@ -0,0 +1,286 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _map = require('babel-runtime/core-js/map');
+
+var _map2 = _interopRequireDefault(_map);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _nullthrows = require('nullthrows');
+
+var _nullthrows2 = _interopRequireDefault(_nullthrows);
+
+var _oauth2Server = require('oauth2-server');
+
+var _HttpError = require('../lib/HttpError');
+
+var _HttpError2 = _interopRequireDefault(_HttpError);
+
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
+var _settings = require('../settings');
+
+var _settings2 = _interopRequireDefault(_settings);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var PermissionManager = function PermissionManager(deviceAttributeRepository, userRepository, webhookRepository, oauthServer) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, PermissionManager);
+  this._repositoriesByEntityName = new _map2.default();
+
+  this.checkPermissionsForEntityByID = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(entityName, id) {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return _this.getEntityByID(entityName, id);
+
+            case 2:
+              return _context.abrupt('return', !!_context.sent);
+
+            case 3:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x, _x2) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.getAllEntitiesForCurrentUser = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(entityName) {
+      var currentUser;
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              currentUser = _this._userRepository.getCurrentUser();
+              _context2.next = 3;
+              return (0, _nullthrows2.default)(_this._repositoriesByEntityName.get(entityName)).getAll(currentUser.id);
+
+            case 3:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 4:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x3) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.getEntityByID = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(entityName, id) {
+      var entity;
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              _context3.next = 2;
+              return (0, _nullthrows2.default)(_this._repositoriesByEntityName.get(entityName)).getByID(id);
+
+            case 2:
+              entity = _context3.sent;
+
+              if (entity) {
+                _context3.next = 5;
+                break;
+              }
+
+              return _context3.abrupt('return', null);
+
+            case 5:
+              if (_this._doesUserHaveAccess(entity.ownerID)) {
+                _context3.next = 7;
+                break;
+              }
+
+              throw new _HttpError2.default('User doesn\'t have access', 403);
+
+            case 7:
+              return _context3.abrupt('return', entity);
+
+            case 8:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function (_x4, _x5) {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this._createDefaultAdminUser = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4() {
+    var token;
+    return _regenerator2.default.wrap(function _callee4$(_context4) {
+      while (1) {
+        switch (_context4.prev = _context4.next) {
+          case 0:
+            _context4.prev = 0;
+            _context4.next = 3;
+            return _this._userRepository.createWithCredentials({
+              password: _settings2.default.DEFAULT_ADMIN_PASSWORD,
+              username: _settings2.default.DEFAULT_ADMIN_USERNAME
+            }, 'administrator');
+
+          case 3:
+            _context4.next = 5;
+            return _this._generateAdminToken();
+
+          case 5:
+            token = _context4.sent;
+
+
+            _logger2.default.info('New default admin user created with token: ' + token);
+            _context4.next = 12;
+            break;
+
+          case 9:
+            _context4.prev = 9;
+            _context4.t0 = _context4['catch'](0);
+
+            _logger2.default.error('Error during default admin user creating: ' + _context4.t0);
+
+          case 12:
+          case 'end':
+            return _context4.stop();
+        }
+      }
+    }, _callee4, _this, [[0, 9]]);
+  }));
+
+  this._doesUserHaveAccess = function (ownerID) {
+    var currentUser = _this._userRepository.getCurrentUser();
+    return currentUser.role === 'administrator' || currentUser.id === ownerID;
+  };
+
+  this._generateAdminToken = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+    var request, response, tokenPayload;
+    return _regenerator2.default.wrap(function _callee5$(_context5) {
+      while (1) {
+        switch (_context5.prev = _context5.next) {
+          case 0:
+            request = new _oauth2Server.Request({
+              body: {
+                client_id: 'spark-server',
+                client_secret: 'spark-server',
+                grant_type: 'password',
+                password: _settings2.default.DEFAULT_ADMIN_PASSWORD,
+                username: _settings2.default.DEFAULT_ADMIN_USERNAME
+              },
+              headers: {
+                'content-type': 'application/x-www-form-urlencoded',
+                'transfer-encoding': 'chunked'
+              },
+              method: 'POST',
+              query: {}
+            });
+            response = new _oauth2Server.Response({ body: {}, headers: {} });
+            _context5.next = 4;
+            return _this._oauthServer.server.token(request, response,
+            // oauth server doesn't allow us to use infinite access token
+            // so we pass some big value here
+            { accessTokenLifetime: 9999999999 });
+
+          case 4:
+            tokenPayload = _context5.sent;
+            return _context5.abrupt('return', tokenPayload.accessToken);
+
+          case 6:
+          case 'end':
+            return _context5.stop();
+        }
+      }
+    }, _callee5, _this);
+  }));
+  this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+    var defaultAdminUser;
+    return _regenerator2.default.wrap(function _callee6$(_context6) {
+      while (1) {
+        switch (_context6.prev = _context6.next) {
+          case 0:
+            _context6.next = 2;
+            return _this._userRepository.getByUsername(_settings2.default.DEFAULT_ADMIN_USERNAME);
+
+          case 2:
+            defaultAdminUser = _context6.sent;
+
+            if (!defaultAdminUser) {
+              _context6.next = 7;
+              break;
+            }
+
+            _logger2.default.info('Default admin accessToken: ' + defaultAdminUser.accessTokens[0].accessToken);
+            _context6.next = 9;
+            break;
+
+          case 7:
+            _context6.next = 9;
+            return _this._createDefaultAdminUser();
+
+          case 9:
+          case 'end':
+            return _context6.stop();
+        }
+      }
+    }, _callee6, _this);
+  }));
+
+  this._userRepository = userRepository;
+  this._repositoriesByEntityName.set('deviceAttributes', deviceAttributeRepository);
+  this._repositoriesByEntityName.set('webhook', webhookRepository);
+  this._oauthServer = oauthServer;
+
+  (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
+    return _regenerator2.default.wrap(function _callee7$(_context7) {
+      while (1) {
+        switch (_context7.prev = _context7.next) {
+          case 0:
+            _context7.next = 2;
+            return _this._init();
+
+          case 2:
+            return _context7.abrupt('return', _context7.sent);
+
+          case 3:
+          case 'end':
+            return _context7.stop();
+        }
+      }
+    }, _callee7, _this);
+  }))();
+};
+
+exports.default = PermissionManager;
\ No newline at end of file
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 4ca0bd3f..cd4cdea4 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -22,6 +22,7 @@ var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+// getByID, deleteByID and update uses model.deviceID as ID for querying
 var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseRepository(database, permissionManager) {
   var _this = this;
 
@@ -43,13 +44,13 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
   }));
 
   this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
+              return _this._database.remove(_this._collectionName, { deviceID: deviceID });
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
@@ -96,13 +97,13 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
   }();
 
   this.getByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id });
+              return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
 
             case 2:
               return _context4.abrupt('return', _context4.sent);
@@ -127,7 +128,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { _id: model.deviceID, timeStamp: new Date() }) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { deviceID: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { timeStamp: new Date() }) }, { new: true, upsert: true });
 
             case 2:
               return _context5.abrupt('return', _context5.sent);
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index 8473b1b7..f7dfb35c 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -22,6 +22,7 @@ var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+// getByID, deleteByID and update uses model.deviceID as ID for querying
 var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database) {
   var _this = this;
 
@@ -54,13 +55,13 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
   }();
 
   this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
+              return _this._database.remove(_this._collectionName, { deviceID: deviceID });
 
             case 2:
               return _context2.abrupt('return', _context2.sent);
@@ -100,7 +101,7 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: deviceID });
+              return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
 
             case 2:
               return _context4.abrupt('return', _context4.sent);
@@ -125,7 +126,7 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: model.deviceID }, null, { $set: (0, _extends3.default)({}, model) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { deviceID: model.deviceID }, null, { $set: (0, _extends3.default)({}, model) }, { new: true, upsert: true });
 
             case 2:
               return _context5.abrupt('return', _context5.sent);
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 288d0e1c..f4d04368 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -6,6 +6,7 @@ import type {
   IDeviceAttributeRepository,
 } from '../types';
 
+// getByID, deleteByID and update uses model.deviceID as ID for querying
 class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
   _database: IBaseDatabase;
   _collectionName: string = 'deviceAttributes';
@@ -20,8 +21,8 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     throw new Error('The method is not implemented');
   };
 
-  deleteByID = async (id: string): Promise =>
-    await this._database.remove(this._collectionName, { _id: id });
+  deleteByID = async (deviceID: string): Promise =>
+    await this._database.remove(this._collectionName, { deviceID });
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
@@ -32,15 +33,15 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     );
   };
 
-  getByID = async (id: string): Promise =>
-    await this._database.findOne(this._collectionName, { _id: id });
+  getByID = async (deviceID: string): Promise =>
+    await this._database.findOne(this._collectionName, { deviceID });
 
   update = async (model: DeviceAttributes): Promise =>
     await this._database.findAndModify(
       this._collectionName,
-      { _id: model.deviceID },
+      { deviceID: model.deviceID },
       null,
-      { $set: { ...model, _id: model.deviceID, timeStamp: new Date() } },
+      { $set: { ...model, timeStamp: new Date() } },
       { new: true, upsert: true },
     );
 }
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index 685a0517..ca3e85cc 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -2,6 +2,8 @@
 
 import type { DeviceKeyObject, IBaseDatabase, IDeviceKeyRepository } from '../types';
 
+
+// getByID, deleteByID and update uses model.deviceID as ID for querying
 class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
   _database: IBaseDatabase;
   _collectionName: string = 'deviceKeys';
@@ -16,8 +18,8 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
       { _id: model.deviceID, ...model },
     );
 
-  deleteByID = async (id: string): Promise =>
-    await this._database.remove(this._collectionName, { _id: id });
+  deleteByID = async (deviceID: string): Promise =>
+    await this._database.remove(this._collectionName, { deviceID });
 
   getAll = async (): Promise> => {
     throw new Error('The method is not implemented.');
@@ -26,13 +28,13 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
   getByID = async (deviceID: string): Promise =>
     await this._database.findOne(
       this._collectionName,
-      { _id: deviceID },
+      { deviceID },
     );
 
   update = async (model: DeviceKeyObject): Promise =>
     await this._database.findAndModify(
       this._collectionName,
-      { _id: model.deviceID },
+      { deviceID: model.deviceID },
       null,
       { $set: { ...model } },
       { new: true, upsert: true },

From 6076862179ef4a58aa0cb3b8be70e58fd81ae293 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 7 Jun 2017 01:09:40 +0200
Subject: [PATCH 392/504] clean Flow types

---
 dist/controllers/ProvisioningController.js | 11 +++++-
 src/OAuthModel.js                          |  8 ++---
 src/controllers/DeviceClaimsController.js  |  3 +-
 src/controllers/DevicesController.js       |  3 +-
 src/controllers/ProductsController.js      |  2 +-
 src/controllers/ProvisioningController.js  |  6 +++-
 src/types.js                               | 41 ----------------------
 7 files changed, 24 insertions(+), 50 deletions(-)

diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js
index 62a3eabd..4c2fa828 100644
--- a/dist/controllers/ProvisioningController.js
+++ b/dist/controllers/ProvisioningController.js
@@ -123,9 +123,18 @@ var ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
 
               case 4:
                 device = _context.sent;
+
+                if (device) {
+                  _context.next = 7;
+                  break;
+                }
+
+                throw new _HttpError2.default('Provisioning error');
+
+              case 7:
                 return _context.abrupt('return', this.ok((0, _deviceToAPI2.default)(device)));
 
-              case 6:
+              case 8:
               case 'end':
                 return _context.stop();
             }
diff --git a/src/OAuthModel.js b/src/OAuthModel.js
index bfc05431..dbcbb9ca 100644
--- a/src/OAuthModel.js
+++ b/src/OAuthModel.js
@@ -4,17 +4,17 @@ import oauthClients from './oauthClients.json';
 
 import type {
   Client,
+  IUserRepository,
   TokenObject,
   User,
-  UserRepository,
 } from './types';
 
 const OAUTH_CLIENTS = oauthClients;
 
 class OauthModel {
-  _userRepository: UserRepository;
+  _userRepository: IUserRepository;
 
-  constructor(userRepository: UserRepository) {
+  constructor(userRepository: IUserRepository) {
     this._userRepository = userRepository;
   }
 
@@ -34,7 +34,7 @@ class OauthModel {
     }
 
     return {
-      accessToken: userTokenObject.accessToken,
+      ...userTokenObject,
       user,
     };
   };
diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js
index cde6174a..96b94037 100644
--- a/src/controllers/DeviceClaimsController.js
+++ b/src/controllers/DeviceClaimsController.js
@@ -1,7 +1,8 @@
 // @flow
 
-import type { Device, DeviceManager } from '../types';
 import type { ClaimCodeManager } from 'spark-protocol';
+import type DeviceManager from '../managers/DeviceManager';
+import type { Device } from '../types';
 
 import Controller from './Controller';
 import httpVerb from '../decorators/httpVerb';
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index 4e38071b..6ad11a32 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -1,6 +1,7 @@
 // @flow
 
-import type { Device, DeviceManager } from '../types';
+import type DeviceManager from '../managers/DeviceManager';
+import type { Device } from '../types';
 import type { DeviceAPIType } from '../lib/deviceToAPI';
 
 import nullthrows from 'nullthrows';
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 0d8ed59d..2ea2632a 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -1,7 +1,7 @@
 // @flow
 /* eslint-disable */
 
-import type { DeviceManager } from '../types';
+import type DeviceManager from '../managers/DeviceManager';
 
 import Controller from './Controller';
 import httpVerb from '../decorators/httpVerb';
diff --git a/src/controllers/ProvisioningController.js b/src/controllers/ProvisioningController.js
index 3f4a94c4..fc520444 100644
--- a/src/controllers/ProvisioningController.js
+++ b/src/controllers/ProvisioningController.js
@@ -1,6 +1,6 @@
 // @flow
 
-import type { DeviceManager } from '../types';
+import type DeviceManager from '../managers/DeviceManager';
 
 import Controller from './Controller';
 import httpVerb from '../decorators/httpVerb';
@@ -39,6 +39,10 @@ class ProvisioningController extends Controller {
       postBody.alogrithm,
     );
 
+    if (!device) {
+      throw new HttpError('Provisioning error');
+    }
+
     return this.ok(deviceToAPI(device));
   }
 }
diff --git a/src/types.js b/src/types.js
index 52ad6d81..1a664c4f 100644
--- a/src/types.js
+++ b/src/types.js
@@ -132,24 +132,6 @@ export type Device = DeviceAttributes & {
   variables?: ?Object,
 };
 
-export type Repository = {
-  create: (model: TModel | $Shape) => Promise,
-  deleteById: (id: string) => Promise,
-  getAll: () => Promise>,
-  getById: (id: string) => Promise,
-  update: (model: TModel) => Promise,
-};
-
-export type UserRepository = Repository & {
-  createWithCredentials(credentials: UserCredentials): Promise,
-  deleteAccessToken(userID: string, accessToken: string): Promise,
-  getByAccessToken(accessToken: string): Promise,
-  getByUsername(username: string): Promise,
-  isUserNameInUse(username: string): Promise,
-  saveAccessToken(userID: string, tokenObject: TokenObject): Promise,
-  validateLogin(username: string, password: string): Promise,
-};
-
 export type Settings = {
   ACCESS_TOKEN_LIFETIME: number,
   API_TIMEOUT: number,
@@ -184,29 +166,6 @@ export type Settings = {
   WEBHOOKS_DIRECTORY: string,
 };
 
-export type DeviceAttributeRepository = Repository & {
-  doesUserHaveAccess(deviceID: string, userID: string): Promise,
-};
-
-export type DeviceManager = {
-  callFunction(
-    deviceID: string,
-    functionName: string,
-    functionArguments: {[key: string]: string},
-  ): Promise<*>,
-  claimDevice(deviceID: string, userID: string): Promise,
-  flashBinary(deviceID: string, files: File): Promise<*>,
-  flashKnownApp(deviceID: string, app: string): Promise<*>,
-  getAll(): Promise>,
-  getByID(deviceID: string): Promise,
-  getDetailsByID(deviceID: string): Promise<*>,
-  getVariableValue(deviceID: string, varName: string): Promise,
-  provision(deviceID: string, publicKey: string): Promise<*>,
-  raiseYourHand(deviceID: string, shouldShowSignal: boolean): Promise,
-  renameDevice(deviceID: string, name: string): Promise,
-  unclaimDevice(deviceID: string): Promise,
-};
-
 export type RequestOptions = {
   auth?: { password: string, username: string },
   body: ?Object,

From 5b13ae5c6510b83a4fc43c460eb63e852bb52098 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Tue, 6 Jun 2017 18:11:07 -0700
Subject: [PATCH 393/504] Allow passing in pre-created express app to do custom
 routing.

---
 dist/app.js | 4 ++--
 src/app.js  | 3 ++-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/dist/app.js b/dist/app.js
index 3364ac19..ef1a1b66 100644
--- a/dist/app.js
+++ b/dist/app.js
@@ -22,8 +22,8 @@ var _RouteConfig2 = _interopRequireDefault(_RouteConfig);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-exports.default = function (container, settings) {
-  var app = (0, _express2.default)();
+exports.default = function (container, settings, existingApp) {
+  var app = existingApp || (0, _express2.default)();
 
   var setCORSHeaders = function setCORSHeaders(request, response, next) {
     if (request.method === 'OPTIONS') {
diff --git a/src/app.js b/src/app.js
index 3e726b29..a765a200 100644
--- a/src/app.js
+++ b/src/app.js
@@ -18,8 +18,9 @@ import routeConfig from './RouteConfig';
 export default (
   container: Container,
   settings: Settings,
+  existingApp?: express,
 ): $Application => {
-  const app = express();
+  const app = existingApp || express();
 
   const setCORSHeaders: Middleware = (
     request: $Request,

From d5d984bc6820766039d6b91e85acc013f3707586 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 7 Jun 2017 03:11:42 +0200
Subject: [PATCH 394/504] add deviceKeys migration to migrateScript

---
 dist/scripts/migrateFilesToDatabase.js | 51 +++++++++++++++++++-------
 src/scripts/migrateFilesToDatabase.js  | 41 +++++++++++++++++----
 2 files changed, 71 insertions(+), 21 deletions(-)

diff --git a/dist/scripts/migrateFilesToDatabase.js b/dist/scripts/migrateFilesToDatabase.js
index 8a814ea3..9d5f29f5 100644
--- a/dist/scripts/migrateFilesToDatabase.js
+++ b/dist/scripts/migrateFilesToDatabase.js
@@ -90,12 +90,17 @@ var setupDatabase = function () {
 }();
 
 var getFiles = function getFiles(directoryPath) {
+  var fileExtension = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '.json';
+
   var fileNames = _fs2.default.readdirSync(directoryPath).filter(function (fileName) {
-    return fileName.endsWith('.json');
+    return fileName.endsWith(fileExtension);
   });
 
   return fileNames.map(function (fileName) {
-    return _fs2.default.readFileSync(directoryPath + '/' + fileName);
+    return {
+      fileBuffer: _fs2.default.readFileSync(directoryPath + '/' + fileName),
+      fileName: fileName
+    };
   });
 };
 
@@ -142,7 +147,7 @@ var insertItem = function insertItem(database, collectionName) {
       }, _callee2, undefined);
     }));
 
-    return function (_x) {
+    return function (_x2) {
       return _ref3.apply(this, arguments);
     };
   }();
@@ -180,7 +185,7 @@ var insertUsers = function () {
                 }, _callee3, undefined);
               }));
 
-              return function (_x4) {
+              return function (_x5) {
                 return _ref5.apply(this, arguments);
               };
             }()));
@@ -196,7 +201,7 @@ var insertUsers = function () {
     }, _callee4, undefined);
   }));
 
-  return function insertUsers(_x2, _x3) {
+  return function insertUsers(_x3, _x4) {
     return _ref4.apply(this, arguments);
   };
 }();
@@ -218,37 +223,57 @@ var insertUsers = function () {
 
           console.log('Start migration to ' + DATABASE_TYPE);
 
-          users = getFiles(_settings2.default.USERS_DIRECTORY).map(parseFile);
+          users = getFiles(_settings2.default.USERS_DIRECTORY).map(function (_ref7) {
+            var fileBuffer = _ref7.fileBuffer;
+            return parseFile(fileBuffer);
+          });
           _context5.next = 9;
           return insertUsers(database, users);
 
         case 9:
           userIDsMap = _context5.sent;
           _context5.next = 12;
-          return _promise2.default.all(getFiles(_settings2.default.WEBHOOKS_DIRECTORY).map(parseFile).map(mapOwnerID(userIDsMap)).map(filterID).map(insertItem(database, 'webhooks')));
+          return _promise2.default.all(getFiles(_settings2.default.WEBHOOKS_DIRECTORY).map(function (_ref8) {
+            var fileBuffer = _ref8.fileBuffer;
+            return parseFile(fileBuffer);
+          }).map(mapOwnerID(userIDsMap)).map(filterID).map(insertItem(database, 'webhooks')));
 
         case 12:
           _context5.next = 14;
-          return _promise2.default.all(getFiles(_settings2.default.DEVICE_DIRECTORY).map(parseFile).map(mapOwnerID(userIDsMap)).map(translateDeviceID).map(filterID).map(insertItem(database, 'deviceAttributes')));
+          return _promise2.default.all(getFiles(_settings2.default.DEVICE_DIRECTORY).map(function (_ref9) {
+            var fileBuffer = _ref9.fileBuffer;
+            return parseFile(fileBuffer);
+          }).map(mapOwnerID(userIDsMap)).map(translateDeviceID).map(filterID).map(insertItem(database, 'deviceAttributes')));
 
         case 14:
+          _context5.next = 16;
+          return _promise2.default.all(getFiles(_settings2.default.DEVICE_DIRECTORY, '.pub.pem').map(function (_ref10) {
+            var fileName = _ref10.fileName,
+                fileBuffer = _ref10.fileBuffer;
+            return {
+              deviceID: fileName.substring(0, fileName.indexOf('.pub.pem')),
+              key: fileBuffer.toString()
+            };
+          }).map(insertItem(database, 'deviceKeys')));
+
+        case 16:
 
           console.log('All files migrated to the database successfully!');
           process.exit(0);
-          _context5.next = 22;
+          _context5.next = 24;
           break;
 
-        case 18:
-          _context5.prev = 18;
+        case 20:
+          _context5.prev = 20;
           _context5.t0 = _context5['catch'](0);
 
           console.log(_context5.t0);
           process.exit(1);
 
-        case 22:
+        case 24:
         case 'end':
           return _context5.stop();
       }
     }
-  }, _callee5, undefined, [[0, 18]]);
+  }, _callee5, undefined, [[0, 20]]);
 }))();
\ No newline at end of file
diff --git a/src/scripts/migrateFilesToDatabase.js b/src/scripts/migrateFilesToDatabase.js
index b35f5cb0..946b520a 100644
--- a/src/scripts/migrateFilesToDatabase.js
+++ b/src/scripts/migrateFilesToDatabase.js
@@ -1,5 +1,7 @@
 // @flow
 
+import type { DeviceKeyObject } from '../types';
+
 import fs from 'fs';
 import settings from '../settings';
 import { MongoClient, ObjectId } from 'mongodb';
@@ -10,6 +12,11 @@ type DatabaseType = 'mongo' | 'tingo';
 
 type Database = TingoDb | MongoDb;
 
+type FileObject = {
+  fileName: string,
+  fileBuffer: Buffer,
+};
+
 const DATABASE_TYPE: DatabaseType = ((process.argv[2]): any);
 
 const setupDatabase = async (): Promise => {
@@ -24,13 +31,17 @@ const setupDatabase = async (): Promise => {
   throw new Error('Wrong database type');
 };
 
-const getFiles = (directoryPath: string): Array => {
+const getFiles = (
+  directoryPath: string,
+  fileExtension?: string = '.json',
+): Array => {
   const fileNames = fs.readdirSync(directoryPath)
-    .filter((fileName: string): boolean => fileName.endsWith('.json'));
+    .filter((fileName: string): boolean => fileName.endsWith(fileExtension));
 
-  return fileNames.map((fileName: string): Buffer =>
-    fs.readFileSync(`${directoryPath}/${fileName}`),
-  );
+  return fileNames.map((fileName: string): FileObject => ({
+    fileBuffer: fs.readFileSync(`${directoryPath}/${fileName}`),
+    fileName,
+  }));
 };
 
 const parseFile = (file: Buffer): Object => JSON.parse(file.toString());
@@ -74,25 +85,39 @@ const insertUsers = async (
     console.log(`Start migration to ${DATABASE_TYPE}`);
 
     const users = getFiles(settings.USERS_DIRECTORY)
-      .map(parseFile);
+      .map(
+        ({ fileBuffer }: FileObject): Object => parseFile(fileBuffer),
+      );
 
     const userIDsMap = await insertUsers(database, users);
 
     await Promise.all(getFiles(settings.WEBHOOKS_DIRECTORY)
-      .map(parseFile)
+      .map(
+        ({ fileBuffer }: FileObject): Object => parseFile(fileBuffer),
+      )
       .map(mapOwnerID(userIDsMap))
       .map(filterID)
       .map(insertItem(database, 'webhooks')),
     );
 
     await Promise.all(getFiles(settings.DEVICE_DIRECTORY)
-      .map(parseFile)
+      .map(
+        ({ fileBuffer }: FileObject): Object => parseFile(fileBuffer),
+      )
       .map(mapOwnerID(userIDsMap))
       .map(translateDeviceID)
       .map(filterID)
       .map(insertItem(database, 'deviceAttributes')),
     );
 
+    await Promise.all(getFiles(settings.DEVICE_DIRECTORY, '.pub.pem')
+      .map(({ fileName, fileBuffer }: FileObject): DeviceKeyObject => ({
+        deviceID: fileName.substring(0, fileName.indexOf('.pub.pem')),
+        key: fileBuffer.toString(),
+      }))
+      .map(insertItem(database, 'deviceKeys')),
+    );
+
     console.log('All files migrated to the database successfully!');
     process.exit(0);
   } catch (error) {

From 0417a2617cfe1debf102dc583c7825be2dc5b452 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Wed, 7 Jun 2017 06:43:41 -0700
Subject: [PATCH 395/504] Create azure-mongodb.js

---
 examples/azure-mongodb.js | 135 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 examples/azure-mongodb.js

diff --git a/examples/azure-mongodb.js b/examples/azure-mongodb.js
new file mode 100644
index 00000000..20a4509e
--- /dev/null
+++ b/examples/azure-mongodb.js
@@ -0,0 +1,135 @@
+/*
+* In this example we are using Azure storage for our SSL keys +
+* MongoDB (Azure Cosmos DB)
+*
+* If you are using Cosmos and aren't replicating your data, be
+* sure to remove the `replicaSet=globaldb` from your connection
+* string.
+*/
+
+// @flow
+
+import arrayFlatten from 'array-flatten';
+import azure from 'azure-storage';
+import { Container } from 'constitute';
+import express from 'express';
+import http from 'http';
+import https from 'https';
+import {MongoClient} from 'mongodb';
+import os from 'os';
+import path from 'path';
+import settings from './settings';
+import { MongoDb, createApp, defaultBindings, logger } from 'spark-server';
+
+const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
+const useHttp = NODE_PORT !== 443;
+
+process.on('uncaughtException', (exception: Error) => {
+  logger.error(
+    'uncaughtException',
+    { message: exception.message, stack: exception.stack },
+  ); // logging with MetaData
+  process.exit(1); // exit with failure
+});
+
+const container = new Container();
+defaultBindings(container, settings);
+
+function promisify(
+  call: (serviceCallback: (error: StorageError, result: T) => void) => void,
+): Promise {
+  return new Promise((resolve, reject) => {
+    try {
+      call((error, result) => {
+        if (error) {
+          reject(error);
+          return;
+        }
+        resolve(result);
+      });
+    } catch (err) {
+      reject(err);
+    }
+  });
+}
+const blobService = azure.createBlobService(
+  settings.AZURE_STORAGE_CONNECTION_KEY,
+);
+
+MongoClient.connect(
+  settings.DB_CONFIG.URL,
+  {
+    // retry to connect for 60 times
+    reconnectTries: 60,
+    // wait 1 second before retrying
+    reconnectInterval: 1000
+  },
+).then(
+  async (database: Object): Promise => {
+    try {
+      const sslCertificate = await promisify(
+        cb => blobService.getBlobToText('keys', settings.SSL_CERTIFICATE, cb),
+      );
+      const privateKey = await promisify(
+        cb => blobService.getBlobToText('keys', settings.SSL_PRIVATE_KEY, cb),
+      );
+
+      database.on('error', function(err) {
+          console.log("DB connection Error: "+err);
+      });
+      database.on('open', function() {
+          console.log("DB connected");
+      });
+      database.on('close', function(str) {
+          console.log("DB disconnected: "+str);
+      });
+
+      const options = {
+        cert: sslCertificate,
+        key: privateKey,
+        // This is necessary only if using the client certificate authentication.
+        requestCert: true,
+      };
+
+      container.bindValue('DATABASE_CONNECTION', database);
+      container.bindClass(
+        'Database',
+        MongoDb,
+        ['DATABASE_CONNECTION'],
+      );
+
+      const deviceServer = container.constitute('DeviceServer');
+      deviceServer.start();
+
+      const app = express();
+      createApp(container, settings, app);
+
+      (useHttp
+        ? http.createServer(app)
+        : https.createServer(options, app))
+        .listen(
+          NODE_PORT,
+          (): void =>
+            console.log(`express server started on port ${NODE_PORT}`),
+        );
+
+      const addresses = arrayFlatten(
+        Object.entries(os.networkInterfaces()).map(
+          // eslint-disable-next-line no-unused-vars
+          ([name, nic]: [string, mixed]): Array =>
+            (nic: any)
+              .filter((address: Object): boolean =>
+                address.family === 'IPv4' &&
+                address.address !== '127.0.0.1',
+              )
+              .map((address: Object): boolean => address.address),
+        ),
+      );
+      addresses.forEach((address: string): void =>
+        console.log(`Your device server IP address is: ${address}`),
+      );
+    } catch (error) {
+      console.log('SSL ERROR', error);
+    }
+  },
+);

From 710d1b2925190d44f81082fa12e690e91261924f Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 7 Jun 2017 22:40:07 +0200
Subject: [PATCH 396/504] disable eslint in azure example

---
 examples/azure-mongodb.js | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/examples/azure-mongodb.js b/examples/azure-mongodb.js
index 20a4509e..80dbcb3f 100644
--- a/examples/azure-mongodb.js
+++ b/examples/azure-mongodb.js
@@ -7,7 +7,7 @@
 * string.
 */
 
-// @flow
+/* eslint-disable */
 
 import arrayFlatten from 'array-flatten';
 import azure from 'azure-storage';
@@ -15,7 +15,7 @@ import { Container } from 'constitute';
 import express from 'express';
 import http from 'http';
 import https from 'https';
-import {MongoClient} from 'mongodb';
+import { MongoClient } from 'mongodb';
 import os from 'os';
 import path from 'path';
 import settings from './settings';
@@ -62,7 +62,7 @@ MongoClient.connect(
     // retry to connect for 60 times
     reconnectTries: 60,
     // wait 1 second before retrying
-    reconnectInterval: 1000
+    reconnectInterval: 1000,
   },
 ).then(
   async (database: Object): Promise => {
@@ -74,14 +74,14 @@ MongoClient.connect(
         cb => blobService.getBlobToText('keys', settings.SSL_PRIVATE_KEY, cb),
       );
 
-      database.on('error', function(err) {
-          console.log("DB connection Error: "+err);
+      database.on('error', (err) => {
+        console.log(`DB connection Error: ${err}`);
       });
-      database.on('open', function() {
-          console.log("DB connected");
+      database.on('open', () => {
+        console.log('DB connected');
       });
-      database.on('close', function(str) {
-          console.log("DB disconnected: "+str);
+      database.on('close', (str) => {
+        console.log(`DB disconnected: ${str}`);
       });
 
       const options = {

From ee1db310955aac487b079482877379ae252a1ba7 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 7 Jun 2017 22:41:43 +0200
Subject: [PATCH 397/504] fix package-lock.json

---
 package-lock.json | 131 +++++++++++++---------------------------------
 1 file changed, 35 insertions(+), 96 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index f4cfd1d1..3e560355 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -78,11 +78,6 @@
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
       "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
     },
-    "any-promise": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
-      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
-    },
     "anymatch": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
@@ -256,8 +251,7 @@
     "babel-helper-bindify-decorators": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
-      "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=",
-      "dev": true
+      "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA="
     },
     "babel-helper-builder-binary-assignment-operator-visitor": {
       "version": "6.24.1",
@@ -286,8 +280,7 @@
     "babel-helper-explode-class": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz",
-      "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=",
-      "dev": true
+      "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes="
     },
     "babel-helper-function-name": {
       "version": "6.24.1",
@@ -397,8 +390,7 @@
     "babel-plugin-syntax-decorators": {
       "version": "6.13.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
-      "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
-      "dev": true
+      "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs="
     },
     "babel-plugin-syntax-do-expressions": {
       "version": "6.13.0",
@@ -475,8 +467,7 @@
     "babel-plugin-transform-decorators": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz",
-      "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=",
-      "dev": true
+      "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0="
     },
     "babel-plugin-transform-decorators-legacy": {
       "version": "1.3.4",
@@ -922,11 +913,6 @@
       "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
       "dev": true
     },
-    "camel-case": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-1.2.2.tgz",
-      "integrity": "sha1-Gsp8TRlTWaLOmVV5NDPG5VQlEfI="
-    },
     "camelcase": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
@@ -1402,9 +1388,9 @@
       "dev": true
     },
     "es5-ext": {
-      "version": "0.10.22",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.22.tgz",
-      "integrity": "sha512-YXTXSlZkJsVwMEVljp1Bh5P9+Raa3524OMl9kywGMp1aazKTCnAqORRL/8dkuqNHk+LRYe0LezuS8PlUt3+mOw=="
+      "version": "0.10.23",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.23.tgz",
+      "integrity": "sha1-dXi1G+l0IHpUh4IbVlOMIk5Oezg="
     },
     "es6-iterator": {
       "version": "2.0.1",
@@ -1673,9 +1659,9 @@
       }
     },
     "express-oauth-server": {
-      "version": "2.0.0-b1",
-      "resolved": "https://registry.npmjs.org/express-oauth-server/-/express-oauth-server-2.0.0-b1.tgz",
-      "integrity": "sha1-Gj3/oy8b/OBKiKSTIOL+ixP3cZI="
+      "version": "2.0.0-b3",
+      "resolved": "https://registry.npmjs.org/express-oauth-server/-/express-oauth-server-2.0.0-b3.tgz",
+      "integrity": "sha1-+Mgw2/kSkc6pJ+YdYD7C7Rr+fAw="
     },
     "extend": {
       "version": "3.0.1",
@@ -2467,9 +2453,9 @@
       "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg="
     },
     "globals": {
-      "version": "9.17.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-9.17.0.tgz",
-      "integrity": "sha1-DAymltm5u2lNLlRwvTd3fKrVAoY="
+      "version": "9.18.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+      "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ=="
     },
     "globby": {
       "version": "6.1.0",
@@ -3127,11 +3113,6 @@
       "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
       "dev": true
     },
-    "lower-case": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
-      "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
-    },
     "lowercase-keys": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
@@ -3139,9 +3120,9 @@
       "dev": true
     },
     "lru-cache": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
-      "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz",
+      "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==",
       "dev": true
     },
     "lru-queue": {
@@ -3449,21 +3430,9 @@
       "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
     },
     "oauth2-server": {
-      "version": "3.0.0-b3.1",
-      "resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.0-b3.1.tgz",
-      "integrity": "sha1-DmCDK7BBRr0xcOCB4J5kGy70Fj4=",
-      "dependencies": {
-        "bluebird": {
-          "version": "2.11.0",
-          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
-          "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
-        },
-        "lodash": {
-          "version": "3.10.1",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
-          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
-        }
-      }
+      "version": "3.0.0-b4",
+      "resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.0-b4.tgz",
+      "integrity": "sha1-9915nX+JvJYR3YCvOd9SFECFa3M="
     },
     "object-assign": {
       "version": "4.1.1",
@@ -3860,9 +3829,9 @@
       "dev": true
     },
     "readable-stream": {
-      "version": "2.2.10",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.10.tgz",
-      "integrity": "sha512-HQEnnoV404e0EtwB9yNiuk2tJ+egeVC8Y9QBAxzDg8DBJt4BzRp+yQuIb/t3FIWkSTmIi+sgx7yVv/ZM0GNoqw=="
+      "version": "2.2.11",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz",
+      "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q=="
     },
     "readdirp": {
       "version": "2.1.0",
@@ -3948,9 +3917,9 @@
       }
     },
     "remove-trailing-separator": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz",
-      "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ="
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz",
+      "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE="
     },
     "repeat-element": {
       "version": "1.1.2",
@@ -4048,9 +4017,9 @@
       "integrity": "sha1-F4FZvuRXkawhYoruLau3SlLV0HI="
     },
     "safe-buffer": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.0.tgz",
-      "integrity": "sha512-aSLEDudu6OoRr/2rU609gRmnYboRLxgDG1z9o2Q0os7236FwvcqIOO8r8U5JUEwivZOhDaKlFO4SbPTJYyBEyQ=="
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+      "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
     },
     "samsam": {
       "version": "1.1.2",
@@ -4081,11 +4050,6 @@
         }
       }
     },
-    "sentence-case": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-1.1.3.tgz",
-      "integrity": "sha1-gDSq/CFFdy06vhUJqkLJ4QQtwTk="
-    },
     "serve-static": {
       "version": "1.12.3",
       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz",
@@ -4114,9 +4078,9 @@
       "dev": true
     },
     "shelljs": {
-      "version": "0.7.7",
-      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz",
-      "integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=",
+      "version": "0.7.8",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz",
+      "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
       "dev": true
     },
     "signal-exit": {
@@ -4170,7 +4134,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#7ea6c12a1a71b58725bf278a8a7c5086c7977ac6"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#109141fdf255de47c597f3689d0225a421d2393c"
     },
     "spawn-sync": {
       "version": "1.0.15",
@@ -4226,16 +4190,6 @@
       "integrity": "sha1-lAy4L8z6hOj/Lz/fKT/ngBa+zNE=",
       "dev": true
     },
-    "standard-error": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/standard-error/-/standard-error-1.1.0.tgz",
-      "integrity": "sha1-I+UWj6HAggGJ5YEnAaeQWFENDTQ="
-    },
-    "standard-http-error": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/standard-http-error/-/standard-http-error-1.2.0.tgz",
-      "integrity": "sha1-ELvTHNhvAj0wE6WCCL4344UZ/ac="
-    },
     "statuses": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
@@ -4264,9 +4218,9 @@
       "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
     },
     "string_decoder": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz",
-      "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg="
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz",
+      "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk="
     },
     "string-length": {
       "version": "1.0.1",
@@ -4383,11 +4337,6 @@
       "integrity": "sha1-AIRwUAVzDdhNt1UlPJMa45jblSI=",
       "dev": true
     },
-    "thenify": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
-      "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk="
-    },
     "through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -4577,11 +4526,6 @@
       "integrity": "sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=",
       "dev": true
     },
-    "upper-case": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
-      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
-    },
     "url-parse-lax": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
@@ -4638,11 +4582,6 @@
       "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
       "dev": true
     },
-    "validator.js": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/validator.js/-/validator.js-1.2.3.tgz",
-      "integrity": "sha1-+OYj9g5TutpUiAMzZJlwmWo0RuE="
-    },
     "vary": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz",

From b51661d95d4a8fb4e2d3f739f0e0a7cbd1110d92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Thu, 8 Jun 2017 09:37:43 +0200
Subject: [PATCH 398/504] Added a Docker Build Script and Explanation

---
 examples/docker/production/Dockerfile | 45 +++++++++++++
 examples/docker/readme.md             | 93 +++++++++++++++++++++++++++
 2 files changed, 138 insertions(+)
 create mode 100644 examples/docker/production/Dockerfile
 create mode 100644 examples/docker/readme.md

diff --git a/examples/docker/production/Dockerfile b/examples/docker/production/Dockerfile
new file mode 100644
index 00000000..b3a61500
--- /dev/null
+++ b/examples/docker/production/Dockerfile
@@ -0,0 +1,45 @@
+#
+# Local Cloud Sparc-Server  
+#
+
+#
+# based on Alpine Node (version 6 or 7 is tested, other Version should work as well)
+#
+FROM mhart/alpine-node:6
+
+# create the Working Directory
+RUN mkdir -p /usr/src/localCloud
+
+# install all dependencies and delete them in ONE single RUN
+# you can split this into different steps, but this will make the Image MUCH larger!
+RUN apk add --no-cache git; \
+    apk add --no-cache python; \
+    apk add --no-cache make; \
+    apk add --no-cache g++; \
+    cd /usr/src/localCloud; \
+    npm install -g node-gyp; \
+    git clone https://github.com/Brewskey/spark-server.git; \
+    cd /usr/src/localCloud/spark-server; \
+    npm install; \
+    npm run update-firmware; \
+    npm run prebuild; \    
+    npm run build; \    
+    apk del python make g++
+
+# Set Working Directory
+WORKDIR /usr/src/localCloud/spark-server
+
+
+# Expose SparkPort to be mapped
+EXPOSE 5683
+
+# Expose ServerPort for API
+EXPOSE 8080
+
+# Expose DataDirectory to store DB and Device Keys 
+VOLUME /usr/src/localCloud/spark-server/data
+
+ENTRYPOINT ["npm", "run", "start:prod"]
+
+
+
diff --git a/examples/docker/readme.md b/examples/docker/readme.md
new file mode 100644
index 00000000..8362ccc7
--- /dev/null
+++ b/examples/docker/readme.md
@@ -0,0 +1,93 @@
+
+
+
+
+# Build a Spark-Server as a Docker Image
+
+## 1. Install Docker
+
+[https://www.docker.com/]()
+
+## 2. Compile Image
+
+> This has to be done only once or if you want to create a uptodate Version from the Repository
+
+* navigate to the folder production
+* Start Building (note: there is a final . at end of line!)
+```shell
+docker build -t spark-server:latest .
+```
+ > **Note about Version Tag (spark-server:latest) !**   
+ Docker uses Tags and Version semantics to specify a Version of an image.  
+ Right now there is not a version of the **spark-server** to be used here.  
+So it is up to you to define a Version for different compilations !  
+I will use **latest**  as a version tag here!
+ 
+ > 
+
+The Dockerengine will now build a Image. Contents of the Image
+* Alpine Node (a very small node version)
+* Fresh Spark-Server version (using git clone https://github.com/Brewskey/spark-server.git \)
+* Dependencies (like node-gyp, gcc compiler etc.)
+
+if you run 
+```shell
+    docker images
+```
+you should see a line like 
+```shell
+spark-server          latest       e72ee8de918f    ... ago        208.3 MB
+```
+
+> Yep, the image is only 210 Mb.   
+Thats all you need to run a spark-server
+
+## 3. Use the Image
+
+The Image will expose the following EndPoints
+* PORT **5683** (COAP Port)
+* PORT **8080** (API Port)
+* DIRECTORY **/usr/src/localCloud/spark-server/data**
+
+If you want to start the image, you need a place to store all your runtime data (database files, keyfiles etc.)
+
+> Since Docker images are NOT VMWARE Machines, Docker Instances are completly stateless. So you have to bind directories to your Enviroment so the image can store persistent data
+
+Create a directory for your production (lets asume you are in **/runtime**)
+```shell
+    mkdir spark-server
+``` 
+
+navigate to this directory
+```shell
+    cd spark-server
+``` 
+
+Create a data-directory so the instance can store all runtime files in this directory
+```shell
+    mkdir data
+``` 
+
+Your working directory is now **/runtime/spark-server**
+
+To start the Server 
+
+```shell
+docker run -p 8100:8080 -p 5683:5683 --volume /runtime/spark-server/data:/usr/src/localCloud/spark-server/data --rm --name spark-server --hostname spark-server spark-server:latest
+``` 
+
+Just a short explanation what's happening
+
+* **-p 8100:8080** API Port will be available on port 8100 on HostMachine
+* **-p 5683:5683** Sparc Port is mapped to the same port on HostMachine
+* **--volume /runtime/spark-server/data:/usr/src/localCloud/spark-server/data**   
+your data directory created early is mapped to the data directory insinde the instance
+* **--rm** Instance is removed after finished
+* **--name spark-server** you can access or reference this instance using **spark-server**   
+this also ensures only one instance with this name can run at the same time!
+* **--hostname spark-server** any other running **Docker Instance** can access the Server using the hostname **spark-server**
+* **spark-server:latest** use the image tagged with **latest** for this instance
+
+## Thats all!
+
+If the Server is running you should see runtime file inside the data directory. These are also the Files to Backup your existing Spark-Server   

From c182732cad6075354846f6034076fe987d599994 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Thu, 8 Jun 2017 08:30:02 -0700
Subject: [PATCH 399/504] Added `ping` route

---
 src/controllers/EventsController.js | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js
index 58a7a0f7..d77cfdd8 100644
--- a/src/controllers/EventsController.js
+++ b/src/controllers/EventsController.js
@@ -4,6 +4,7 @@ import type { Event, EventData } from '../types';
 import type EventManager from '../managers/EventManager';
 
 import Controller from './Controller';
+import anonymous from '../decorators/anonymous';
 import route from '../decorators/route';
 import httpVerb from '../decorators/httpVerb';
 import serverSentEvents from '../decorators/serverSentEvents';
@@ -43,6 +44,16 @@ class EventsController extends Controller {
     }
   }
 
+  @httpVerb('post')
+  @route('/v1/ping')
+  @anonymous()
+  async ping(payload: Object): Promise<*> {
+    return this.ok({
+      ...payload,
+      serverPayload: Math.random(),
+    });
+  }
+
   @httpVerb('get')
   @route('/v1/events/:eventNamePrefix?*')
   @serverSentEvents()

From 9cf4983b7463f1610588fabeead1ae374dcd338a Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 9 Jun 2017 01:33:42 +0200
Subject: [PATCH 400/504] use _eventPublisher.publishAndListenForResponse
 instead coupling of deviceServer. fix tests, fix raise your hand issue.

---
 dist/controllers/DevicesController.js |  64 ++---
 dist/controllers/EventsController.js  | 102 +++++---
 dist/defaultBindings.js               |   2 +-
 dist/managers/DeviceManager.js        | 201 ++++++++------
 dist/managers/EventManager.js         |   2 +-
 dist/managers/WebhookManager.js       |   8 +-
 src/controllers/DevicesController.js  |  44 ++--
 src/defaultBindings.js                |   2 +-
 src/managers/DeviceManager.js         | 126 +++++----
 src/managers/EventManager.js          |   2 +-
 src/managers/WebhookManager.js        |   8 +-
 src/types.js                          |   1 +
 test/DeviceClaimsController.test.js   |  23 ++
 test/DevicesController.test.js        | 363 ++++++++++----------------
 test/ProvisioningController.test.js   |  23 ++
 15 files changed, 516 insertions(+), 455 deletions(-)

diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js
index 79d17924..952aa551 100644
--- a/dist/controllers/DevicesController.js
+++ b/dist/controllers/DevicesController.js
@@ -359,7 +359,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
     key: 'updateDevice',
     value: function () {
       var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) {
-        var updatedAttributes, flashStatus, file, _flashStatus;
+        var updatedAttributes, flashResult, file, _flashResult;
 
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
@@ -378,60 +378,60 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
                 return _context8.abrupt('return', this.ok({ name: updatedAttributes.name, ok: true }));
 
               case 5:
-                if (!postBody.app_id) {
-                  _context8.next = 10;
+                if (!postBody.signal) {
+                  _context8.next = 11;
                   break;
                 }
 
-                _context8.next = 8;
-                return this._deviceManager.flashKnownApp(deviceID, postBody.app_id);
+                if (['1', '0'].includes(postBody.signal)) {
+                  _context8.next = 8;
+                  break;
+                }
+
+                throw new _HttpError2.default('Wrong signal value');
 
               case 8:
-                flashStatus = _context8.sent;
-                return _context8.abrupt('return', this.ok({ id: deviceID, status: flashStatus }));
+                _context8.next = 10;
+                return this._deviceManager.raiseYourHand(deviceID, !!parseInt(postBody.signal, 10));
 
               case 10:
-                if (!(this.request.files && !this.request.files.file)) {
-                  _context8.next = 12;
+                return _context8.abrupt('return', this.ok({ id: deviceID, ok: true }));
+
+              case 11:
+                if (!postBody.app_id) {
+                  _context8.next = 16;
                   break;
                 }
 
-                throw new Error('Firmware file not provided');
+                _context8.next = 14;
+                return this._deviceManager.flashKnownApp(deviceID, postBody.app_id);
 
-              case 12:
-                file = this.request.files && this.request.files.file[0];
+              case 14:
+                flashResult = _context8.sent;
+                return _context8.abrupt('return', this.ok({ id: deviceID, status: flashResult.status }));
 
-                if (!(file && file.originalname.endsWith('.bin'))) {
+              case 16:
+                if (!(this.request.files && !this.request.files.file)) {
                   _context8.next = 18;
                   break;
                 }
 
-                _context8.next = 16;
-                return this._deviceManager.flashBinary(deviceID, file);
-
-              case 16:
-                _flashStatus = _context8.sent;
-                return _context8.abrupt('return', this.ok({ id: deviceID, status: _flashStatus }));
+                throw new Error('Firmware file not provided');
 
               case 18:
-                if (!postBody.signal) {
-                  _context8.next = 24;
-                  break;
-                }
+                file = this.request.files && this.request.files.file[0];
 
-                if (['1', '0'].includes(postBody.signal)) {
-                  _context8.next = 21;
+                if (!(file && file.originalname.endsWith('.bin'))) {
+                  _context8.next = 24;
                   break;
                 }
 
-                throw new _HttpError2.default('Wrong signal value');
-
-              case 21:
-                _context8.next = 23;
-                return this._deviceManager.raiseYourHand(deviceID, !!parseInt(postBody.signal, 10));
+                _context8.next = 22;
+                return this._deviceManager.flashBinary(deviceID, file);
 
-              case 23:
-                return _context8.abrupt('return', this.ok({ id: deviceID, ok: true }));
+              case 22:
+                _flashResult = _context8.sent;
+                return _context8.abrupt('return', this.ok({ id: deviceID, status: _flashResult.status }));
 
               case 24:
                 throw new _HttpError2.default('Did not update device');
diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js
index 4860098e..93e7d37c 100644
--- a/dist/controllers/EventsController.js
+++ b/dist/controllers/EventsController.js
@@ -12,6 +12,10 @@ var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
@@ -44,12 +48,16 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _desc, _value, _class;
+var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _desc, _value, _class;
 
 var _Controller2 = require('./Controller');
 
 var _Controller3 = _interopRequireDefault(_Controller2);
 
+var _anonymous = require('../decorators/anonymous');
+
+var _anonymous2 = _interopRequireDefault(_anonymous);
+
 var _route = require('../decorators/route');
 
 var _route2 = _interopRequireDefault(_route);
@@ -101,7 +109,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec3 = (0, _serverSentEvents2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('post'), _dec11 = (0, _route2.default)('/v1/devices/events'), (_class = function (_Controller) {
+var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/ping'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec12 = (0, _serverSentEvents2.default)(), _dec13 = (0, _httpVerb2.default)('post'), _dec14 = (0, _route2.default)('/v1/devices/events'), (_class = function (_Controller) {
   (0, _inherits3.default)(EventsController, _Controller);
 
   function EventsController(eventManager) {
@@ -142,22 +150,18 @@ var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _rout
       }
     }
   }, {
-    key: 'getEvents',
+    key: 'ping',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(eventNamePrefix) {
-        var subscriptionID;
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(payload) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { userID: this.user.id });
-                _context.next = 3;
-                return this._closeStream(subscriptionID);
+                return _context.abrupt('return', this.ok((0, _extends3.default)({}, payload, {
+                  serverPayload: Math.random()
+                })));
 
-              case 3:
-                return _context.abrupt('return', this.ok());
-
-              case 4:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -165,14 +169,14 @@ var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _rout
         }, _callee, this);
       }));
 
-      function getEvents(_x) {
+      function ping(_x) {
         return _ref.apply(this, arguments);
       }
 
-      return getEvents;
+      return ping;
     }()
   }, {
-    key: 'getMyEvents',
+    key: 'getEvents',
     value: function () {
       var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) {
         var subscriptionID;
@@ -180,10 +184,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _rout
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), {
-                  mydevices: true,
-                  userID: this.user.id
-                });
+                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { userID: this.user.id });
                 _context2.next = 3;
                 return this._closeStream(subscriptionID);
 
@@ -198,23 +199,23 @@ var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _rout
         }, _callee2, this);
       }));
 
-      function getMyEvents(_x2) {
+      function getEvents(_x2) {
         return _ref2.apply(this, arguments);
       }
 
-      return getMyEvents;
+      return getEvents;
     }()
   }, {
-    key: 'getDeviceEvents',
+    key: 'getMyEvents',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID, eventNamePrefix) {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(eventNamePrefix) {
         var subscriptionID;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
             switch (_context3.prev = _context3.next) {
               case 0:
                 subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), {
-                  deviceID: deviceID,
+                  mydevices: true,
                   userID: this.user.id
                 });
                 _context3.next = 3;
@@ -231,20 +232,53 @@ var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _rout
         }, _callee3, this);
       }));
 
-      function getDeviceEvents(_x3, _x4) {
+      function getMyEvents(_x3) {
         return _ref3.apply(this, arguments);
       }
 
+      return getMyEvents;
+    }()
+  }, {
+    key: 'getDeviceEvents',
+    value: function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, eventNamePrefix) {
+        var subscriptionID;
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), {
+                  deviceID: deviceID,
+                  userID: this.user.id
+                });
+                _context4.next = 3;
+                return this._closeStream(subscriptionID);
+
+              case 3:
+                return _context4.abrupt('return', this.ok());
+
+              case 4:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, this);
+      }));
+
+      function getDeviceEvents(_x4, _x5) {
+        return _ref4.apply(this, arguments);
+      }
+
       return getDeviceEvents;
     }()
   }, {
     key: 'publish',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(postBody) {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(postBody) {
         var eventData;
-        return _regenerator2.default.wrap(function _callee4$(_context4) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
-            switch (_context4.prev = _context4.next) {
+            switch (_context5.prev = _context5.next) {
               case 0:
                 eventData = {
                   data: postBody.data,
@@ -256,23 +290,23 @@ var EventsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _rout
 
 
                 this._eventManager.publish(eventData);
-                return _context4.abrupt('return', this.ok({ ok: true }));
+                return _context5.abrupt('return', this.ok({ ok: true }));
 
               case 3:
               case 'end':
-                return _context4.stop();
+                return _context5.stop();
             }
           }
-        }, _callee4, this);
+        }, _callee5, this);
       }));
 
-      function publish(_x5) {
-        return _ref4.apply(this, arguments);
+      function publish(_x6) {
+        return _ref5.apply(this, arguments);
       }
 
       return publish;
     }()
   }]);
   return EventsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'ping', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'ping'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec10, _dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class));
 exports.default = EventsController;
\ No newline at end of file
diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index e1c4c11b..742531b2 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -147,7 +147,7 @@ exports.default = function (container, newSettings) {
   container.bindClass('WebhooksController', _WebhooksController2.default, ['WebhookManager']);
 
   // managers
-  container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'DeviceServer', 'PermissionManager']);
+  container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'PermissionManager', 'EventPublisher']);
   container.bindClass('EventManager', _EventManager2.default, ['EventPublisher']);
   container.bindClass('WebhookManager', _WebhookManager2.default, ['EventPublisher', 'PermissionManager', 'WebhookLogger', 'WebhookRepository']);
 
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 80455800..fa10e869 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -28,6 +28,8 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _sparkProtocol = require('spark-protocol');
+
 var _ursa = require('ursa');
 
 var _ursa2 = _interopRequireDefault(_ursa);
@@ -38,7 +40,7 @@ var _HttpError2 = _interopRequireDefault(_HttpError);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, deviceServer, permissionManager) {
+var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirmwareRepository, deviceKeyRepository, permissionManager, eventPublisher) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, DeviceManager);
@@ -147,7 +149,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.getByID = function () {
     var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID) {
-      var attributes, device;
+      var attributes, pingResponse;
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
@@ -166,14 +168,21 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('No device found', 404);
 
             case 5:
-              device = _this._deviceServer.getDevice(attributes.deviceID);
+              _context3.next = 7;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE
+              });
+
+            case 7:
+              pingResponse = _context3.sent;
               return _context3.abrupt('return', (0, _extends3.default)({}, attributes, {
-                connected: device && device.ping().connected || false,
+                connected: pingResponse.connected || false,
                 lastFlashedAppName: null,
-                lastHeard: device && device.ping().lastPing || attributes.lastHeard
+                lastHeard: pingResponse.lastPing || attributes.lastHeard
               }));
 
-            case 7:
+            case 9:
             case 'end':
               return _context3.stop();
           }
@@ -188,21 +197,26 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.getDetailsByID = function () {
     var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
-      var device, _ref5, _ref6, attributes, description;
+      var _ref5, _ref6, attributes, description, pingResponse;
 
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
-              device = _this._deviceServer.getDevice(deviceID);
-              _context4.next = 3;
-              return _promise2.default.all([_this._permissionManager.getEntityByID('deviceAttributes', deviceID), device && device.getDescription()]);
+              _context4.next = 2;
+              return _promise2.default.all([_this._permissionManager.getEntityByID('deviceAttributes', deviceID), _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION
+              }), _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE })]);
 
-            case 3:
+            case 2:
               _ref5 = _context4.sent;
-              _ref6 = (0, _slicedToArray3.default)(_ref5, 2);
+              _ref6 = (0, _slicedToArray3.default)(_ref5, 3);
               attributes = _ref6[0];
               description = _ref6[1];
+              pingResponse = _ref6[2];
 
               if (attributes) {
                 _context4.next = 9;
@@ -213,11 +227,11 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 9:
               return _context4.abrupt('return', (0, _extends3.default)({}, attributes, {
-                connected: device && device.ping().connected || false,
-                functions: description ? description.state.f : null,
+                connected: pingResponse.connected,
+                functions: description.state ? description.state.f : null,
                 lastFlashedAppName: null,
-                lastHeard: device && device.ping().lastPing || attributes.lastHeard,
-                variables: description ? description.state.v : null
+                lastHeard: pingResponse.lastPing || attributes.lastHeard,
+                variables: description.state ? description.state.v : null
               }));
 
             case 10:
@@ -246,16 +260,19 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
             devicesAttributes = _context6.sent;
             devicePromises = devicesAttributes.map(function () {
               var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) {
-                var device;
+                var pingResponse;
                 return _regenerator2.default.wrap(function _callee5$(_context5) {
                   while (1) {
                     switch (_context5.prev = _context5.next) {
                       case 0:
-                        device = _this._deviceServer.getDevice(attributes.deviceID);
+                        pingResponse = _this._eventPublisher.publishAndListenForResponse({
+                          context: { deviceID: attributes.deviceID },
+                          name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE
+                        });
                         return _context5.abrupt('return', (0, _extends3.default)({}, attributes, {
-                          connected: device && device.ping().connected || false,
+                          connected: pingResponse.connected || false,
                           lastFlashedAppName: null,
-                          lastHeard: device && device.ping().lastPing || attributes.lastHeard
+                          lastHeard: pingResponse.lastPing || attributes.lastHeard
                         }));
 
                       case 2:
@@ -282,7 +299,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.callFunction = function () {
     var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, functionName, functionArguments) {
-      var device;
+      var callFunctionResponse, error;
       return _regenerator2.default.wrap(function _callee7$(_context7) {
         while (1) {
           switch (_context7.prev = _context7.next) {
@@ -291,23 +308,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              device = _this._deviceServer.getDevice(deviceID);
+              _context7.next = 4;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID, functionArguments: functionArguments, functionName: functionName },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.CALL_DEVICE_FUNCTION
+              });
+
+            case 4:
+              callFunctionResponse = _context7.sent;
+              error = callFunctionResponse.error;
 
-              if (device) {
-                _context7.next = 5;
+              if (!error) {
+                _context7.next = 8;
                 break;
               }
 
-              throw new _HttpError2.default('Could not get device for ID', 404);
-
-            case 5:
-              _context7.next = 7;
-              return device.callFunction(functionName, functionArguments);
-
-            case 7:
-              return _context7.abrupt('return', _context7.sent);
+              throw new _HttpError2.default(error);
 
             case 8:
+              return _context7.abrupt('return', callFunctionResponse);
+
+            case 9:
             case 'end':
               return _context7.stop();
           }
@@ -321,8 +342,8 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.getVariableValue = function () {
-    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, varName) {
-      var device;
+    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, variableName) {
+      var getVariableResponse, error, result;
       return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
           switch (_context8.prev = _context8.next) {
@@ -331,23 +352,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              device = _this._deviceServer.getDevice(deviceID);
+              _context8.next = 4;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID, variableName: variableName },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.GET_DEVICE_VARIABLE_VALUE
+              });
+
+            case 4:
+              getVariableResponse = _context8.sent;
+              error = getVariableResponse.error, result = getVariableResponse.result;
 
-              if (device) {
-                _context8.next = 5;
+              if (!error) {
+                _context8.next = 8;
                 break;
               }
 
-              throw new _HttpError2.default('Could not get device for ID', 404);
-
-            case 5:
-              _context8.next = 7;
-              return device.getVariableValue(varName);
-
-            case 7:
-              return _context8.abrupt('return', _context8.sent);
+              throw new _HttpError2.default(error);
 
             case 8:
+              return _context8.abrupt('return', result);
+
+            case 9:
             case 'end':
               return _context8.stop();
           }
@@ -362,7 +387,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.flashBinary = function () {
     var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) {
-      var device;
+      var flashResponse, error;
       return _regenerator2.default.wrap(function _callee9$(_context9) {
         while (1) {
           switch (_context9.prev = _context9.next) {
@@ -371,23 +396,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              device = _this._deviceServer.getDevice(deviceID);
+              _context9.next = 4;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID, fileBuffer: file.buffer },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.FLASH_DEVICE
+              });
 
-              if (device) {
-                _context9.next = 5;
+            case 4:
+              flashResponse = _context9.sent;
+              error = flashResponse.error;
+
+              if (!error) {
+                _context9.next = 8;
                 break;
               }
 
-              throw new _HttpError2.default('Could not get device for ID', 404);
-
-            case 5:
-              _context9.next = 7;
-              return device.flash(file.buffer);
-
-            case 7:
-              return _context9.abrupt('return', _context9.sent);
+              throw new _HttpError2.default(error);
 
             case 8:
+              return _context9.abrupt('return', flashResponse);
+
+            case 9:
             case 'end':
               return _context9.stop();
           }
@@ -402,7 +431,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.flashKnownApp = function () {
     var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, appName) {
-      var knownFirmware, device;
+      var knownFirmware, flashResponse, error;
       return _regenerator2.default.wrap(function _callee10$(_context10) {
         while (1) {
           switch (_context10.prev = _context10.next) {
@@ -421,23 +450,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('No firmware ' + appName + ' found', 404);
 
             case 5:
-              device = _this._deviceServer.getDevice(deviceID);
+              _context10.next = 7;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID, fileBuffer: knownFirmware },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.FLASH_DEVICE
+              });
+
+            case 7:
+              flashResponse = _context10.sent;
+              error = flashResponse.error;
 
-              if (device) {
-                _context10.next = 8;
+              if (!error) {
+                _context10.next = 11;
                 break;
               }
 
-              throw new _HttpError2.default('Could not get device for ID', 404);
-
-            case 8:
-              _context10.next = 10;
-              return device.flash(knownFirmware);
-
-            case 10:
-              return _context10.abrupt('return', _context10.sent);
+              throw new _HttpError2.default(error);
 
             case 11:
+              return _context10.abrupt('return', flashResponse);
+
+            case 12:
             case 'end':
               return _context10.stop();
           }
@@ -526,7 +559,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.raiseYourHand = function () {
     var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, shouldShowSignal) {
-      var device;
+      var RaiseYourHandResponse, error;
       return _regenerator2.default.wrap(function _callee12$(_context12) {
         while (1) {
           switch (_context12.prev = _context12.next) {
@@ -535,23 +568,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              device = _this._deviceServer.getDevice(deviceID);
+              _context12.next = 4;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID, shouldShowSignal: shouldShowSignal },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.RAISE_YOUR_HAND
+              });
+
+            case 4:
+              RaiseYourHandResponse = _context12.sent;
+              error = RaiseYourHandResponse.error;
 
-              if (device) {
-                _context12.next = 5;
+              if (!error) {
+                _context12.next = 8;
                 break;
               }
 
-              throw new _HttpError2.default('Could not get device for ID', 404);
-
-            case 5:
-              _context12.next = 7;
-              return device.raiseYourHand(shouldShowSignal);
-
-            case 7:
-              return _context12.abrupt('return', _context12.sent);
+              throw new _HttpError2.default(error);
 
             case 8:
+              return _context12.abrupt('return', RaiseYourHandResponse);
+
+            case 9:
             case 'end':
               return _context12.stop();
           }
@@ -610,8 +647,8 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   this._deviceAttributeRepository = deviceAttributeRepository;
   this._deviceFirmwareRepository = deviceFirmwareRepository;
   this._deviceKeyRepository = deviceKeyRepository;
-  this._deviceServer = deviceServer;
   this._permissionManager = permissionManager;
+  this._eventPublisher = eventPublisher;
 };
 
 exports.default = DeviceManager;
\ No newline at end of file
diff --git a/dist/managers/EventManager.js b/dist/managers/EventManager.js
index 08436883..3d95ecf1 100644
--- a/dist/managers/EventManager.js
+++ b/dist/managers/EventManager.js
@@ -16,7 +16,7 @@ var EventManager = function EventManager(eventPublisher) {
   (0, _classCallCheck3.default)(this, EventManager);
 
   this.subscribe = function (eventNamePrefix, eventHandler, filterOptions) {
-    return _this._eventPublisher.subscribe(eventNamePrefix, eventHandler, filterOptions);
+    return _this._eventPublisher.subscribe(eventNamePrefix, eventHandler, { filterOptions: filterOptions });
   };
 
   this.unsubscribe = function (subscriptionID) {
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 4cb07028..07228e98 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -255,9 +255,11 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
 
   this._subscribeWebhook = function (webhook) {
     var subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), {
-      deviceID: webhook.deviceID,
-      mydevices: webhook.mydevices,
-      userID: webhook.ownerID
+      filterOptions: {
+        deviceID: webhook.deviceID,
+        mydevices: webhook.mydevices,
+        userID: webhook.ownerID
+      }
     });
     _this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID);
   };
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index 6ad11a32..56767f19 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -133,17 +133,33 @@ class DevicesController extends Controller {
       return this.ok({ name: updatedAttributes.name, ok: true });
     }
 
-    // 2 flash device with known application
+    // 2
+    // If signal exists then we want to toggle nyan mode. This just makes the
+    // LED change colors.
+    if (postBody.signal) {
+      if (!['1', '0'].includes(postBody.signal)) {
+        throw new HttpError('Wrong signal value');
+      }
+
+      await this._deviceManager.raiseYourHand(
+        deviceID,
+        !!parseInt(postBody.signal, 10),
+      );
+
+      return this.ok({ id: deviceID, ok: true });
+    }
+
+    // 3 flash device with known application
     if (postBody.app_id) {
-      const flashStatus = await this._deviceManager.flashKnownApp(
+      const flashResult = await this._deviceManager.flashKnownApp(
         deviceID,
         postBody.app_id,
       );
 
-      return this.ok({ id: deviceID, status: flashStatus });
+      return this.ok({ id: deviceID, status: flashResult.status });
     }
 
-    // 3 flash device with custom application
+    // 4 flash device with custom application
     if (this.request.files && !(this.request.files: any).file) {
       throw new Error('Firmware file not provided');
     }
@@ -153,26 +169,10 @@ class DevicesController extends Controller {
       (this.request.files: any).file[0];
 
     if (file && file.originalname.endsWith('.bin')) {
-      const flashStatus = await this._deviceManager
+      const flashResult = await this._deviceManager
         .flashBinary(deviceID, file);
 
-      return this.ok({ id: deviceID, status: flashStatus });
-    }
-
-    // 4
-    // If signal exists then we want to toggle nyan mode. This just makes the
-    // LED change colors.
-    if (postBody.signal) {
-      if (!['1', '0'].includes(postBody.signal)) {
-        throw new HttpError('Wrong signal value');
-      }
-
-      await this._deviceManager.raiseYourHand(
-        deviceID,
-        !!parseInt(postBody.signal, 10),
-      );
-
-      return this.ok({ id: deviceID, ok: true });
+      return this.ok({ id: deviceID, status: flashResult.status });
     }
 
     throw new HttpError('Did not update device');
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index c7bdf90f..d47df741 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -144,8 +144,8 @@ export default (container: Container, newSettings: Settings) => {
       'DeviceAttributeRepository',
       'DeviceFirmwareRepository',
       'DeviceKeyRepository',
-      'DeviceServer',
       'PermissionManager',
+      'EventPublisher',
     ],
   );
   container.bindClass(
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index f3b58b67..99674972 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -1,7 +1,7 @@
 // @flow
 
 import type { File } from 'express';
-import type { DeviceServer } from 'spark-protocol';
+import type { EventPublisher } from 'spark-protocol';
 import type PermissionManager from './PermissionManager';
 import type {
   Device,
@@ -11,6 +11,7 @@ import type {
   IDeviceFirmwareRepository,
 } from '../types';
 
+import { SPARK_SERVER_EVENTS } from 'spark-protocol';
 import ursa from 'ursa';
 import HttpError from '../lib/HttpError';
 
@@ -18,21 +19,21 @@ class DeviceManager {
   _deviceAttributeRepository: IDeviceAttributeRepository;
   _deviceFirmwareRepository: IDeviceFirmwareRepository;
   _deviceKeyRepository: IDeviceKeyRepository;
-  _deviceServer: DeviceServer;
   _permissionManager: PermissionManager;
+  _eventPublisher: EventPublisher;
 
   constructor(
     deviceAttributeRepository: IDeviceAttributeRepository,
     deviceFirmwareRepository: IDeviceFirmwareRepository,
     deviceKeyRepository: IDeviceKeyRepository,
-    deviceServer: DeviceServer,
     permissionManager: PermissionManager,
+    eventPublisher: EventPublisher,
   ) {
     this._deviceAttributeRepository = deviceAttributeRepository;
     this._deviceFirmwareRepository = deviceFirmwareRepository;
     this._deviceKeyRepository = deviceKeyRepository;
-    this._deviceServer = deviceServer;
     this._permissionManager = permissionManager;
+    this._eventPublisher = eventPublisher;
   }
 
   claimDevice = async (
@@ -85,22 +86,30 @@ class DeviceManager {
       throw new HttpError('No device found', 404);
     }
 
-    const device = this._deviceServer.getDevice(attributes.deviceID);
+    const pingResponse = await this._eventPublisher.publishAndListenForResponse({
+      context: { deviceID },
+      name: SPARK_SERVER_EVENTS.PING_DEVICE,
+    });
 
     return {
       ...attributes,
-      connected: device && device.ping().connected || false,
+      connected: pingResponse.connected || false,
       lastFlashedAppName: null,
-      lastHeard: device && device.ping().lastPing || attributes.lastHeard,
+      lastHeard: pingResponse.lastPing || attributes.lastHeard,
     };
   };
 
   getDetailsByID = async (deviceID: string): Promise => {
-    const device = this._deviceServer.getDevice(deviceID);
-
-    const [attributes, description] = await Promise.all([
+    const [attributes, description, pingResponse] = await Promise.all([
       this._permissionManager.getEntityByID('deviceAttributes', deviceID),
-      device && device.getDescription(),
+      this._eventPublisher.publishAndListenForResponse({
+        context: { deviceID },
+        name: SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION,
+      }),
+      this._eventPublisher.publishAndListenForResponse({
+        context: { deviceID },
+        name: SPARK_SERVER_EVENTS.PING_DEVICE },
+      ),
     ]);
 
     if (!attributes) {
@@ -109,11 +118,11 @@ class DeviceManager {
 
     return {
       ...attributes,
-      connected: device && device.ping().connected || false,
-      functions: description ? description.state.f : null,
+      connected: pingResponse.connected,
+      functions: description.state ? description.state.f : null,
       lastFlashedAppName: null,
-      lastHeard: device && device.ping().lastPing || attributes.lastHeard,
-      variables: description ? description.state.v : null,
+      lastHeard: pingResponse.lastPing || attributes.lastHeard,
+      variables: description.state ? description.state.v : null,
     };
   };
 
@@ -123,13 +132,16 @@ class DeviceManager {
 
     const devicePromises = devicesAttributes.map(
       async (attributes: DeviceAttributes): Promise => {
-        const device = this._deviceServer.getDevice(attributes.deviceID);
+        const pingResponse = this._eventPublisher.publishAndListenForResponse({
+          context: { deviceID: attributes.deviceID },
+          name: SPARK_SERVER_EVENTS.PING_DEVICE,
+        });
 
         return {
           ...attributes,
-          connected: device && device.ping().connected || false,
+          connected: pingResponse.connected || false,
           lastFlashedAppName: null,
-          lastHeard: device && device.ping().lastPing || attributes.lastHeard,
+          lastHeard: pingResponse.lastPing || attributes.lastHeard,
         };
       },
     );
@@ -146,56 +158,69 @@ class DeviceManager {
       'deviceAttributes',
       deviceID,
     );
-
-    const device = this._deviceServer.getDevice(deviceID);
-    if (!device) {
-      throw new HttpError('Could not get device for ID', 404);
+    const callFunctionResponse =
+      await this._eventPublisher.publishAndListenForResponse({
+        context: { deviceID, functionArguments, functionName },
+        name: SPARK_SERVER_EVENTS.CALL_DEVICE_FUNCTION,
+      });
+
+    const { error } = callFunctionResponse;
+    if (error) {
+      throw new HttpError(error);
     }
 
-    return await device.callFunction(
-      functionName,
-      functionArguments,
-    );
+    return callFunctionResponse;
   };
 
   getVariableValue = async (
     deviceID: string,
-    varName: string,
+    variableName: string,
   ): Promise<*> => {
     await this._permissionManager.checkPermissionsForEntityByID(
       'deviceAttributes',
       deviceID,
     );
 
-    const device = this._deviceServer.getDevice(deviceID);
-    if (!device) {
-      throw new HttpError('Could not get device for ID', 404);
+    const getVariableResponse =
+      await this._eventPublisher.publishAndListenForResponse({
+        context: { deviceID, variableName },
+        name: SPARK_SERVER_EVENTS.GET_DEVICE_VARIABLE_VALUE,
+      });
+
+    const { error, result } = getVariableResponse;
+    if (error) {
+      throw new HttpError(error);
     }
 
-    return await device.getVariableValue(varName);
+    return result;
   };
 
   flashBinary = async (
     deviceID: string,
     file: File,
-  ): Promise => {
+  ): Promise<*> => {
     await this._permissionManager.checkPermissionsForEntityByID(
       'deviceAttributes',
       deviceID,
     );
 
-    const device = this._deviceServer.getDevice(deviceID);
-    if (!device) {
-      throw new HttpError('Could not get device for ID', 404);
+    const flashResponse = await this._eventPublisher.publishAndListenForResponse({
+      context: { deviceID, fileBuffer: file.buffer },
+      name: SPARK_SERVER_EVENTS.FLASH_DEVICE,
+    });
+
+    const { error } = flashResponse;
+    if (error) {
+      throw new HttpError(error);
     }
 
-    return await device.flash(file.buffer);
+    return flashResponse;
   };
 
   flashKnownApp = async (
     deviceID: string,
     appName: string,
-  ): Promise => {
+  ): Promise<*> => {
     await this._permissionManager.checkPermissionsForEntityByID(
       'deviceAttributes',
       deviceID,
@@ -207,12 +232,17 @@ class DeviceManager {
       throw new HttpError(`No firmware ${appName} found`, 404);
     }
 
-    const device = this._deviceServer.getDevice(deviceID);
-    if (!device) {
-      throw new HttpError('Could not get device for ID', 404);
+    const flashResponse = await this._eventPublisher.publishAndListenForResponse({
+      context: { deviceID, fileBuffer: knownFirmware },
+      name: SPARK_SERVER_EVENTS.FLASH_DEVICE,
+    });
+
+    const { error } = flashResponse;
+    if (error) {
+      throw new HttpError(error);
     }
 
-    return await device.flash(knownFirmware);
+    return flashResponse;
   };
 
   provision = async (
@@ -259,12 +289,18 @@ class DeviceManager {
       deviceID,
     );
 
-    const device = this._deviceServer.getDevice(deviceID);
-    if (!device) {
-      throw new HttpError('Could not get device for ID', 404);
+    const RaiseYourHandResponse =
+      await this._eventPublisher.publishAndListenForResponse({
+        context: { deviceID, shouldShowSignal },
+        name: SPARK_SERVER_EVENTS.RAISE_YOUR_HAND,
+      });
+
+    const { error } = RaiseYourHandResponse;
+    if (error) {
+      throw new HttpError(error);
     }
 
-    return await device.raiseYourHand(shouldShowSignal);
+    return RaiseYourHandResponse;
   };
 
   renameDevice = async (
diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js
index c5b4f51f..d0b6e1d6 100644
--- a/src/managers/EventManager.js
+++ b/src/managers/EventManager.js
@@ -24,7 +24,7 @@ class EventManager {
     this._eventPublisher.subscribe(
       eventNamePrefix,
       eventHandler,
-      filterOptions,
+      { filterOptions },
     );
 
   unsubscribe = (subscriptionID: string): void =>
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index cc371987..3269fa31 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -129,9 +129,11 @@ class WebhookManager {
       webhook.event,
       this._onNewWebhookEvent(webhook),
       {
-        deviceID: webhook.deviceID,
-        mydevices: webhook.mydevices,
-        userID: webhook.ownerID,
+        filterOptions: {
+          deviceID: webhook.deviceID,
+          mydevices: webhook.mydevices,
+          userID: webhook.ownerID,
+        },
       },
     );
     this._subscriptionIDsByWebhookID.set(webhook.id, subscriptionID);
diff --git a/src/types.js b/src/types.js
index 1a664c4f..ef1764ae 100644
--- a/src/types.js
+++ b/src/types.js
@@ -85,6 +85,7 @@ export type Event = EventData & {
 };
 
 export type EventData = {
+  context?: Object,
   data?: string,
   deviceID?: ?string,
   isPublic: boolean,
diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js
index 048e1190..1244665d 100644
--- a/test/DeviceClaimsController.test.js
+++ b/test/DeviceClaimsController.test.js
@@ -1,9 +1,11 @@
 /* eslint-disable */
 import test from 'ava';
 import request from 'supertest';
+import sinon from 'sinon';
 import ouathClients from '../src/oauthClients.json';
 import app from './setup/testApp';
 import TestData from './setup/TestData';
+import { SPARK_SERVER_EVENTS } from 'spark-protocol';
 
 const container = app.container;
 let DEVICE_ID = null;
@@ -15,6 +17,27 @@ test.before(async () => {
   const USER_CREDENTIALS = TestData.getUser();
   DEVICE_ID = TestData.getID();
 
+  sinon.stub(
+    container.constitute('EventPublisher'),
+    'publishAndListenForResponse',
+    ({ name }) => {
+      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION) {
+        return {
+          state : {
+            f: null,
+            v: null,
+          },
+        };
+      }
+      if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
+        return {
+          connected: true,
+          lastPing: new Date(),
+        };
+      }
+    }
+  );
+
   const userResponse = await request(app)
     .post('/v1/users')
     .send(USER_CREDENTIALS);
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index 0ef3082b..acb79b19 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -6,18 +6,89 @@ import path from 'path';
 import ouathClients from '../src/oauthClients.json';
 import app from './setup/testApp';
 import TestData from './setup/TestData';
+import { SPARK_SERVER_EVENTS } from 'spark-protocol';
 
 const container = app.container;
 let customFirmwareFilePath;
 let customFirmwareBuffer;
-let DEVICE_ID;
+
+const USER_CREDENTIALS = TestData.getUser();
+const CONNECTED_DEVICE_ID = TestData.getID();
+const DISCONNECTED_DEVICE_ID = TestData.getID();
 let testUser;
 let userToken;
-let deviceToApiAttributes;
+let connectedDeviceToApiAttributes;
+let disconnectedDeviceToApiAttributes;
+
+const TEST_LAST_HEARD = new Date();
+const TEST_DEVICE_FUNTIONS = ['testFunction'];
+const TEST_FUNCTION_ARGUMENT = 'testArgument';
+const TEST_DEVICE_VARIABLES = ['testVariable1', 'testVariable2'];
+const TEST_VARIABLE_RESULT = 'resultValue';
 
 test.before(async () => {
-  const USER_CREDENTIALS = TestData.getUser();
-  DEVICE_ID = TestData.getID();
+  sinon.stub(
+    container.constitute('EventPublisher'),
+    'publishAndListenForResponse',
+    ({
+       name,
+       context: {
+         deviceID,
+         functionArguments,
+         functionName,
+         shouldShowSignal,
+         variableName,
+       },
+    }) => {
+      if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
+        return deviceID === CONNECTED_DEVICE_ID
+          ? {
+              connected: true,
+              lastPing: TEST_LAST_HEARD,
+            }
+          : {
+              connected: false,
+              lastPing: null,
+            };
+      }
+
+      if(deviceID !== CONNECTED_DEVICE_ID) {
+        return { error: new Error('Could not get device for ID')};
+      }
+
+      if(name === SPARK_SERVER_EVENTS.CALL_DEVICE_FUNCTION) {
+        if(TEST_DEVICE_FUNTIONS.includes(functionName)) {
+          return functionArguments.argument
+        } else {
+          return { error: new Error(`Unknown Function ${functionName}`) };
+        }
+      }
+
+      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION) {
+        return {
+          state: {
+            f: TEST_DEVICE_FUNTIONS,
+            v: TEST_DEVICE_VARIABLES,
+          },
+        };
+      }
+
+      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_VARIABLE_VALUE) {
+        if(!TEST_DEVICE_VARIABLES.includes(variableName)) {
+          throw new Error(`Variable not found`)
+        }
+        return { result: TEST_VARIABLE_RESULT };
+      }
+
+      if (name === SPARK_SERVER_EVENTS.FLASH_DEVICE) {
+        return { status: 'Update finished' }
+      }
+
+      if(name === SPARK_SERVER_EVENTS.RAISE_YOUR_HAND) {
+        return shouldShowSignal ? { status: 1 } : { status: 0 };
+      }
+    }
+  );
   const { filePath, fileBuffer } = await TestData.createCustomFirmwareBinary();
   customFirmwareFilePath = filePath;
   customFirmwareBuffer = fileBuffer;
@@ -46,15 +117,25 @@ test.before(async () => {
     throw new Error('test user creation fails');
   }
 
-  const provisionResponse = await request(app)
-    .post(`/v1/provisioning/${DEVICE_ID}`)
+  const provisionConnectedDeviceResponse = await request(app)
+    .post(`/v1/provisioning/${CONNECTED_DEVICE_ID}`)
+    .query({ access_token: userToken })
+    .send({ publicKey: TestData.getPublicKey() });
+
+  connectedDeviceToApiAttributes = provisionConnectedDeviceResponse.body;
+
+  const provisionDisconnectedDeviceResponse = await request(app)
+    .post(`/v1/provisioning/${DISCONNECTED_DEVICE_ID}`)
     .query({ access_token: userToken })
     .send({ publicKey: TestData.getPublicKey() });
 
-  deviceToApiAttributes = provisionResponse.body;
+  disconnectedDeviceToApiAttributes = provisionDisconnectedDeviceResponse.body;
 
-  if (!deviceToApiAttributes.id) {
-    throw new Error('test device creation fails');
+  if (
+    !connectedDeviceToApiAttributes.id ||
+    !disconnectedDeviceToApiAttributes.id
+  ) {
+    throw new Error('test devices creation fails');
   }
 });
 
@@ -70,79 +151,47 @@ test('should throw an error for compile source code endpoint', async t => {
 test.serial(
   'should return device details for connected device',
   async t => {
-
-    const testFunctions = ['testFunction'];
-    const testVariables = ['testVariable1', 'testVariable2'];
-    const lastHeard = new Date();
-    const device = {
-      getDescription: () => ({
-        state : {
-          f: testFunctions,
-          v: testVariables,
-        },
-      }),
-      ping: () => ({
-        connected: true,
-        lastPing: lastHeard,
-      }),
-    };
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
     const response = await request(app)
-      .get(`/v1/devices/${DEVICE_ID}`)
+      .get(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .query({ access_token: userToken });
 
-    deviceServerStub.restore();
-
-
     t.is(response.status, 200);
     t.is(response.body.connected, true);
     t.is(
       JSON.stringify(response.body.functions),
-      JSON.stringify(testFunctions),
+      JSON.stringify(TEST_DEVICE_FUNTIONS),
     );
-    t.is(response.body.id, deviceToApiAttributes.id);
-    t.is(response.body.name, deviceToApiAttributes.name);
-    t.is(response.body.ownerID, deviceToApiAttributes.ownerID);
+    t.is(response.body.id, connectedDeviceToApiAttributes.id);
+    t.is(response.body.name, connectedDeviceToApiAttributes.name);
+    t.is(response.body.ownerID, connectedDeviceToApiAttributes.ownerID);
     t.is(
       JSON.stringify(response.body.variables),
-      JSON.stringify(testVariables),
+      JSON.stringify(TEST_DEVICE_VARIABLES),
     );
-    t.is(response.body.last_heard, lastHeard.toISOString());
+    t.is(response.body.last_heard, TEST_LAST_HEARD.toISOString());
   },
 );
 
 test.serial(
   'should return device details for disconnected device',
   async t => {
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(null);
-
     const response = await request(app)
-      .get(`/v1/devices/${DEVICE_ID}`)
+      .get(`/v1/devices/${DISCONNECTED_DEVICE_ID}`)
       .query({ access_token: userToken });
 
-    deviceServerStub.restore();
-
     t.is(response.status, 200);
     t.is(response.body.connected, false);
     t.is(response.body.functions, null);
-    t.is(response.body.id, deviceToApiAttributes.id);
-    t.is(response.body.name, deviceToApiAttributes.name);
-    t.is(response.body.ownerID, deviceToApiAttributes.ownerID);
+    t.is(response.body.id, disconnectedDeviceToApiAttributes.id);
+    t.is(response.body.name, disconnectedDeviceToApiAttributes.name);
+    t.is(response.body.ownerID, disconnectedDeviceToApiAttributes.ownerID);
     t.is(response.body.variables, null);
   },
 );
 
 test.serial('should throw an error if device not found', async t => {
   const response = await request(app)
-    .get(`/v1/devices/${DEVICE_ID}123`)
+    .get(`/v1/devices/${CONNECTED_DEVICE_ID}123`)
     .query({ access_token: userToken });
 
   t.is(response.status, 404);
@@ -162,14 +211,14 @@ test.serial('should return all devices', async t => {
 
 test.serial('should unclaim device', async t => {
   const unclaimResponse = await request(app)
-    .delete(`/v1/devices/${DEVICE_ID}`)
+    .delete(`/v1/devices/${CONNECTED_DEVICE_ID}`)
     .query({ access_token: userToken });
 
   t.is(unclaimResponse.status, 200);
   t.is(unclaimResponse.body.ok, true);
 
   const getDeviceResponse = await request(app)
-    .get(`/v1/devices/${DEVICE_ID}`)
+    .get(`/v1/devices/${CONNECTED_DEVICE_ID}`)
     .query({ access_token: userToken });
 
   t.is(getDeviceResponse.status, 403);
@@ -181,14 +230,14 @@ test.serial('should claim device', async t => {
     .set('Content-Type', 'application/x-www-form-urlencoded')
     .send({
       access_token: userToken,
-      id: DEVICE_ID,
+      id: CONNECTED_DEVICE_ID,
     });
 
   t.is(claimDeviceResponse.status, 200);
   t.is(claimDeviceResponse.body.ok, true);
 
   const getDeviceResponse = await request(app)
-    .get(`/v1/devices/${DEVICE_ID}`)
+    .get(`/v1/devices/${CONNECTED_DEVICE_ID}`)
     .query({ access_token: userToken });
 
   t.is(getDeviceResponse.status, 200);
@@ -202,7 +251,7 @@ test.serial(
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
-        id: DEVICE_ID,
+        id: CONNECTED_DEVICE_ID,
       });
 
     t.is(claimDeviceResponse.status, 400);
@@ -223,7 +272,7 @@ test.serial(
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
-        id: DEVICE_ID,
+        id: CONNECTED_DEVICE_ID,
       });
 
     deviceAttributesStub.restore();
@@ -236,71 +285,31 @@ test.serial(
 test.serial(
   'should return function call result and device attributes',
   async t => {
-    const testFunctionName = 'testFunction';
-    const testArgument = 'testArgument';
-    const device = {
-      callFunction: (functionName, functionArguments) =>
-        functionName === testFunctionName && functionArguments.argument,
-      ping: () => ({
-        connected: true,
-        lastPing: new Date(),
-      }),
-    };
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
     const callFunctionResponse = await request(app)
-      .post(`/v1/devices/${DEVICE_ID}/${testFunctionName}`)
+      .post(`/v1/devices/${CONNECTED_DEVICE_ID}/${TEST_DEVICE_FUNTIONS[0]}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
-        argument: testArgument,
+        argument: TEST_FUNCTION_ARGUMENT,
       });
 
-
-    deviceServerStub.restore();
-
     t.is(callFunctionResponse.status, 200);
-    t.is(callFunctionResponse.body.return_value, testArgument);
+    t.is(callFunctionResponse.body.return_value, TEST_FUNCTION_ARGUMENT);
     t.is(callFunctionResponse.body.connected, true);
-    t.is(callFunctionResponse.body.id, DEVICE_ID);
+    t.is(callFunctionResponse.body.id, CONNECTED_DEVICE_ID);
   },
 );
 
 test.serial(
   'should throw an error if function doesn\'t exist',
   async t => {
-    const testFunctionName = 'testFunction';
-    const device = {
-      callFunction: (functionName, functionArguments) => {
-        if(functionName !== testFunctionName) {
-          throw new Error(`Unknown Function ${functionName}`)
-        }
-        return 1;
-      },
-      ping: () => ({
-        connected: true,
-        lastPing: new Date(),
-      }),
-    };
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
     const callFunctionResponse = await request(app)
-      .post(`/v1/devices/${DEVICE_ID}/wrong${testFunctionName}`)
+      .post(`/v1/devices/${CONNECTED_DEVICE_ID}/wrong${TEST_DEVICE_FUNTIONS[0]}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
       });
 
-    deviceServerStub.restore();
-
     t.is(callFunctionResponse.status, 404);
     t.is(callFunctionResponse.body.error, 'Function not found');
   },
@@ -309,55 +318,24 @@ test.serial(
 test.serial(
   'should return variable value',
   async t => {
-    const testVariableName = 'testVariable';
-    const testVariableResult = 'resultValue';
-    const device = {
-      getVariableValue: (variableName) =>
-        variableName === testVariableName && testVariableResult,
-    };
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
     const getVariableResponse = await request(app)
-      .get(`/v1/devices/${DEVICE_ID}/${testVariableName}/`)
+      .get(`/v1/devices/${CONNECTED_DEVICE_ID}/${TEST_DEVICE_VARIABLES[0]}/`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .query({ access_token: userToken });
 
-    deviceServerStub.restore();
-
     t.is(getVariableResponse.status, 200);
-    t.is(getVariableResponse.body.result, testVariableResult);
+    t.is(getVariableResponse.body.result, TEST_VARIABLE_RESULT);
   },
 );
 
 test.serial(
   'should throw an error if variable not found',
   async t => {
-    const testVariableName = 'testVariable';
-    const device = {
-      getVariableValue: (variableName) => {
-        if(variableName !== testVariableName) {
-          throw new Error(`Variable not found`)
-        }
-        return 1;
-      },
-    };
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
     const getVariableResponse = await request(app)
-      .get(`/v1/devices/${DEVICE_ID}/wrong${testVariableName}/`)
+      .get(`/v1/devices/${CONNECTED_DEVICE_ID}/wrong${TEST_DEVICE_VARIABLES[0]}/`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .query({ access_token: userToken });
 
-    deviceServerStub.restore();
-
     t.is(getVariableResponse.status, 404);
     t.is(getVariableResponse.body.error, 'Variable not found');
   },
@@ -369,7 +347,7 @@ test.serial(
     const newDeviceName = 'newDeviceName';
 
     const renameDeviceResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
@@ -382,60 +360,17 @@ test.serial(
 );
 
 test.serial(
-  'should start raise your hand on device',
+  'should invoke raise your hand on device',
   async t => {
-    const raiseYourHandSpy = sinon.spy();
-    const device = {
-      raiseYourHand: raiseYourHandSpy,
-    };
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
     const raiseYourHandResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
         signal: '1',
       });
 
-    deviceServerStub.restore();
-
-    t.is(raiseYourHandResponse.status, 200);
-    t.truthy(raiseYourHandSpy.calledWith(true));
-    t.is(raiseYourHandResponse.body.id, DEVICE_ID);
-  },
-);
-
-test.serial(
-  'should stop raise your hand on device',
-  async t => {
-    const raiseYourHandSpy = sinon.spy();
-    const device = {
-      raiseYourHand: raiseYourHandSpy,
-    };
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
-    const raiseYourHandResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .send({
-        access_token: userToken,
-        signal: '0',
-      });
-
-    deviceServerStub.restore();
-
     t.is(raiseYourHandResponse.status, 200);
-    t.truthy(raiseYourHandSpy.calledWith(false));
-    t.is(raiseYourHandResponse.body.id, DEVICE_ID);
   },
 );
 
@@ -443,7 +378,7 @@ test.serial(
   'should throw an error if signal is wrong value',
   async t => {
     const raiseYourHandResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
@@ -460,17 +395,6 @@ test.serial(
   async t => {
     const knownAppName = 'knownAppName';
     const knownAppBuffer = new Buffer(knownAppName);
-    const flashStatus = 'update finished';
-    const device = {
-      flash: () => flashStatus,
-    };
-    const flashSpy = sinon.spy(device, 'flash');
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
 
     const deviceFirmwareStub = sinon.stub(
       container.constitute('DeviceFirmwareRepository'),
@@ -478,20 +402,17 @@ test.serial(
     ).returns(knownAppBuffer);
 
     const flashKnownAppResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
         app_id: knownAppName,
       });
-
-    deviceServerStub.restore();
     deviceFirmwareStub.restore();
 
     t.is(flashKnownAppResponse.status, 200);
-    t.truthy(flashSpy.calledWith(knownAppBuffer));
-    t.is(flashKnownAppResponse.body.status, flashStatus);
-    t.is(flashKnownAppResponse.body.id, DEVICE_ID);
+    t.is(flashKnownAppResponse.body.status, 'Update finished');
+    t.is(flashKnownAppResponse.body.id, CONNECTED_DEVICE_ID);
   },
 );
 
@@ -499,21 +420,15 @@ test.serial(
   'should throws an error if known application not found',
   async t => {
     const knownAppName = 'knownAppName';
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns({});
 
     const flashKnownAppResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .send({
         access_token: userToken,
         app_id: knownAppName,
       });
 
-    deviceServerStub.restore();
-
     t.is(flashKnownAppResponse.status, 404);
     t.is(
       flashKnownAppResponse.body.error,
@@ -525,29 +440,15 @@ test.serial(
 test.serial(
   'should start device flashing process with custom application',
   async t => {
-    const flashStatus = 'update finished';
-    const device = {
-      flash: () => flashStatus,
-    };
-    const flashSpy = sinon.spy(device, 'flash');
-
-    const deviceServerStub = sinon.stub(
-      container.constitute('DeviceServer'),
-      'getDevice',
-    ).returns(device);
-
     const flashCustomFirmwareResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .attach('file', customFirmwareFilePath)
       .query({ access_token: userToken });
 
-    deviceServerStub.restore();
-
     t.is(flashCustomFirmwareResponse.status, 200);
-    t.truthy(flashSpy.calledWith(customFirmwareBuffer));
-    t.is(flashCustomFirmwareResponse.body.status, flashStatus);
-    t.is(flashCustomFirmwareResponse.body.id, DEVICE_ID);
+    t.is(flashCustomFirmwareResponse.body.status, 'Update finished');
+    t.is(flashCustomFirmwareResponse.body.id, CONNECTED_DEVICE_ID);
   },
 );
 
@@ -555,7 +456,7 @@ test.serial(
   'should throw an error if custom firmware file not provided',
   async t => {
     const flashCustomFirmwareResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       .field('file_type', 'binary')
       .query({ access_token: userToken });
@@ -569,7 +470,7 @@ test.serial(
   'should throw an error if custom firmware file type not binary',
   async t => {
     const flashCustomFirmwareResponse = await request(app)
-      .put(`/v1/devices/${DEVICE_ID}`)
+      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
       .set('Content-Type', 'application/x-www-form-urlencoded')
       // send random not binary file
       .attach('file', path.join(__dirname, 'DevicesController.test.js'))
@@ -583,6 +484,8 @@ test.serial(
 test.after.always(async (): Promise => {
   await TestData.deleteCustomFirmwareBinary(customFirmwareFilePath);
   await container.constitute('UserRepository').deleteByID(testUser.id);
-  await container.constitute('DeviceAttributeRepository').deleteByID(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteByID(DEVICE_ID);
+  await container.constitute('DeviceAttributeRepository').deleteByID(CONNECTED_DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteByID(CONNECTED_DEVICE_ID);
+  await container.constitute('DeviceAttributeRepository').deleteByID(DISCONNECTED_DEVICE_ID);
+  await container.constitute('DeviceKeyRepository').deleteByID(DISCONNECTED_DEVICE_ID);
 });
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index 7d391cab..6b7fefd3 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -1,9 +1,11 @@
 /* eslint-disable */
 import test from 'ava';
 import request from 'supertest';
+import sinon from 'sinon';
 import ouathClients from '../src/oauthClients.json';
 import app from './setup/testApp';
 import TestData from './setup/TestData';
+import { SPARK_SERVER_EVENTS } from 'spark-protocol';
 
 const container = app.container;
 let DEVICE_ID;
@@ -16,6 +18,27 @@ test.before(async () => {
   DEVICE_ID = TestData.getID();
   TEST_PUBLIC_KEY = TestData.getPublicKey();
 
+  sinon.stub(
+    container.constitute('EventPublisher'),
+    'publishAndListenForResponse',
+    ({ name }) => {
+      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION) {
+        return {
+          state : {
+            f: null,
+            v: null,
+          },
+        };
+      }
+      if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
+        return {
+          connected: true,
+          lastPing: new Date(),
+        };
+      }
+    }
+  );
+
   const userResponse = await request(app)
     .post('/v1/users')
     .send(USER_CREDENTIALS);

From 940c26d9e1db19cf10e5e756440a4c246341df4d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Fri, 9 Jun 2017 11:54:53 +0200
Subject: [PATCH 401/504] added setting.json so vscode works with flow

---
 .vscode/settings.json | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .vscode/settings.json

diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..86bf6219
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+// Place your settings in this file to overwrite default and user settings.
+{
+    "javascript.validate.enable": false,
+    "flow.useNPMPackagedFlow": true
+}
\ No newline at end of file

From e1a96c2e1464a242763c11721eb22d5285c68b5c Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 9 Jun 2017 17:22:10 +0200
Subject: [PATCH 402/504] fix var name nit

---
 dist/managers/DeviceManager.js | 8 ++++----
 src/managers/DeviceManager.js  | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index fa10e869..6e46fdbb 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -559,7 +559,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.raiseYourHand = function () {
     var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, shouldShowSignal) {
-      var RaiseYourHandResponse, error;
+      var raiseYourHandResponse, error;
       return _regenerator2.default.wrap(function _callee12$(_context12) {
         while (1) {
           switch (_context12.prev = _context12.next) {
@@ -575,8 +575,8 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               });
 
             case 4:
-              RaiseYourHandResponse = _context12.sent;
-              error = RaiseYourHandResponse.error;
+              raiseYourHandResponse = _context12.sent;
+              error = raiseYourHandResponse.error;
 
               if (!error) {
                 _context12.next = 8;
@@ -586,7 +586,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default(error);
 
             case 8:
-              return _context12.abrupt('return', RaiseYourHandResponse);
+              return _context12.abrupt('return', raiseYourHandResponse);
 
             case 9:
             case 'end':
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 99674972..22c735c7 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -289,18 +289,18 @@ class DeviceManager {
       deviceID,
     );
 
-    const RaiseYourHandResponse =
+    const raiseYourHandResponse =
       await this._eventPublisher.publishAndListenForResponse({
         context: { deviceID, shouldShowSignal },
         name: SPARK_SERVER_EVENTS.RAISE_YOUR_HAND,
       });
 
-    const { error } = RaiseYourHandResponse;
+    const { error } = raiseYourHandResponse;
     if (error) {
       throw new HttpError(error);
     }
 
-    return RaiseYourHandResponse;
+    return raiseYourHandResponse;
   };
 
   renameDevice = async (

From 0b764d830e2da7669264f5e90fcb5b7fae059bf7 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 10 Jun 2017 09:25:05 -0700
Subject: [PATCH 403/504] Flow fixes

---
 package-lock.json | 2 +-
 package.json      | 2 +-
 src/app.js        | 2 +-
 src/lib/logger.js | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 3e560355..837ae600 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4134,7 +4134,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#109141fdf255de47c597f3689d0225a421d2393c"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#de560c1ccfd7672fc88e7ba0fbc400462493d8b1"
     },
     "spawn-sync": {
       "version": "1.0.15",
diff --git a/package.json b/package.json
index 43ca84b2..151b3e1e 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,7 @@
     "rimraf": "^2.5.4",
     "rmfr": "^1.0.1",
     "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev",
-    "tingodb": "git+https://github.com/Brewskey/tingodb",
+    "tingodb": "git+https://github.com/Brewskey/tingodb.git",
     "ursa": "^0.9.4",
     "uuid": "^3.0.1"
   },
diff --git a/src/app.js b/src/app.js
index a765a200..aba9eb40 100644
--- a/src/app.js
+++ b/src/app.js
@@ -18,7 +18,7 @@ import routeConfig from './RouteConfig';
 export default (
   container: Container,
   settings: Settings,
-  existingApp?: express,
+  existingApp?: express$Application,
 ): $Application => {
   const app = existingApp || express();
 
diff --git a/src/lib/logger.js b/src/lib/logger.js
index df1135cb..0e2de9b8 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -23,7 +23,7 @@ import chalk from 'chalk';
 import settings from '../settings';
 
 function _transform(...params: Array): Array {
-  return params.map(JSON.stringify);
+  return params.map((param: any): string => JSON.stringify(param));
 }
 
 class Logger {

From 3fc46b2611617ee5ad66ee2d113ccb71a970d965 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Mon, 12 Jun 2017 08:04:47 -0700
Subject: [PATCH 404/504] Removing ursa

---
 README.md                         | 28 +++++++++++-----------------
 package-lock.json                 |  5 +++++
 package.json                      |  4 ++--
 src/lib/logger.js                 |  8 +++++++-
 src/managers/DeviceManager.js     | 13 ++++++++++---
 src/managers/PermissionManager.js |  3 ++-
 src/settings.js                   |  3 ---
 test/setup/TestData.js            | 13 +++----------
 8 files changed, 40 insertions(+), 37 deletions(-)

diff --git a/README.md b/README.md
index d0ae8931..6ba79168 100644
--- a/README.md
+++ b/README.md
@@ -17,29 +17,18 @@ An API compatible open source server for interacting with devices speaking the [
 Quick Install
 ==============
 
-### You'll need to prepare your system for node-gyp. This is used in the URSA package.
-**https://github.com/nodejs/node-gyp**
-
 ```
 git clone https://github.com/Brewskey/spark-server.git
 cd spark-server/
 npm install
-./node_modules/.bin/babel src/ -d lib
-node lib/main.js
 ```
 
 The babel command pre-processes all the src/ to allow modern node
 syntax to be used in older versions of node. The modified code that is
-actually running lives in lib/
-If you change anything in src/ you'll need to rerun babel for changes
+actually running lives in dist/
+If you change anything in src/ you'll need to rerun `npm build` for changes
 to take effect.
- 
-> **Windows Setup**  
-> You'll need to install Python 2.7 and OpenSSL 1.0.2 or older.  
-> *The newer version doesn't have the lib files needed to build the project*.  
-> [Python Download](https://www.python.org/downloads/)  
-> [OpenSSL Download](http://slproweb.com/products/Win32OpenSSL.html)  
-  
+
 [Raspberry pi Quick Install](raspberryPi.md)
 
 
@@ -47,9 +36,14 @@ How do I get started?
 =====================
 
 1) Run the server with:
+Run with babel (useful for local development)
+```
+npm start
+```
 
+For production - uses transpiled files from babel.
 ```
-node lib/main.js
+npm run start:prod
 ```
 
 2) Watch for your IP address, you'll see something like:
@@ -68,7 +62,7 @@ For the local cloud, the port number 8080 needs to be added behind: `http://doma
 
 This will create a new profile to point to your server and switching back to the spark cloud is simply `particle config particle` and other profiles would be `particle config profile_name`
 
-4) We will now point over to the local cloud using 
+4) We will now point over to the local cloud using
 ```
 particle config profile_name
 ```
@@ -83,7 +77,7 @@ This will create an account on the local cloud
 
 Perform CTRL + C once you logon with Particle-CLI asking you to send Wifi-credentials etc...
 
-6) Put your core into listening mode, and run 
+6) Put your core into listening mode, and run
 ```
 particle identify
 ```
diff --git a/package-lock.json b/package-lock.json
index 837ae600..ff21fb38 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3328,6 +3328,11 @@
       "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
       "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
     },
+    "node-rsa": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
+      "integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA="
+    },
     "node-status-codes": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
diff --git a/package.json b/package.json
index 151b3e1e..bae2d19d 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
     "prebuild": "npm run build:clean",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
-    "start:prod": "npm run build && node ./dist/main.js",
+    "start:prod": "node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",
@@ -75,13 +75,13 @@
     "mongodb": "^2.2.26",
     "morgan": "^1.7.0",
     "multer": "^1.2.1",
+    "node-rsa": "^0.4.2",
     "nullthrows": "^1.0.0",
     "request": "*",
     "rimraf": "^2.5.4",
     "rmfr": "^1.0.1",
     "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev",
     "tingodb": "git+https://github.com/Brewskey/tingodb.git",
-    "ursa": "^0.9.4",
     "uuid": "^3.0.1"
   },
   "devDependencies": {
diff --git a/src/lib/logger.js b/src/lib/logger.js
index 0e2de9b8..d25d64a5 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -23,7 +23,13 @@ import chalk from 'chalk';
 import settings from '../settings';
 
 function _transform(...params: Array): Array {
-  return params.map((param: any): string => JSON.stringify(param));
+  return params.map((param: any): string => {
+    if (typeof param === 'string') {
+      return param;
+    }
+
+    return JSON.stringify(param);
+  });
 }
 
 class Logger {
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 22c735c7..f3474403 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -12,7 +12,7 @@ import type {
 } from '../types';
 
 import { SPARK_SERVER_EVENTS } from 'spark-protocol';
-import ursa from 'ursa';
+import NodeRSA from 'node-rsa';
 import HttpError from '../lib/HttpError';
 
 class DeviceManager {
@@ -256,8 +256,15 @@ class DeviceManager {
     }
 
     try {
-      const createdKey = ursa.createPublicKey(publicKey);
-      if (!ursa.isPublicKey(createdKey)) {
+      const createdKey = new NodeRSA(
+        publicKey,
+        'pkcs1-public-pem',
+        {
+          encryptionScheme: 'pkcs1',
+          signingScheme: 'pkcs1',
+        },
+      );
+      if (!createdKey.isPublic()) {
         throw new HttpError('Not a public key');
       }
     } catch (error) {
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index 548c853c..d2780aa0 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -117,7 +117,8 @@ class PermissionManager {
       await this._userRepository.getByUsername(settings.DEFAULT_ADMIN_USERNAME);
     if (defaultAdminUser) {
       logger.info(
-        `Default admin accessToken: ${defaultAdminUser.accessTokens[0].accessToken}`,
+        'Default admin accessToken: ' +
+          `${defaultAdminUser.accessTokens[0].accessToken}`,
       );
     } else {
       await this._createDefaultAdminUser();
diff --git a/src/settings.js b/src/settings.js
index 8b9b2a4b..d866e3b3 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -26,14 +26,11 @@ export default {
   BUILD_DIRECTORY: path.join(__dirname, '../data/build'),
   DEFAULT_ADMIN_PASSWORD: 'adminPassword',
   DEFAULT_ADMIN_USERNAME: '__admin__',
-  DEVICE_DIRECTORY: path.join(__dirname, '../data/deviceKeys'),
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: path.join(__dirname, '../data/knownApps'),
   FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../../spark-firmware'),
   SERVER_KEY_FILENAME: 'default_key.pem',
   SERVER_KEYS_DIRECTORY: path.join(__dirname, '../data'),
-  USERS_DIRECTORY: path.join(__dirname, '../data/users'),
-  WEBHOOKS_DIRECTORY: path.join(__dirname, '../data/webhooks'),
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_ALGORITHM: 'aes-128-cbc',
diff --git a/test/setup/TestData.js b/test/setup/TestData.js
index 285932d8..dc4ce451 100644
--- a/test/setup/TestData.js
+++ b/test/setup/TestData.js
@@ -4,13 +4,11 @@ import type { UserCredentials } from '../../src/types';
 
 import crypto from 'crypto';
 import uuid from 'uuid';
-import ursa from 'ursa';
+import NodeRSA from 'node-rsa';
 import fs from 'fs';
 import settings from './settings';
 
-
 const uuidSet = new Set();
-const privateKeys = new Set();
 
 type CreateCustomFirmwareResult = {
   filePath: string,
@@ -74,14 +72,9 @@ class TestData {
   };
 
   static getPublicKey = (): string => {
-    let key = ursa.generatePrivateKey();
-
-    while (privateKeys.has(key.toPrivatePem())) {
-      key = ursa.generatePrivateKey();
-    }
+    const key = new NodeRSA({ b: 1024 });
 
-    privateKeys.add(key.toPrivatePem());
-    return key.toPublicPem();
+    return key.exportKey('pkcs1-public-pem');
   };
 }
 

From 5a86d20ea168e289bf325b5692c24cd903b8cb9d Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Mon, 12 Jun 2017 08:18:01 -0700
Subject: [PATCH 405/504] Allow custom template replacement on webhooks.

---
 src/managers/WebhookManager.js | 2 ++
 src/settings.js                | 7 +++++++
 src/types.js                   | 1 +
 test/setup/settings.js         | 1 +
 4 files changed, 11 insertions(+)

diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 3269fa31..ac7c8ab0 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -17,6 +17,7 @@ import HttpError from '../lib/HttpError';
 import logger from '../lib/logger';
 import nullthrows from 'nullthrows';
 import request from 'request';
+import settings from '../settings';
 import throttle from 'lodash/throttle';
 
 const parseEventData = (event: Event): Object => {
@@ -361,6 +362,7 @@ class WebhookManager {
       SPARK_EVENT_NAME: event.name,
       SPARK_EVENT_VALUE: event.data,
       SPARK_PUBLISHED_AT: event.publishedAt,
+      ...settings.WEBHOOK_TEMPLATE_PARAMETERS,
     };
 
     const eventDataVariables = parseEventData(event);
diff --git a/src/settings.js b/src/settings.js
index d866e3b3..fcd69278 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -26,11 +26,14 @@ export default {
   BUILD_DIRECTORY: path.join(__dirname, '../data/build'),
   DEFAULT_ADMIN_PASSWORD: 'adminPassword',
   DEFAULT_ADMIN_USERNAME: '__admin__',
+  DEVICE_DIRECTORY: path.join(__dirname, '../data/deviceKeys'),
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: path.join(__dirname, '../data/knownApps'),
   FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../../spark-firmware'),
   SERVER_KEY_FILENAME: 'default_key.pem',
   SERVER_KEYS_DIRECTORY: path.join(__dirname, '../data'),
+  USERS_DIRECTORY: path.join(__dirname, '../data/users'),
+  WEBHOOKS_DIRECTORY: path.join(__dirname, '../data/webhooks'),
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_ALGORITHM: 'aes-128-cbc',
@@ -57,4 +60,8 @@ export default {
     HOST: 'localhost',
     PORT: 5683,
   },
+  // Override template parameters in webhooks with this object
+  WEBHOOK_TEMPLATE_PARAMETERS: {
+    // SOME_AUTH_TOKEN: '12312312',
+  },
 };
diff --git a/src/types.js b/src/types.js
index ef1764ae..9a1380c0 100644
--- a/src/types.js
+++ b/src/types.js
@@ -164,6 +164,7 @@ export type Settings = {
     PORT: number,
   },
   USERS_DIRECTORY: string,
+  WEBHOOK_TEMPLATE_PARAMETERS: {[key: string]: string},
   WEBHOOKS_DIRECTORY: string,
 };
 
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 92fae39c..9490cc47 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -44,4 +44,5 @@ export default {
     PATH: path.join(__dirname, '../__test_data__/db'),
     URL: null,
   },
+  WEBHOOK_TEMPLATE_PARAMETERS: {},
 };

From da7d9adc52d1248a1de668889d01c2c3c64dae3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Tue, 13 Jun 2017 10:23:45 +0200
Subject: [PATCH 406/504] Added Image to Docker HUB, remove Node-GYP from Build
 Process

---
 examples/docker/production/Dockerfile | 25 ++++++++++++++-----------
 examples/docker/readme.md             |  6 ++++++
 2 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/examples/docker/production/Dockerfile b/examples/docker/production/Dockerfile
index b3a61500..f2083c03 100644
--- a/examples/docker/production/Dockerfile
+++ b/examples/docker/production/Dockerfile
@@ -1,11 +1,13 @@
+
 #
 # Local Cloud Sparc-Server  
 #
 
 #
-# based on Alpine Node (version 6 or 7 is tested, other Version should work as well)
+# based on Alpine Node (version 8 is tested, other Version should work as well)
 #
-FROM mhart/alpine-node:6
+FROM mhart/alpine-node:7
+
 
 # create the Working Directory
 RUN mkdir -p /usr/src/localCloud
@@ -13,18 +15,17 @@ RUN mkdir -p /usr/src/localCloud
 # install all dependencies and delete them in ONE single RUN
 # you can split this into different steps, but this will make the Image MUCH larger!
 RUN apk add --no-cache git; \
-    apk add --no-cache python; \
-    apk add --no-cache make; \
-    apk add --no-cache g++; \
     cd /usr/src/localCloud; \
-    npm install -g node-gyp; \
     git clone https://github.com/Brewskey/spark-server.git; \
-    cd /usr/src/localCloud/spark-server; \
+    cd  /usr/src/localCloud/spark-server; \
+    git checkout c182732cad6075354846f6034076fe987d599994; \
+    rm -rf .git; \
     npm install; \
-    npm run update-firmware; \
-    npm run prebuild; \    
-    npm run build; \    
-    apk del python make g++
+    apk del git; \
+    npm run prebuild; \
+    npm run build
+
+
 
 # Set Working Directory
 WORKDIR /usr/src/localCloud/spark-server
@@ -39,6 +40,8 @@ EXPOSE 8080
 # Expose DataDirectory to store DB and Device Keys 
 VOLUME /usr/src/localCloud/spark-server/data
 
+
+
 ENTRYPOINT ["npm", "run", "start:prod"]
 
 
diff --git a/examples/docker/readme.md b/examples/docker/readme.md
index 8362ccc7..2fc4df73 100644
--- a/examples/docker/readme.md
+++ b/examples/docker/readme.md
@@ -1,5 +1,11 @@
 
+# Run Spark Server from Docker HUB
 
+If you have installed Docker you can start a ParticleServer directly from CommandLine.
+
+[https://hub.docker.com/r/keatec/spark-server/](https://hub.docker.com/r/keatec/spark-server/)
+
+Follow link for more informations !
 
 
 # Build a Spark-Server as a Docker Image

From 03a2cd34e2e45d965c3313d9728b520e7afbf740 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Tue, 13 Jun 2017 15:46:18 +0200
Subject: [PATCH 407/504] first text

---
 examples/DockerCompile/readme.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 examples/DockerCompile/readme.md

diff --git a/examples/DockerCompile/readme.md b/examples/DockerCompile/readme.md
new file mode 100644
index 00000000..e69de29b

From 3603a02d240b26c214e075cf7dbd56592f614b5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Tue, 13 Jun 2017 15:54:42 +0200
Subject: [PATCH 408/504] Added ReadMe on Compile with docker

---
 examples/DockerCompile/readme.md | 36 ++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/examples/DockerCompile/readme.md b/examples/DockerCompile/readme.md
index e69de29b..96ceff5e 100644
--- a/examples/DockerCompile/readme.md
+++ b/examples/DockerCompile/readme.md
@@ -0,0 +1,36 @@
+
+
+# Using Docker to compile Firmware local (on any OS supporting Docker)
+
+Particle provides docker images including the complete toolchain.
+
+[https://hub.docker.com/r/particle/buildpack-particle-firmware/](https://hub.docker.com/r/particle/buildpack-particle-firmware/)
+
+to compile your source against a specific firmware version follow the following instructions
+
+## Preparations
+
+You need 2 directory "input" and "output". "input" will contains all your sources ()
+
+
+```
+
+function xcompile () {
+    echo "#define EXCONFIG \",$1,\"" > code/config.h
+    echo "#define EXCOMPILETIME \""$version $(date +"%Y-%m-%d_%H-%M-%S")"\"" > code/config_compile.h
+    echo ""
+    echo ""
+    echo "--> Compile $2 - $1"
+    rm ./images/firmware.bin 2> /dev/null
+    docker run --rm  \
+        -v /keastick/RemoteRep/sm_firmware/code:/input \
+        -v /keastick/RemoteRep/sm_firmware/images:/output \
+        -e PLATFORM_ID=6 particle/buildpack-particle-firmware:0.6.2-photon \
+         | grep -A2 " warning:\| error:"
+    for f in images/firmware.bin; do 
+        mv -- "$f" "${f%}.$2.bin"
+    done
+}
+
+
+```
\ No newline at end of file

From 4de6cc6274e026a1dab31d22f6e655fad3fcadad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Tue, 13 Jun 2017 15:56:22 +0200
Subject: [PATCH 409/504] Patch

---
 examples/DockerCompile/readme.md | 47 ++++++++++++++++++--------------
 1 file changed, 27 insertions(+), 20 deletions(-)

diff --git a/examples/DockerCompile/readme.md b/examples/DockerCompile/readme.md
index 96ceff5e..8cccef65 100644
--- a/examples/DockerCompile/readme.md
+++ b/examples/DockerCompile/readme.md
@@ -10,27 +10,34 @@ to compile your source against a specific firmware version follow the following
 
 ## Preparations
 
-You need 2 directory "input" and "output". "input" will contains all your sources ()
+You need 2 directory "input" and "output". "input" will contain all your sources (the user directory), output will contain the firmware binaries.
 
+__Note__: You have to rename your *.ino file to application.cpp to make this work.
 
+Let's asume your directores are 
 ```
+/runtime/sources
+``` 
+
+```
+/runtime/binaries
+```
+
+Choose your plattform and version from available tags [TAGS](https://hub.docker.com/r/particle/buildpack-particle-firmware/tags/).
+
+We will use 
+```
+0.6.2-photon
+``` 
+for the latest stable firmware on photon
+
+## Execute
+
+So now compilation can be executed by running
+
+```
+    docker run --rm -v /runtime/sources:/input -v /runtime/binaries:/output -e PLATFORM_ID=6 particle/buildpack-particle-firmware:0.6.2-photon
+```
+
+You will find 3 Files (Sytem part 1, System part 2 and your USERPART in output directory)
 
-function xcompile () {
-    echo "#define EXCONFIG \",$1,\"" > code/config.h
-    echo "#define EXCOMPILETIME \""$version $(date +"%Y-%m-%d_%H-%M-%S")"\"" > code/config_compile.h
-    echo ""
-    echo ""
-    echo "--> Compile $2 - $1"
-    rm ./images/firmware.bin 2> /dev/null
-    docker run --rm  \
-        -v /keastick/RemoteRep/sm_firmware/code:/input \
-        -v /keastick/RemoteRep/sm_firmware/images:/output \
-        -e PLATFORM_ID=6 particle/buildpack-particle-firmware:0.6.2-photon \
-         | grep -A2 " warning:\| error:"
-    for f in images/firmware.bin; do 
-        mv -- "$f" "${f%}.$2.bin"
-    done
-}
-
-
-```
\ No newline at end of file

From 7d9984d339f53ff4e2daa721a71ad459100f4caa Mon Sep 17 00:00:00 2001
From: straccio 
Date: Tue, 13 Jun 2017 17:09:30 +0200
Subject: [PATCH 410/504] Pre fork from Brewskey

---
 .gitignore            |  1 +
 js/main.js            |  2 +-
 js/oauth_clients.json | 10 ++++++++++
 js/settings.js        |  2 +-
 4 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore
index f87b0742..5e6fb08a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,4 @@ spark-server.esproj
 .DS_Store
 /js/oauth_clients.json
 js/orgs/*
+.idea
diff --git a/js/main.js b/js/main.js
index 01e79a01..a5216fad 100644
--- a/js/main.js
+++ b/js/main.js
@@ -104,7 +104,7 @@ app.use(function (req, res, next) {
 });
 
 
-var node_port = process.env.NODE_PORT || '9000';
+var node_port = process.env.NODE_PORT ||(settings.http_port || '9000');
 node_port = parseInt(node_port);
 
 console.log("Starting server, listening on " + node_port);
diff --git a/js/oauth_clients.json b/js/oauth_clients.json
index 82af918d..7c19fced 100644
--- a/js/oauth_clients.json
+++ b/js/oauth_clients.json
@@ -1,7 +1,17 @@
 [
+	{
+		"client_id" : "CLI2",
+		"client_secret" : "client_secret_here",
+		"grants" : [ "password", "refresh_token" ]
+	},
 	{
 		"client_id" : "particle",
 		"client_secret" : "particle",
 		"grants" : [ "password", "refresh_token" ]
+	},
+	{
+		"client_id" : "DIYSmartDomoApi",
+		"client_secret" : "DIYSmartDomoApi",
+		"grants" : [ "password", "refresh_token" ]
 	}
 ]
\ No newline at end of file
diff --git a/js/settings.js b/js/settings.js
index 8b80a958..b9caed00 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -22,7 +22,7 @@ module.exports = {
     baseUrl: "http://localhost",
     userDataDir: path.join(__dirname, "users"),
     coreKeysDir: path.join(__dirname, "core_keys"),
-
+    http_port:8080,
     coreRequestTimeout: 3000,
     isCoreOnlineTimeout: 2000,
 

From f4beeda7210f014fb5f913bac9be068dcca34319 Mon Sep 17 00:00:00 2001
From: straccio 
Date: Tue, 13 Jun 2017 18:25:16 +0200
Subject: [PATCH 411/504] Package for debugging purpose

---
 package-lock.json | 5 ++++-
 package.json      | 4 +++-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index ff21fb38..a214566e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2485,7 +2485,10 @@
       "integrity": "sha1-JEh3MFMOSwDkm1rxcEldrPpAEb4="
     },
     "h5.coap": {
-      "version": "git+https://github.com/morkai/h5.coap.git#51c3b2a4cb1af7f43d20e25a9d8658fed9169b9c"
+      "version": "git://github.com/morkai/h5.coap.git#51c3b2a4cb1af7f43d20e25a9d8658fed9169b9c"
+    },
+    "h5.linkformat": {
+      "version": "git://github.com/morkai/h5.linkformat.git#3aa6f80462b14761feeeab36e5a751c65d27975f"
     },
     "har-schema": {
       "version": "1.0.5",
diff --git a/package.json b/package.json
index bae2d19d..d295ccca 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "migrate-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
     "prebuild": "npm run build:clean",
-    "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
+    "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
     "start:prod": "node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
@@ -68,6 +68,8 @@
     "constitute": "^1.6.2",
     "express": "^4.14.0",
     "express-oauth-server": "^2.0.0-b3",
+    "h5.coap": "git://github.com/morkai/h5.coap.git",
+    "h5.linkformat": "git://github.com/morkai/h5.linkformat.git",
     "hogan.js": "^3.0.2",
     "lodash": "^4.17.4",
     "mkdirp": "^0.5.1",

From 67c92c38b50a1b041ec303c2c0647383c7afae22 Mon Sep 17 00:00:00 2001
From: straccio 
Date: Tue, 13 Jun 2017 18:26:17 +0200
Subject: [PATCH 412/504] Corrected get devices that now return "connected"
 attribute correctly

---
 src/managers/DeviceManager.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index f3474403..830d9256 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -132,11 +132,10 @@ class DeviceManager {
 
     const devicePromises = devicesAttributes.map(
       async (attributes: DeviceAttributes): Promise => {
-        const pingResponse = this._eventPublisher.publishAndListenForResponse({
+        const pingResponse = await this._eventPublisher.publishAndListenForResponse({
           context: { deviceID: attributes.deviceID },
           name: SPARK_SERVER_EVENTS.PING_DEVICE,
         });
-
         return {
           ...attributes,
           connected: pingResponse.connected || false,

From 4f3d5ca82d9a6a932678d7ed7dfd528ef48c885c Mon Sep 17 00:00:00 2001
From: straccio 
Date: Tue, 13 Jun 2017 22:06:07 +0200
Subject: [PATCH 413/504] Removed h5.coap and added script for debugging

---
 package.json | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index d295ccca..1a446956 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,8 @@
     "migrate-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
     "prebuild": "npm run build:clean",
-    "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
+    "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
+    "start:debug": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
     "start:prod": "node ./dist/main.js",
     "test": "ava --serial",
     "test:watch": "ava --watch --serial",
@@ -68,8 +69,6 @@
     "constitute": "^1.6.2",
     "express": "^4.14.0",
     "express-oauth-server": "^2.0.0-b3",
-    "h5.coap": "git://github.com/morkai/h5.coap.git",
-    "h5.linkformat": "git://github.com/morkai/h5.linkformat.git",
     "hogan.js": "^3.0.2",
     "lodash": "^4.17.4",
     "mkdirp": "^0.5.1",

From 9fd861e9e1c76750e3f904aba7cd9b4f20e99d73 Mon Sep 17 00:00:00 2001
From: straccio 
Date: Tue, 13 Jun 2017 22:21:56 +0200
Subject: [PATCH 414/504] package-lock.json

---
 package-lock.json | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index a214566e..51acec89 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2484,12 +2484,6 @@
       "resolved": "https://registry.npmjs.org/h5.buffers/-/h5.buffers-0.1.1.tgz",
       "integrity": "sha1-JEh3MFMOSwDkm1rxcEldrPpAEb4="
     },
-    "h5.coap": {
-      "version": "git://github.com/morkai/h5.coap.git#51c3b2a4cb1af7f43d20e25a9d8658fed9169b9c"
-    },
-    "h5.linkformat": {
-      "version": "git://github.com/morkai/h5.linkformat.git#3aa6f80462b14761feeeab36e5a751c65d27975f"
-    },
     "har-schema": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",

From 12eb7eb25961b4b81d89940c6ec81729b1627cd2 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 14 Jun 2017 02:10:43 +0200
Subject: [PATCH 415/504] change update to updateByID and use as PATH instead
 PUT

---
 dist/lib/logger.js                            |  8 +-
 dist/managers/DeviceManager.js                | 91 +++++++++----------
 dist/managers/PermissionManager.js            |  2 +-
 dist/managers/WebhookManager.js               |  8 +-
 .../DeviceAttributeDatabaseRepository.js      |  8 +-
 .../repository/DeviceKeyDatabaseRepository.js |  8 +-
 dist/repository/UserDatabaseRepository.js     | 10 +-
 dist/repository/UserFileRepository.js         | 54 ++++++-----
 dist/repository/WebhookDatabaseRepository.js  |  2 +-
 dist/repository/WebhookFileRepository.js      | 38 ++++----
 dist/settings.js                              |  4 +
 src/managers/DeviceManager.js                 | 47 +++++-----
 .../DeviceAttributeDatabaseRepository.js      |  9 +-
 src/repository/DeviceKeyDatabaseRepository.js |  9 +-
 src/repository/UserDatabaseRepository.js      | 10 +-
 src/repository/UserFileRepository.js          | 43 ++++-----
 src/repository/WebhookDatabaseRepository.js   |  2 +-
 src/repository/WebhookFileRepository.js       |  4 +-
 src/types.js                                  |  4 +-
 test/UserFileRepository.test.js               |  2 +-
 20 files changed, 188 insertions(+), 175 deletions(-)

diff --git a/dist/lib/logger.js b/dist/lib/logger.js
index 682ef9e6..64e84de7 100644
--- a/dist/lib/logger.js
+++ b/dist/lib/logger.js
@@ -52,7 +52,13 @@ function _transform() {
     params[_key] = arguments[_key];
   }
 
-  return params.map(_stringify2.default);
+  return params.map(function (param) {
+    if (typeof param === 'string') {
+      return param;
+    }
+
+    return (0, _stringify2.default)(param);
+  });
 }
 
 var Logger = function () {
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 6e46fdbb..0c9983a6 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -12,14 +12,14 @@ var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
 
 var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
 
-var _regenerator = require('babel-runtime/regenerator');
-
-var _regenerator2 = _interopRequireDefault(_regenerator);
-
 var _extends2 = require('babel-runtime/helpers/extends');
 
 var _extends3 = _interopRequireDefault(_extends2);
 
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
@@ -30,9 +30,9 @@ var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
 var _sparkProtocol = require('spark-protocol');
 
-var _ursa = require('ursa');
+var _nodeRsa = require('node-rsa');
 
-var _ursa2 = _interopRequireDefault(_ursa);
+var _nodeRsa2 = _interopRequireDefault(_nodeRsa);
 
 var _HttpError = require('../lib/HttpError');
 
@@ -47,7 +47,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.claimDevice = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) {
-      var deviceAttributes, attributesToSave;
+      var deviceAttributes;
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -82,16 +82,13 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('The device is already claimed.');
 
             case 9:
-              attributesToSave = (0, _extends3.default)({}, deviceAttributes, {
-                ownerID: userID
-              });
-              _context.next = 12;
-              return _this._deviceAttributeRepository.update(attributesToSave);
+              _context.next = 11;
+              return _this._deviceAttributeRepository.updateByID(deviceID, { ownerID: userID });
 
-            case 12:
+            case 11:
               return _context.abrupt('return', _context.sent);
 
-            case 13:
+            case 12:
             case 'end':
               return _context.stop();
           }
@@ -106,7 +103,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.unclaimDevice = function () {
     var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
-      var deviceAttributes, attributesToSave;
+      var deviceAttributes;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
@@ -125,16 +122,13 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('No device found', 404);
 
             case 5:
-              attributesToSave = (0, _extends3.default)({}, deviceAttributes, {
-                ownerID: null
-              });
-              _context2.next = 8;
-              return _this._deviceAttributeRepository.update(attributesToSave);
+              _context2.next = 7;
+              return _this._deviceAttributeRepository.updateByID(deviceID, { ownerID: null });
 
-            case 8:
+            case 7:
               return _context2.abrupt('return', _context2.sent);
 
-            case 9:
+            case 8:
             case 'end':
               return _context2.stop();
           }
@@ -265,17 +259,21 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
                   while (1) {
                     switch (_context5.prev = _context5.next) {
                       case 0:
-                        pingResponse = _this._eventPublisher.publishAndListenForResponse({
+                        _context5.next = 2;
+                        return _this._eventPublisher.publishAndListenForResponse({
                           context: { deviceID: attributes.deviceID },
                           name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE
                         });
+
+                      case 2:
+                        pingResponse = _context5.sent;
                         return _context5.abrupt('return', (0, _extends3.default)({}, attributes, {
                           connected: pingResponse.connected || false,
                           lastFlashedAppName: null,
                           lastHeard: pingResponse.lastPing || attributes.lastHeard
                         }));
 
-                      case 2:
+                      case 4:
                       case 'end':
                         return _context5.stop();
                     }
@@ -485,7 +483,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.provision = function () {
     var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey, algorithm) {
-      var createdKey, existingAttributes, attributes;
+      var createdKey;
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
@@ -499,9 +497,12 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 2:
               _context11.prev = 2;
-              createdKey = _ursa2.default.createPublicKey(publicKey);
+              createdKey = new _nodeRsa2.default(publicKey, 'pkcs1-public-pem', {
+                encryptionScheme: 'pkcs1',
+                signingScheme: 'pkcs1'
+              });
 
-              if (_ursa2.default.isPublicKey(createdKey)) {
+              if (createdKey.isPublic()) {
                 _context11.next = 6;
                 break;
               }
@@ -519,32 +520,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 11:
               _context11.next = 13;
-              return _this._deviceKeyRepository.update({ deviceID: deviceID, key: publicKey });
+              return _this._deviceKeyRepository.updateByID(deviceID, {
+                deviceID: deviceID,
+                key: publicKey
+              });
 
             case 13:
               _context11.next = 15;
-              return _this._deviceAttributeRepository.getByID(deviceID);
-
-            case 15:
-              existingAttributes = _context11.sent;
-              attributes = (0, _extends3.default)({
-                deviceID: deviceID
-              }, existingAttributes, {
+              return _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: userID,
                 registrar: userID,
                 timestamp: new Date()
               });
-              _context11.next = 19;
-              return _this._deviceAttributeRepository.update(attributes);
 
-            case 19:
-              _context11.next = 21;
+            case 15:
+              _context11.next = 17;
               return _this.getByID(deviceID);
 
-            case 21:
+            case 17:
               return _context11.abrupt('return', _context11.sent);
 
-            case 22:
+            case 18:
             case 'end':
               return _context11.stop();
           }
@@ -603,7 +599,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.renameDevice = function () {
     var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, name) {
-      var attributes, attributesToSave;
+      var attributes;
       return _regenerator2.default.wrap(function _callee13$(_context13) {
         while (1) {
           switch (_context13.prev = _context13.next) {
@@ -622,16 +618,13 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('No device found', 404);
 
             case 5:
-              attributesToSave = (0, _extends3.default)({}, attributes, {
-                name: name
-              });
-              _context13.next = 8;
-              return _this._deviceAttributeRepository.update(attributesToSave);
+              _context13.next = 7;
+              return _this._deviceAttributeRepository.updateByID(deviceID, { name: name });
 
-            case 8:
+            case 7:
               return _context13.abrupt('return', _context13.sent);
 
-            case 9:
+            case 8:
             case 'end':
               return _context13.stop();
           }
diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index 8e9c1721..ff7f260a 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -242,7 +242,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
               break;
             }
 
-            _logger2.default.info('Default admin accessToken: ' + defaultAdminUser.accessTokens[0].accessToken);
+            _logger2.default.info('Default admin accessToken: ' + ('' + defaultAdminUser.accessTokens[0].accessToken));
             _context6.next = 9;
             break;
 
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 07228e98..f298e036 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -52,6 +52,10 @@ var _request = require('request');
 
 var _request2 = _interopRequireDefault(_request);
 
+var _settings = require('../settings');
+
+var _settings2 = _interopRequireDefault(_settings);
+
 var _throttle = require('lodash/throttle');
 
 var _throttle2 = _interopRequireDefault(_throttle);
@@ -423,7 +427,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
   };
 
   this._getEventVariables = function (event) {
-    var defaultWebhookVariables = {
+    var defaultWebhookVariables = (0, _extends3.default)({
       PARTICLE_DEVICE_ID: event.deviceID,
       PARTICLE_EVENT_NAME: event.name,
       PARTICLE_EVENT_VALUE: event.data,
@@ -433,7 +437,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
       SPARK_EVENT_NAME: event.name,
       SPARK_EVENT_VALUE: event.data,
       SPARK_PUBLISHED_AT: event.publishedAt
-    };
+    }, _settings2.default.WEBHOOK_TEMPLATE_PARAMETERS);
 
     var eventDataVariables = parseEventData(event);
 
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index cd4cdea4..faf24723 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -121,14 +121,14 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     };
   }();
 
-  this.update = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(model) {
+  this.updateByID = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, props) {
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { deviceID: model.deviceID }, null, { $set: (0, _extends3.default)({}, model, { timeStamp: new Date() }) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, null, { $set: (0, _extends3.default)({}, props, { timeStamp: new Date() }) }, { new: true, upsert: true });
 
             case 2:
               return _context5.abrupt('return', _context5.sent);
@@ -141,7 +141,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
       }, _callee5, _this);
     }));
 
-    return function (_x4) {
+    return function (_x4, _x5) {
       return _ref5.apply(this, arguments);
     };
   }();
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index f7dfb35c..5d28ee82 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -119,14 +119,14 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
     };
   }();
 
-  this.update = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(model) {
+  this.updateByID = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, props) {
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { deviceID: model.deviceID }, null, { $set: (0, _extends3.default)({}, model) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, null, { $set: (0, _extends3.default)({}, props) }, { new: true, upsert: true });
 
             case 2:
               return _context5.abrupt('return', _context5.sent);
@@ -139,7 +139,7 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
       }, _callee5, _this);
     }));
 
-    return function (_x4) {
+    return function (_x4, _x5) {
       return _ref5.apply(this, arguments);
     };
   }();
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 9ce3b597..1af2423a 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -316,14 +316,14 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
     _this._currentUser = user;
   };
 
-  this.update = function () {
-    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(model) {
+  this.updateByID = function () {
+    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id, props) {
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
             case 0:
               _context11.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: model.id }, null, { $set: (0, _extends3.default)({}, model, { timeStamp: new Date() }) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: id }, null, { $set: (0, _extends3.default)({}, props, { timeStamp: new Date() }) }, { new: true, upsert: true });
 
             case 2:
               return _context11.abrupt('return', _context11.sent);
@@ -336,7 +336,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee11, _this);
     }));
 
-    return function (_x13) {
+    return function (_x13, _x14) {
       return _ref11.apply(this, arguments);
     };
   }();
@@ -392,7 +392,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
       }, _callee12, _this, [[0, 14]]);
     }));
 
-    return function (_x14, _x15) {
+    return function (_x15, _x16) {
       return _ref12.apply(this, arguments);
     };
   }();
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index 18155125..ac42ec06 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -8,14 +8,14 @@ var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-pr
 
 var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
 
-var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
-
-var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
-
 var _extends2 = require('babel-runtime/helpers/extends');
 
 var _extends3 = _interopRequireDefault(_extends2);
 
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -132,7 +132,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
     this.deleteAccessToken = function () {
       var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userID, token) {
-        var user, userToSave;
+        var user;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
@@ -151,13 +151,15 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                 throw new Error('User doesn\'t exist');
 
               case 5:
-                userToSave = (0, _extends3.default)({}, user, {
+                _context2.next = 7;
+                return _this.updateByID(userID, {
                   accessTokens: user.accessTokens.filter(function (tokenObject) {
                     return tokenObject.accessToken !== token;
                   })
                 });
-                _context2.next = 8;
-                return _this.update(userToSave);
+
+              case 7:
+                return _context2.abrupt('return', _context2.sent);
 
               case 8:
               case 'end':
@@ -209,7 +211,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
     this.saveAccessToken = function () {
       var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, tokenObject) {
-        var user, userToSave;
+        var user;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -228,16 +230,13 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                 throw new _HttpError2.default('Could not find user for user ID');
 
               case 5:
-                userToSave = (0, _extends3.default)({}, user, {
-                  accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject])
-                });
-                _context4.next = 8;
-                return _this.update(userToSave);
+                _context4.next = 7;
+                return _this.updateByID(userID, { accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]) });
 
-              case 8:
+              case 7:
                 return _context4.abrupt('return', _context4.sent);
 
-              case 9:
+              case 8:
               case 'end':
                 return _context4.stop();
             }
@@ -504,17 +503,26 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
       return isUserNameInUse;
     }()
   }, {
-    key: 'update',
+    key: 'updateByID',
     value: function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(model) {
+      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(id, props) {
+        var user, modelToSave;
         return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
             switch (_context12.prev = _context12.next) {
               case 0:
-                this._fileManager.writeFile(model.id + '.json', model);
-                return _context12.abrupt('return', model);
+                _context12.next = 2;
+                return this.getByID(id);
 
               case 2:
+                user = _context12.sent;
+                modelToSave = (0, _extends3.default)({}, user || {}, props);
+
+
+                this._fileManager.writeFile(id + '.json', modelToSave);
+                return _context12.abrupt('return', modelToSave);
+
+              case 6:
               case 'end':
                 return _context12.stop();
             }
@@ -522,13 +530,13 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
         }, _callee12, this);
       }));
 
-      function update(_x15) {
+      function updateByID(_x15, _x16) {
         return _ref12.apply(this, arguments);
       }
 
-      return update;
+      return updateByID;
     }()
   }]);
   return UserFileRepository;
-}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'update', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'update'), _class.prototype)), _class));
+}(), (_applyDecoratedDescriptor(_class.prototype, 'create', [_dec], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'create'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteByID', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAll', [_dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAll'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByID', [_dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByID'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getByUsername', [_dec5], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getByUsername'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'isUserNameInUse', [_dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'isUserNameInUse'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateByID', [_dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateByID'), _class.prototype)), _class));
 exports.default = UserFileRepository;
\ No newline at end of file
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index 31aa9bd2..01da6aae 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -133,7 +133,7 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
     };
   }();
 
-  this.update = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+  this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
     return _regenerator2.default.wrap(function _callee5$(_context5) {
       while (1) {
         switch (_context5.prev = _context5.next) {
diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js
index 2fe18d09..afe2f171 100644
--- a/dist/repository/WebhookFileRepository.js
+++ b/dist/repository/WebhookFileRepository.js
@@ -116,26 +116,20 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
       };
     }();
 
-    this.update = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(model) {
-        return _regenerator2.default.wrap(function _callee2$(_context2) {
-          while (1) {
-            switch (_context2.prev = _context2.next) {
-              case 0:
-                throw new _HttpError2.default('Not implemented');
-
-              case 1:
-              case 'end':
-                return _context2.stop();
-            }
+    this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              throw new _HttpError2.default('Not implemented');
+
+            case 1:
+            case 'end':
+              return _context2.stop();
           }
-        }, _callee2, _this);
-      }));
-
-      return function (_x2) {
-        return _ref2.apply(this, arguments);
-      };
-    }();
+        }
+      }, _callee2, _this);
+    }));
 
     this._fileManager = new _sparkProtocol.JSONFileManager(path);
   }
@@ -183,7 +177,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
         }, _callee3, this);
       }));
 
-      function create(_x3) {
+      function create(_x2) {
         return _ref3.apply(this, arguments);
       }
 
@@ -207,7 +201,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
         }, _callee4, this);
       }));
 
-      function deleteByID(_x4) {
+      function deleteByID(_x3) {
         return _ref4.apply(this, arguments);
       }
 
@@ -231,7 +225,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
         }, _callee5, this);
       }));
 
-      function getByID(_x5) {
+      function getByID(_x4) {
         return _ref5.apply(this, arguments);
       }
 
diff --git a/dist/settings.js b/dist/settings.js
index 34eb5b0d..dfc1aeb2 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -48,6 +48,10 @@ exports.default = {
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
     PORT: 5683
+  },
+  // Override template parameters in webhooks with this object
+  WEBHOOK_TEMPLATE_PARAMETERS: {
+    // SOME_AUTH_TOKEN: '12312312',
   }
 }; /**
    *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 830d9256..c35e47f6 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -54,11 +54,10 @@ class DeviceManager {
       throw new HttpError('The device is already claimed.');
     }
 
-    const attributesToSave = {
-      ...deviceAttributes,
-      ownerID: userID,
-    };
-    return await this._deviceAttributeRepository.update(attributesToSave);
+    return await this._deviceAttributeRepository.updateByID(
+      deviceID,
+      { ownerID: userID },
+    );
   };
 
   unclaimDevice = async (deviceID: string): Promise => {
@@ -69,11 +68,10 @@ class DeviceManager {
       throw new HttpError('No device found', 404);
     }
 
-    const attributesToSave = {
-      ...deviceAttributes,
-      ownerID: null,
-    };
-    return await this._deviceAttributeRepository.update(attributesToSave);
+    return await this._deviceAttributeRepository.updateByID(
+      deviceID,
+      { ownerID: null },
+    );
   };
 
   getByID = async (deviceID: string): Promise => {
@@ -270,19 +268,22 @@ class DeviceManager {
       throw new HttpError(`Key error ${error}`);
     }
 
-    await this._deviceKeyRepository.update({ deviceID, key: publicKey });
-    const existingAttributes = await this._deviceAttributeRepository.getByID(
+    await this._deviceKeyRepository.updateByID(
       deviceID,
+      {
+        deviceID,
+        key: publicKey,
+      },
     );
-    const attributes = {
-      deviceID,
-      ...existingAttributes,
-      ownerID: userID,
-      registrar: userID,
-      timestamp: new Date(),
-    };
-    await this._deviceAttributeRepository.update(attributes);
 
+    await this._deviceAttributeRepository.updateByID(
+      deviceID,
+      {
+        ownerID: userID,
+        registrar: userID,
+        timestamp: new Date(),
+      },
+    );
     return await this.getByID(deviceID);
   };
 
@@ -322,11 +323,7 @@ class DeviceManager {
       throw new HttpError('No device found', 404);
     }
 
-    const attributesToSave = {
-      ...attributes,
-      name,
-    };
-    return await this._deviceAttributeRepository.update(attributesToSave);
+    return await this._deviceAttributeRepository.updateByID(deviceID, { name });
   }
 }
 
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index f4d04368..bbc80a56 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -36,12 +36,15 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
   getByID = async (deviceID: string): Promise =>
     await this._database.findOne(this._collectionName, { deviceID });
 
-  update = async (model: DeviceAttributes): Promise =>
+  updateByID = async (
+    deviceID: string,
+    props: $Shape,
+  ): Promise =>
     await this._database.findAndModify(
       this._collectionName,
-      { deviceID: model.deviceID },
+      { deviceID },
       null,
-      { $set: { ...model, timeStamp: new Date() } },
+      { $set: { ...props, timeStamp: new Date() } },
       { new: true, upsert: true },
     );
 }
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index ca3e85cc..89f916b2 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -31,12 +31,15 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
       { deviceID },
     );
 
-  update = async (model: DeviceKeyObject): Promise =>
+  updateByID = async (
+    deviceID: string,
+    props: $Shape,
+  ): Promise =>
     await this._database.findAndModify(
       this._collectionName,
-      { deviceID: model.deviceID },
+      { deviceID },
       null,
-      { $set: { ...model } },
+      { $set: { ...props } },
       { new: true, upsert: true },
     );
 }
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index 51909dd2..d977fcb2 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -51,7 +51,7 @@ class UserDatabaseRepository implements IUserRepository {
     );
   };
 
-  deleteAccessToken = async (userID: string, accessToken: string): Promise =>
+  deleteAccessToken = async (userID: string, accessToken: string): Promise =>
     await this._database.findAndModify(
       this._collectionName,
       { _id: userID },
@@ -113,14 +113,14 @@ class UserDatabaseRepository implements IUserRepository {
 
   setCurrentUser = (user: User) => {
     this._currentUser = user;
-  }
+  };
 
-  update = async (model: User): Promise =>
+  updateByID = async (id: string, props: $Shape): Promise =>
     await this._database.findAndModify(
       this._collectionName,
-      { _id: model.id },
+      { _id: id },
       null,
-      { $set: { ...model, timeStamp: new Date() } },
+      { $set: { ...props, timeStamp: new Date() } },
       { new: true, upsert: true },
     );
 
diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js
index 106364e2..27238af0 100644
--- a/src/repository/UserFileRepository.js
+++ b/src/repository/UserFileRepository.js
@@ -58,21 +58,21 @@ class UserFileRepository implements IUserRepository {
     return modelToSave;
   }
 
-  deleteAccessToken = async (userID: string, token: string): Promise<*> => {
+  deleteAccessToken = async (userID: string, token: string): Promise => {
     const user = await this.getByID(userID);
     if (!user) {
       throw new Error('User doesn\'t exist');
     }
 
-    const userToSave = {
-      ...user,
-      accessTokens: user.accessTokens.filter(
-        (tokenObject: TokenObject): boolean =>
-        tokenObject.accessToken !== token,
-      ),
-    };
-
-    await this.update(userToSave);
+    return await this.updateByID(
+      userID,
+      {
+        accessTokens: user.accessTokens.filter(
+          (tokenObject: TokenObject): boolean =>
+            tokenObject.accessToken !== token,
+        ),
+      },
+    );
   };
 
   @memoizeSet(['id'])
@@ -125,22 +125,23 @@ class UserFileRepository implements IUserRepository {
       throw new HttpError('Could not find user for user ID');
     }
 
-    const userToSave = {
-      ...user,
-      accessTokens: [...user.accessTokens, tokenObject],
-    };
-
-    return await this.update(userToSave);
-  }
+    return await this.updateByID(
+      userID,
+      { accessTokens: [...user.accessTokens, tokenObject] },
+    );
+  };
 
   setCurrentUser = (user: User) => {
     this._currentUser = user;
-  }
+  };
 
   @memoizeSet()
-  async update(model: User): Promise {
-    this._fileManager.writeFile(`${model.id}.json`, model);
-    return model;
+  async updateByID(id: string, props: $Shape): Promise {
+    const user = await this.getByID(id);
+    const modelToSave = { ...(user || {}), ...props };
+
+    this._fileManager.writeFile(`${id}.json`, modelToSave);
+    return modelToSave;
   }
 
   validateLogin = async (username: string, password: string): Promise => {
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index 9652eb7a..8bbe2f66 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -34,7 +34,7 @@ class WebhookDatabaseRepository implements IWebhookRepository {
   getByID = async (id: string): Promise =>
     await this._database.findOne(this._collectionName, { _id: id });
 
-  update = async (): Promise => {
+  updateByID = async (): Promise => {
     throw new Error('The method is not implemented');
   };
 }
diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js
index 5c07be00..2778c942 100644
--- a/src/repository/WebhookFileRepository.js
+++ b/src/repository/WebhookFileRepository.js
@@ -14,7 +14,7 @@ class WebhookFileRepository implements IWebhookRepository {
   }
 
   @memoizeSet()
-  async create(model: $Shape): Promise {
+  async create(model: WebhookMutator): Promise {
     let id = uuid();
     while (await this._fileManager.hasFile(`${id}.json`)) {
       id = uuid();
@@ -55,7 +55,7 @@ class WebhookFileRepository implements IWebhookRepository {
   }
 
   // eslint-disable-next-line no-unused-vars
-  update = async (model: WebhookMutator): Promise => {
+  updateByID = async (): Promise => {
     throw new HttpError('Not implemented');
   };
 
diff --git a/src/types.js b/src/types.js
index 9a1380c0..a8958a33 100644
--- a/src/types.js
+++ b/src/types.js
@@ -198,7 +198,7 @@ export interface IBaseRepository {
   deleteByID(id: string): Promise;
   getAll(): Promise>;
   getByID(id: string): Promise;
-  update(model: TModel): Promise;
+  updateByID(id: string, props: $Shape): Promise;
 }
 
 export interface IWebhookRepository extends IBaseRepository {}
@@ -209,7 +209,7 @@ export interface IDeviceKeyRepository extends IBaseRepository {
 
 export interface IUserRepository extends IBaseRepository {
   createWithCredentials(credentials: UserCredentials): Promise;
-  deleteAccessToken(userID: string, accessToken: string): Promise;
+  deleteAccessToken(userID: string, accessToken: string): Promise;
   getByAccessToken(accessToken: string): Promise;
   getByUsername(username: string): Promise;
   getCurrentUser(): User;
diff --git a/test/UserFileRepository.test.js b/test/UserFileRepository.test.js
index c012010b..486f34fc 100644
--- a/test/UserFileRepository.test.js
+++ b/test/UserFileRepository.test.js
@@ -37,7 +37,7 @@ test(
 
     await testAllAccessors();
 
-    await repository.update(user);
+    await repository.updateByID(user.id, user);
     await testAllAccessors();
 
     await repository.deleteByID(user.id);

From 2763f24d858c7abd6a5edbb9127d85537c0233bd Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 14 Jun 2017 16:16:49 +0200
Subject: [PATCH 416/504] use getAttributes for devices from device-server.

---
 dist/managers/PermissionManager.js       |   4 +-
 package-lock.json                        | 432 ++++++++++++++++-------
 src/controllers/DevicesController.js     |   2 +-
 src/lib/deviceToAPI.js                   |   4 +-
 src/managers/DeviceManager.js            |  97 +++--
 src/managers/PermissionManager.js        |   6 +-
 src/repository/UserDatabaseRepository.js |   2 +-
 src/types.js                             |  15 +-
 test/DeviceClaimsController.test.js      |  11 +-
 test/DevicesController.test.js           |  25 +-
 test/ProvisioningController.test.js      |  11 +-
 11 files changed, 381 insertions(+), 228 deletions(-)

diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index ff7f260a..d646bed7 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -119,7 +119,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
               return _context3.abrupt('return', null);
 
             case 5:
-              if (_this._doesUserHaveAccess(entity.ownerID)) {
+              if (_this.doesUserHaveAccess(entity.ownerID)) {
                 _context3.next = 7;
                 break;
               }
@@ -181,7 +181,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
     }, _callee4, _this, [[0, 9]]);
   }));
 
-  this._doesUserHaveAccess = function (ownerID) {
+  this.doesUserHaveAccess = function (ownerID) {
     var currentUser = _this._userRepository.getCurrentUser();
     return currentUser.role === 'administrator' || currentUser.id === ownerID;
   };
diff --git a/package-lock.json b/package-lock.json
index 51acec89..95b6d599 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -786,11 +786,6 @@
       "resolved": "https://registry.npmjs.org/binary-version-reader/-/binary-version-reader-0.5.1.tgz",
       "integrity": "sha1-1LC9MGFlJlsPCcjHHiBd0K4ffHs="
     },
-    "bindings": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
-      "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
-    },
     "bluebird": {
       "version": "3.5.0",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
@@ -1046,6 +1041,11 @@
         }
       }
     },
+    "coap-packet": {
+      "version": "0.1.14",
+      "resolved": "https://registry.npmjs.org/coap-packet/-/coap-packet-0.1.14.tgz",
+      "integrity": "sha1-OEgitnBau/MaDLxEnvejpkxPPlI="
+    },
     "code-point-at": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -1073,6 +1073,11 @@
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
       "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
     },
+    "compact-array": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/compact-array/-/compact-array-0.0.1.tgz",
+      "integrity": "sha1-cSO7Rbeizoocco3oaBndOqaTo9U="
+    },
     "component-emitter": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
@@ -1248,6 +1253,11 @@
       "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
       "dev": true
     },
+    "defined": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz",
+      "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4="
+    },
     "del": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
@@ -1823,569 +1833,688 @@
       "dependencies": {
         "abbrev": {
           "version": "1.1.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
+          "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
           "optional": true
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
         },
         "ansi-styles": {
           "version": "2.2.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
           "optional": true
         },
         "aproba": {
           "version": "1.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
+          "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=",
           "optional": true
         },
         "are-we-there-yet": {
           "version": "1.1.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
+          "integrity": "sha1-gORw6VoIR5T+GJkmLFZnxuiN4bM=",
           "optional": true
         },
         "asn1": {
           "version": "0.2.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+          "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
           "optional": true
         },
         "assert-plus": {
           "version": "0.2.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
           "optional": true
         },
         "asynckit": {
           "version": "0.4.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+          "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
           "optional": true
         },
         "aws-sign2": {
           "version": "0.6.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
           "optional": true
         },
         "aws4": {
           "version": "1.6.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+          "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
           "optional": true
         },
         "balanced-match": {
           "version": "0.4.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+          "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
         },
         "bcrypt-pbkdf": {
           "version": "1.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+          "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
           "optional": true
         },
         "block-stream": {
           "version": "0.0.9",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+          "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo="
         },
         "boom": {
           "version": "2.10.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+          "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
         },
         "brace-expansion": {
           "version": "1.1.6",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
+          "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk="
         },
         "buffer-shims": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
+          "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
         },
         "caseless": {
           "version": "0.11.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
+          "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
           "optional": true
         },
         "chalk": {
           "version": "1.1.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
           "optional": true
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
         },
         "combined-stream": {
           "version": "1.0.5",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+          "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
         },
         "commander": {
           "version": "2.9.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+          "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
           "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+          "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
         },
         "core-util-is": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
         },
         "cryptiles": {
           "version": "2.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+          "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
           "optional": true
         },
         "dashdash": {
           "version": "1.14.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+          "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
               "optional": true
             }
           }
         },
         "debug": {
           "version": "2.2.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
           "optional": true
         },
         "deep-extend": {
           "version": "0.4.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
+          "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=",
           "optional": true
         },
         "delayed-stream": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+          "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
         },
         "delegates": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
           "optional": true
         },
         "ecc-jsbn": {
           "version": "0.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+          "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
           "optional": true
         },
         "escape-string-regexp": {
           "version": "1.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
           "optional": true
         },
         "extend": {
           "version": "3.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
+          "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=",
           "optional": true
         },
         "extsprintf": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+          "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
         },
         "forever-agent": {
           "version": "0.6.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+          "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
           "optional": true
         },
         "form-data": {
           "version": "2.1.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz",
+          "integrity": "sha1-icNTQAi5fq2ky7FX1Y9vXfAl6uQ=",
           "optional": true
         },
         "fs.realpath": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
         },
         "fstream": {
           "version": "1.0.10",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz",
+          "integrity": "sha1-YE6Kkv4m/9n2+uMDmdSYThqyKCI="
         },
         "fstream-ignore": {
           "version": "1.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
+          "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
           "optional": true
         },
         "gauge": {
           "version": "2.7.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz",
+          "integrity": "sha1-HCOFX5YvF7OtPQ3HRD8wRULt/gk=",
           "optional": true
         },
         "generate-function": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+          "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
           "optional": true
         },
         "generate-object-property": {
           "version": "1.2.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+          "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
           "optional": true
         },
         "getpass": {
           "version": "0.1.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
+          "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=",
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
               "optional": true
             }
           }
         },
         "glob": {
           "version": "7.1.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
+          "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg="
         },
         "graceful-fs": {
           "version": "4.1.11",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
         },
         "graceful-readlink": {
           "version": "1.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+          "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
           "optional": true
         },
         "har-validator": {
           "version": "2.0.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
+          "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
           "optional": true
         },
         "has-ansi": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
           "optional": true
         },
         "has-unicode": {
           "version": "2.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
           "optional": true
         },
         "hawk": {
           "version": "3.1.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+          "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
           "optional": true
         },
         "hoek": {
           "version": "2.16.3",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+          "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
         },
         "http-signature": {
           "version": "1.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
           "optional": true
         },
         "inflight": {
           "version": "1.0.6",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
         },
         "ini": {
           "version": "1.3.4",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+          "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
           "optional": true
         },
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs="
         },
         "is-my-json-valid": {
           "version": "2.15.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz",
+          "integrity": "sha1-k27do8o8IR/ZjzstPgjaQ/eykVs=",
           "optional": true
         },
         "is-property": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+          "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
           "optional": true
         },
         "is-typedarray": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+          "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
           "optional": true
         },
         "isarray": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
         },
         "isstream": {
           "version": "0.1.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
           "optional": true
         },
         "jodid25519": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+          "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
           "optional": true
         },
         "jsbn": {
           "version": "0.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+          "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
           "optional": true
         },
         "json-schema": {
           "version": "0.2.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+          "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
           "optional": true
         },
         "json-stringify-safe": {
           "version": "5.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+          "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
           "optional": true
         },
         "jsonpointer": {
           "version": "4.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+          "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
           "optional": true
         },
         "jsprim": {
           "version": "1.3.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
+          "integrity": "sha1-KnJW9wQSop7jZwqspiWZTE3P8lI=",
           "optional": true
         },
         "mime-db": {
           "version": "1.26.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz",
+          "integrity": "sha1-6v/NDk/Gk1z4E02iRuLmw1MFrf8="
         },
         "mime-types": {
           "version": "2.1.14",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz",
+          "integrity": "sha1-9+99l1g/yvO30oK2+LVnnaselO4="
         },
         "minimatch": {
           "version": "3.0.3",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
+          "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q="
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
         },
         "mkdirp": {
           "version": "0.5.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
         },
         "ms": {
           "version": "0.7.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
           "optional": true
         },
         "node-pre-gyp": {
           "version": "0.6.33",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz",
+          "integrity": "sha1-ZArFUZj2qSWXLgwWxKwmoDTV7Mk=",
           "optional": true
         },
         "nopt": {
           "version": "3.0.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+          "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
           "optional": true
         },
         "npmlog": {
           "version": "4.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz",
+          "integrity": "sha1-0DlQ4OeM4VJ7om0qdZLpNIrD518=",
           "optional": true
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
         },
         "oauth-sign": {
           "version": "0.8.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+          "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
           "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
           "optional": true
         },
         "once": {
           "version": "1.4.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
         },
         "path-is-absolute": {
           "version": "1.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+          "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "pinkie": {
           "version": "2.0.4",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
           "optional": true
         },
         "pinkie-promise": {
           "version": "2.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
           "optional": true
         },
         "process-nextick-args": {
           "version": "1.0.7",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
         },
         "punycode": {
           "version": "1.4.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
           "optional": true
         },
         "qs": {
           "version": "6.3.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz",
+          "integrity": "sha1-kYwLO802Z5dyuvE1say0wWUe150=",
           "optional": true
         },
         "rc": {
           "version": "1.1.7",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
+          "integrity": "sha1-xepWS7B6/5/TpbMukGwdOmWUD+o=",
           "optional": true,
           "dependencies": {
             "minimist": {
               "version": "1.2.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
               "optional": true
             }
           }
         },
         "readable-stream": {
           "version": "2.2.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz",
+          "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=",
           "optional": true
         },
         "request": {
           "version": "2.79.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
+          "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
           "optional": true
         },
         "rimraf": {
           "version": "2.5.4",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz",
+          "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ="
         },
         "semver": {
           "version": "5.3.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
           "optional": true
         },
         "set-blocking": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+          "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
           "optional": true
         },
         "signal-exit": {
           "version": "3.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
           "optional": true
         },
         "sntp": {
           "version": "1.0.9",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+          "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
           "optional": true
         },
         "sshpk": {
           "version": "1.10.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz",
+          "integrity": "sha1-1agEziJpVRVjjnmNviMnPeBwpfo=",
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
               "optional": true
             }
           }
         },
         "string_decoder": {
           "version": "0.10.31",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
         },
         "string-width": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M="
         },
         "stringstream": {
           "version": "0.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+          "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
           "optional": true
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8="
         },
         "strip-json-comments": {
           "version": "2.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
           "optional": true
         },
         "supports-color": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
           "optional": true
         },
         "tar": {
           "version": "2.2.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+          "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE="
         },
         "tar-pack": {
           "version": "3.3.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz",
+          "integrity": "sha1-MJMYFkGPVa/E0hd1r91nIM7kXa4=",
           "optional": true,
           "dependencies": {
             "once": {
               "version": "1.3.3",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+              "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
               "optional": true
             },
             "readable-stream": {
               "version": "2.1.5",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz",
+              "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=",
               "optional": true
             }
           }
         },
         "tough-cookie": {
           "version": "2.3.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+          "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
           "optional": true
         },
         "tunnel-agent": {
           "version": "0.4.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
+          "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
           "optional": true
         },
         "tweetnacl": {
           "version": "0.14.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
           "optional": true
         },
         "uid-number": {
           "version": "0.0.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
+          "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
           "optional": true
         },
         "util-deprecate": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
         },
         "uuid": {
           "version": "3.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
+          "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=",
           "optional": true
         },
         "verror": {
           "version": "1.3.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+          "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
           "optional": true
         },
         "wide-align": {
           "version": "1.1.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz",
+          "integrity": "sha1-QO3egCpx/qHwcNo+YtzaLnrdlq0=",
           "optional": true
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
         },
         "xtend": {
           "version": "4.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
           "optional": true
         }
       }
@@ -3296,7 +3425,8 @@
     "nan": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
-      "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U="
+      "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=",
+      "optional": true
     },
     "natural-compare": {
       "version": "1.4.0",
@@ -3441,6 +3571,11 @@
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
       "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
     },
+    "object-inspect": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-0.4.0.tgz",
+      "integrity": "sha1-9RV8EWwUVbJDsG7pdwM5LFrYn+w="
+    },
     "object.omit": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
@@ -3991,6 +4126,11 @@
       "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
       "dev": true
     },
+    "resumer": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz",
+      "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k="
+    },
     "rimraf": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
@@ -4085,6 +4225,11 @@
       "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
       "dev": true
     },
+    "sigmund": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+      "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
+    },
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -4136,7 +4281,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#de560c1ccfd7672fc88e7ba0fbc400462493d8b1"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#2edeac4f048f6248b96e55534fe29d802e1b7bca"
     },
     "spawn-sync": {
       "version": "1.0.15",
@@ -4327,6 +4472,33 @@
         }
       }
     },
+    "tape": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/tape/-/tape-3.6.1.tgz",
+      "integrity": "sha1-SJPdU+KApfWMDOswwsDrs7zVHh8=",
+      "dependencies": {
+        "deep-equal": {
+          "version": "0.2.2",
+          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz",
+          "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0="
+        },
+        "glob": {
+          "version": "3.2.11",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
+          "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0="
+        },
+        "lru-cache": {
+          "version": "2.7.3",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
+          "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
+        },
+        "minimatch": {
+          "version": "0.3.0",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
+          "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0="
+        }
+      }
+    },
     "text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -4342,8 +4514,7 @@
     "through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
-      "dev": true
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
     },
     "through2": {
       "version": "2.0.3",
@@ -4534,11 +4705,6 @@
       "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
       "dev": true
     },
-    "ursa": {
-      "version": "0.9.4",
-      "resolved": "https://registry.npmjs.org/ursa/-/ursa-0.9.4.tgz",
-      "integrity": "sha1-Ciq/t9xCZ/czsPjy/H8siV1ApBM="
-    },
     "user-home": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index 56767f19..0fedff3d 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -86,7 +86,7 @@ class DevicesController extends Controller {
   @httpVerb('get')
   @route('/v1/devices/:deviceID')
   async getDevice(deviceID: string): Promise<*> {
-    const device = await this._deviceManager.getDetailsByID(deviceID);
+    const device = await this._deviceManager.getByID(deviceID);
     return this.ok(deviceToAPI(device));
   }
 
diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js
index 93add631..fe92e52b 100644
--- a/src/lib/deviceToAPI.js
+++ b/src/lib/deviceToAPI.js
@@ -25,7 +25,7 @@ const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({
   cellular: device.isCellular,
   connected: device.connected,
   current_build_target: device.currentBuildTarget,
-  functions: device.functions,
+  functions: device.functions || null,
   id: device.deviceID,
   imei: device.imei,
   last_app: device.lastFlashedAppName,
@@ -37,7 +37,7 @@ const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({
   product_id: device.particleProductId,
   return_value: result,
   status: 'normal',
-  variables: device.variables,
+  variables: device.variables || null,
 });
 
 export default deviceToAPI;
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index c35e47f6..864faee8 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -40,20 +40,28 @@ class DeviceManager {
     deviceID: string,
     userID: string,
   ): Promise => {
-    const deviceAttributes =
+    // todo check: we may not need to get attributes from db here.
+    const attributes =
       await this._deviceAttributeRepository.getByID(deviceID);
 
-    if (!deviceAttributes) {
+    if (!attributes) {
       throw new HttpError('No device found', 404);
     }
-    if (deviceAttributes.ownerID && deviceAttributes.ownerID !== userID) {
+    if (attributes.ownerID && attributes.ownerID !== userID) {
       throw new HttpError('The device belongs to someone else.');
     }
 
-    if (deviceAttributes.ownerID && deviceAttributes.ownerID === userID) {
+    if (attributes.ownerID && attributes.ownerID === userID) {
       throw new HttpError('The device is already claimed.');
     }
 
+    // update connected device attributes
+    await this._eventPublisher.publishAndListenForResponse({
+      context: { attributes: { ownerID: userID }, deviceID },
+      name: SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES,
+    });
+
+    // todo check: we may not need to update attributes in db here.
     return await this._deviceAttributeRepository.updateByID(
       deviceID,
       { ownerID: userID },
@@ -61,12 +69,13 @@ class DeviceManager {
   };
 
   unclaimDevice = async (deviceID: string): Promise => {
-    const deviceAttributes =
-      await this._permissionManager.getEntityByID('deviceAttributes', deviceID);
+    await this.getByID(deviceID);
 
-    if (!deviceAttributes) {
-      throw new HttpError('No device found', 404);
-    }
+    // update connected device attributes
+    await this._eventPublisher.publishAndListenForResponse({
+      context: { attributes: { ownerID: null }, deviceID },
+      name: SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES,
+    });
 
     return await this._deviceAttributeRepository.updateByID(
       deviceID,
@@ -74,41 +83,26 @@ class DeviceManager {
     );
   };
 
-  getByID = async (deviceID: string): Promise => {
-    const attributes = await this._permissionManager.getEntityByID(
-      'deviceAttributes',
-      deviceID,
-    );
-
-    if (!attributes) {
-      throw new HttpError('No device found', 404);
-    }
-
-    const pingResponse = await this._eventPublisher.publishAndListenForResponse({
-      context: { deviceID },
-      name: SPARK_SERVER_EVENTS.PING_DEVICE,
-    });
-
-    return {
-      ...attributes,
-      connected: pingResponse.connected || false,
-      lastFlashedAppName: null,
-      lastHeard: pingResponse.lastPing || attributes.lastHeard,
-    };
+  getAttributesByID = async (deviceID: string): Promise => {
+    // eslint-disable-next-line no-unused-vars
+    const { connected, ...attributes } = await this.getByID(deviceID);
+    return attributes;
   };
 
-  getDetailsByID = async (deviceID: string): Promise => {
-    const [attributes, description, pingResponse] = await Promise.all([
-      this._permissionManager.getEntityByID('deviceAttributes', deviceID),
-      this._eventPublisher.publishAndListenForResponse({
-        context: { deviceID },
-        name: SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION,
-      }),
-      this._eventPublisher.publishAndListenForResponse({
+  getByID = async (deviceID: string): Promise => {
+    const connectedDeviceAttributes = await this._eventPublisher
+      .publishAndListenForResponse({
         context: { deviceID },
-        name: SPARK_SERVER_EVENTS.PING_DEVICE },
-      ),
-    ]);
+        name: SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES,
+      });
+
+    const attributes = !connectedDeviceAttributes.error &&
+      this._permissionManager.doesUserHaveAccess(connectedDeviceAttributes)
+        ? connectedDeviceAttributes
+        : await this._permissionManager.getEntityByID(
+          'deviceAttributes',
+          deviceID,
+        );
 
     if (!attributes) {
       throw new HttpError('No device found', 404);
@@ -116,11 +110,8 @@ class DeviceManager {
 
     return {
       ...attributes,
-      connected: pingResponse.connected,
-      functions: description.state ? description.state.f : null,
+      connected: !connectedDeviceAttributes.error,
       lastFlashedAppName: null,
-      lastHeard: pingResponse.lastPing || attributes.lastHeard,
-      variables: description.state ? description.state.v : null,
     };
   };
 
@@ -138,7 +129,7 @@ class DeviceManager {
           ...attributes,
           connected: pingResponse.connected || false,
           lastFlashedAppName: null,
-          lastHeard: pingResponse.lastPing || attributes.lastHeard,
+          lastHeard: pingResponse.lastHeard || attributes.lastHeard,
         };
       },
     );
@@ -314,14 +305,14 @@ class DeviceManager {
     deviceID: string,
     name: string,
   ): Promise => {
-    const attributes = await this._permissionManager.getEntityByID(
-      'deviceAttributes',
-      deviceID,
-    );
+    // eslint-disable-next-line no-unused-vars
+    const attributes = await this.getAttributesByID(deviceID);
 
-    if (!attributes) {
-      throw new HttpError('No device found', 404);
-    }
+    // update connected device attributes
+    await this._eventPublisher.publishAndListenForResponse({
+      context: { attributes: { name }, deviceID },
+      name: SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES,
+    });
 
     return await this._deviceAttributeRepository.updateByID(deviceID, { name });
   }
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index d2780aa0..fbe794c5 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -51,9 +51,11 @@ class PermissionManager {
     if (!entity) {
       return null;
     }
-    if (!this._doesUserHaveAccess(entity.ownerID)) {
+
+    if (!this.doesUserHaveAccess(entity)) {
       throw new HttpError('User doesn\'t have access', 403);
     }
+
     return entity;
   };
 
@@ -77,7 +79,7 @@ class PermissionManager {
     }
   };
 
-  _doesUserHaveAccess = (ownerID: ?string): boolean => {
+  doesUserHaveAccess = ({ ownerID }: Object): boolean => {
     const currentUser = this._userRepository.getCurrentUser();
     return currentUser.role === 'administrator' || currentUser.id === ownerID;
   };
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index d977fcb2..4696b0a1 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -120,7 +120,7 @@ class UserDatabaseRepository implements IUserRepository {
       this._collectionName,
       { _id: id },
       null,
-      { $set: { ...props, timeStamp: new Date() } },
+      { $set: { ...props } },
       { new: true, upsert: true },
     );
 
diff --git a/src/types.js b/src/types.js
index a8958a33..1dd845d4 100644
--- a/src/types.js
+++ b/src/types.js
@@ -57,14 +57,21 @@ export type Client = {
   grants: Array,
 };
 
+
+export type Device = DeviceAttributes & {
+  connected: boolean,
+};
+
 export type DeviceAttributes = {
   appHash: ?string,
   currentBuildTarget: string,
   deviceID: string,
+  functions?: ?Array,
   imei?: string,
   ip: string,
   isCellular: boolean,
   last_iccid?: string,
+  lastFlashedAppName: ?string,
   lastHeard: Date,
   name: string,
   ownerID: ?string,
@@ -72,6 +79,7 @@ export type DeviceAttributes = {
   productFirmwareVersion: number,
   registrar: string,
   timestamp: Date,
+  variables?: ?Object,
 };
 
 export type DeviceKeyObject = {
@@ -126,13 +134,6 @@ export type UserRole = 'administrator';
 
 export type ProtectedEntityName = 'deviceAttributes' | 'webhook';
 
-export type Device = DeviceAttributes & {
-  connected: boolean,
-  functions?: ?Array,
-  lastFlashedAppName: ?string,
-  variables?: ?Object,
-};
-
 export type Settings = {
   ACCESS_TOKEN_LIFETIME: number,
   API_TIMEOUT: number,
diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js
index 1244665d..01c989f0 100644
--- a/test/DeviceClaimsController.test.js
+++ b/test/DeviceClaimsController.test.js
@@ -21,18 +21,13 @@ test.before(async () => {
     container.constitute('EventPublisher'),
     'publishAndListenForResponse',
     ({ name }) => {
-      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION) {
-        return {
-          state : {
-            f: null,
-            v: null,
-          },
-        };
+      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
+        return { error: new Error('Could not get device for ID') };
       }
       if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
         return {
           connected: true,
-          lastPing: new Date(),
+          lastHeard: new Date(),
         };
       }
     }
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index acb79b19..a8a3f33e 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -20,6 +20,8 @@ let userToken;
 let connectedDeviceToApiAttributes;
 let disconnectedDeviceToApiAttributes;
 
+const getConnectedAttributes = () => connectedDeviceToApiAttributes;
+
 const TEST_LAST_HEARD = new Date();
 const TEST_DEVICE_FUNTIONS = ['testFunction'];
 const TEST_FUNCTION_ARGUMENT = 'testArgument';
@@ -44,11 +46,11 @@ test.before(async () => {
         return deviceID === CONNECTED_DEVICE_ID
           ? {
               connected: true,
-              lastPing: TEST_LAST_HEARD,
+              lastHeard: TEST_LAST_HEARD,
             }
           : {
               connected: false,
-              lastPing: null,
+              lastHeard: null,
             };
       }
 
@@ -64,12 +66,13 @@ test.before(async () => {
         }
       }
 
-      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION) {
+      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
         return {
-          state: {
-            f: TEST_DEVICE_FUNTIONS,
-            v: TEST_DEVICE_VARIABLES,
-          },
+          deviceID: CONNECTED_DEVICE_ID,
+          functions: TEST_DEVICE_FUNTIONS,
+          lastHeard: TEST_LAST_HEARD,
+          ownerID: testUser.id,
+          variables: TEST_DEVICE_VARIABLES,
         };
       }
 
@@ -211,14 +214,14 @@ test.serial('should return all devices', async t => {
 
 test.serial('should unclaim device', async t => {
   const unclaimResponse = await request(app)
-    .delete(`/v1/devices/${CONNECTED_DEVICE_ID}`)
+    .delete(`/v1/devices/${DISCONNECTED_DEVICE_ID}`)
     .query({ access_token: userToken });
 
   t.is(unclaimResponse.status, 200);
   t.is(unclaimResponse.body.ok, true);
 
   const getDeviceResponse = await request(app)
-    .get(`/v1/devices/${CONNECTED_DEVICE_ID}`)
+    .get(`/v1/devices/${DISCONNECTED_DEVICE_ID}`)
     .query({ access_token: userToken });
 
   t.is(getDeviceResponse.status, 403);
@@ -230,14 +233,14 @@ test.serial('should claim device', async t => {
     .set('Content-Type', 'application/x-www-form-urlencoded')
     .send({
       access_token: userToken,
-      id: CONNECTED_DEVICE_ID,
+      id: DISCONNECTED_DEVICE_ID,
     });
 
   t.is(claimDeviceResponse.status, 200);
   t.is(claimDeviceResponse.body.ok, true);
 
   const getDeviceResponse = await request(app)
-    .get(`/v1/devices/${CONNECTED_DEVICE_ID}`)
+    .get(`/v1/devices/${DISCONNECTED_DEVICE_ID}`)
     .query({ access_token: userToken });
 
   t.is(getDeviceResponse.status, 200);
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index 6b7fefd3..cc048551 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -22,18 +22,13 @@ test.before(async () => {
     container.constitute('EventPublisher'),
     'publishAndListenForResponse',
     ({ name }) => {
-      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION) {
-        return {
-          state : {
-            f: null,
-            v: null,
-          },
-        };
+      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
+        return { error: new Error('Could not get device for ID') };
       }
       if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
         return {
           connected: true,
-          lastPing: new Date(),
+          lastHeard: new Date(),
         };
       }
     }

From 153ed256af126246dd0029083bfaab605ec449cd Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 16 Jun 2017 00:24:05 +0200
Subject: [PATCH 417/504] fix key format. and build some not built files

---
 dist/controllers/DevicesController.js     |   2 +-
 dist/lib/deviceToAPI.js                   |   4 +-
 dist/managers/DeviceManager.js            | 197 ++++++++++------------
 dist/managers/PermissionManager.js        |   6 +-
 dist/repository/UserDatabaseRepository.js |   2 +-
 src/managers/DeviceManager.js             |  10 +-
 test/ProvisioningController.test.js       |   1 +
 test/setup/TestData.js                    |   2 +-
 8 files changed, 105 insertions(+), 119 deletions(-)

diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js
index 952aa551..d939d806 100644
--- a/dist/controllers/DevicesController.js
+++ b/dist/controllers/DevicesController.js
@@ -289,7 +289,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
             switch (_context6.prev = _context6.next) {
               case 0:
                 _context6.next = 2;
-                return this._deviceManager.getDetailsByID(deviceID);
+                return this._deviceManager.getByID(deviceID);
 
               case 2:
                 device = _context6.sent;
diff --git a/dist/lib/deviceToAPI.js b/dist/lib/deviceToAPI.js
index d332c6ab..71582cfc 100644
--- a/dist/lib/deviceToAPI.js
+++ b/dist/lib/deviceToAPI.js
@@ -8,7 +8,7 @@ var deviceToAPI = function deviceToAPI(device, result) {
     cellular: device.isCellular,
     connected: device.connected,
     current_build_target: device.currentBuildTarget,
-    functions: device.functions,
+    functions: device.functions || null,
     id: device.deviceID,
     imei: device.imei,
     last_app: device.lastFlashedAppName,
@@ -20,7 +20,7 @@ var deviceToAPI = function deviceToAPI(device, result) {
     product_id: device.particleProductId,
     return_value: result,
     status: 'normal',
-    variables: device.variables
+    variables: device.variables || null
   };
 };
 
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 0c9983a6..b3ef78d2 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -8,14 +8,14 @@ var _promise = require('babel-runtime/core-js/promise');
 
 var _promise2 = _interopRequireDefault(_promise);
 
-var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
-
-var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
-
 var _extends2 = require('babel-runtime/helpers/extends');
 
 var _extends3 = _interopRequireDefault(_extends2);
 
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -47,7 +47,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.claimDevice = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) {
-      var deviceAttributes;
+      var attributes;
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -56,9 +56,9 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               return _this._deviceAttributeRepository.getByID(deviceID);
 
             case 2:
-              deviceAttributes = _context.sent;
+              attributes = _context.sent;
 
-              if (deviceAttributes) {
+              if (attributes) {
                 _context.next = 5;
                 break;
               }
@@ -66,7 +66,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('No device found', 404);
 
             case 5:
-              if (!(deviceAttributes.ownerID && deviceAttributes.ownerID !== userID)) {
+              if (!(attributes.ownerID && attributes.ownerID !== userID)) {
                 _context.next = 7;
                 break;
               }
@@ -74,7 +74,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('The device belongs to someone else.');
 
             case 7:
-              if (!(deviceAttributes.ownerID && deviceAttributes.ownerID === userID)) {
+              if (!(attributes.ownerID && attributes.ownerID === userID)) {
                 _context.next = 9;
                 break;
               }
@@ -83,12 +83,19 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 9:
               _context.next = 11;
-              return _this._deviceAttributeRepository.updateByID(deviceID, { ownerID: userID });
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { attributes: { ownerID: userID }, deviceID: deviceID },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES
+              });
 
             case 11:
+              _context.next = 13;
+              return _this._deviceAttributeRepository.updateByID(deviceID, { ownerID: userID });
+
+            case 13:
               return _context.abrupt('return', _context.sent);
 
-            case 12:
+            case 14:
             case 'end':
               return _context.stop();
           }
@@ -103,32 +110,28 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.unclaimDevice = function () {
     var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
-      var deviceAttributes;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               _context2.next = 2;
-              return _this._permissionManager.getEntityByID('deviceAttributes', deviceID);
+              return _this.getByID(deviceID);
 
             case 2:
-              deviceAttributes = _context2.sent;
-
-              if (deviceAttributes) {
-                _context2.next = 5;
-                break;
-              }
-
-              throw new _HttpError2.default('No device found', 404);
+              _context2.next = 4;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { attributes: { ownerID: null }, deviceID: deviceID },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES
+              });
 
-            case 5:
-              _context2.next = 7;
+            case 4:
+              _context2.next = 6;
               return _this._deviceAttributeRepository.updateByID(deviceID, { ownerID: null });
 
-            case 7:
+            case 6:
               return _context2.abrupt('return', _context2.sent);
 
-            case 8:
+            case 7:
             case 'end':
               return _context2.stop();
           }
@@ -141,42 +144,24 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     };
   }();
 
-  this.getByID = function () {
+  this.getAttributesByID = function () {
     var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID) {
-      var attributes, pingResponse;
+      var _ref4, connected, attributes;
+
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
             case 0:
               _context3.next = 2;
-              return _this._permissionManager.getEntityByID('deviceAttributes', deviceID);
+              return _this.getByID(deviceID);
 
             case 2:
-              attributes = _context3.sent;
-
-              if (attributes) {
-                _context3.next = 5;
-                break;
-              }
-
-              throw new _HttpError2.default('No device found', 404);
-
-            case 5:
-              _context3.next = 7;
-              return _this._eventPublisher.publishAndListenForResponse({
-                context: { deviceID: deviceID },
-                name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE
-              });
-
-            case 7:
-              pingResponse = _context3.sent;
-              return _context3.abrupt('return', (0, _extends3.default)({}, attributes, {
-                connected: pingResponse.connected || false,
-                lastFlashedAppName: null,
-                lastHeard: pingResponse.lastPing || attributes.lastHeard
-              }));
+              _ref4 = _context3.sent;
+              connected = _ref4.connected;
+              attributes = (0, _objectWithoutProperties3.default)(_ref4, ['connected']);
+              return _context3.abrupt('return', attributes);
 
-            case 9:
+            case 6:
             case 'end':
               return _context3.stop();
           }
@@ -189,46 +174,55 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     };
   }();
 
-  this.getDetailsByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
-      var _ref5, _ref6, attributes, description, pingResponse;
-
+  this.getByID = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+      var connectedDeviceAttributes, attributes;
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
               _context4.next = 2;
-              return _promise2.default.all([_this._permissionManager.getEntityByID('deviceAttributes', deviceID), _this._eventPublisher.publishAndListenForResponse({
-                context: { deviceID: deviceID },
-                name: _sparkProtocol.SPARK_SERVER_EVENTS.GET_DEVICE_DESCRIPTION
-              }), _this._eventPublisher.publishAndListenForResponse({
+              return _this._eventPublisher.publishAndListenForResponse({
                 context: { deviceID: deviceID },
-                name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE })]);
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES
+              });
 
             case 2:
-              _ref5 = _context4.sent;
-              _ref6 = (0, _slicedToArray3.default)(_ref5, 3);
-              attributes = _ref6[0];
-              description = _ref6[1];
-              pingResponse = _ref6[2];
+              connectedDeviceAttributes = _context4.sent;
+
+              if (!(!connectedDeviceAttributes.error && _this._permissionManager.doesUserHaveAccess(connectedDeviceAttributes))) {
+                _context4.next = 7;
+                break;
+              }
+
+              _context4.t0 = connectedDeviceAttributes;
+              _context4.next = 10;
+              break;
+
+            case 7:
+              _context4.next = 9;
+              return _this._permissionManager.getEntityByID('deviceAttributes', deviceID);
+
+            case 9:
+              _context4.t0 = _context4.sent;
+
+            case 10:
+              attributes = _context4.t0;
 
               if (attributes) {
-                _context4.next = 9;
+                _context4.next = 13;
                 break;
               }
 
               throw new _HttpError2.default('No device found', 404);
 
-            case 9:
+            case 13:
               return _context4.abrupt('return', (0, _extends3.default)({}, attributes, {
-                connected: pingResponse.connected,
-                functions: description.state ? description.state.f : null,
-                lastFlashedAppName: null,
-                lastHeard: pingResponse.lastPing || attributes.lastHeard,
-                variables: description.state ? description.state.v : null
+                connected: !connectedDeviceAttributes.error,
+                lastFlashedAppName: null
               }));
 
-            case 10:
+            case 14:
             case 'end':
               return _context4.stop();
           }
@@ -237,7 +231,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x5) {
-      return _ref4.apply(this, arguments);
+      return _ref5.apply(this, arguments);
     };
   }();
 
@@ -253,7 +247,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
           case 2:
             devicesAttributes = _context6.sent;
             devicePromises = devicesAttributes.map(function () {
-              var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) {
+              var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) {
                 var pingResponse;
                 return _regenerator2.default.wrap(function _callee5$(_context5) {
                   while (1) {
@@ -270,7 +264,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
                         return _context5.abrupt('return', (0, _extends3.default)({}, attributes, {
                           connected: pingResponse.connected || false,
                           lastFlashedAppName: null,
-                          lastHeard: pingResponse.lastPing || attributes.lastHeard
+                          lastHeard: pingResponse.lastHeard || attributes.lastHeard
                         }));
 
                       case 4:
@@ -282,7 +276,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               }));
 
               return function (_x6) {
-                return _ref8.apply(this, arguments);
+                return _ref7.apply(this, arguments);
               };
             }());
             return _context6.abrupt('return', _promise2.default.all(devicePromises));
@@ -296,7 +290,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }));
 
   this.callFunction = function () {
-    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, functionName, functionArguments) {
+    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, functionName, functionArguments) {
       var callFunctionResponse, error;
       return _regenerator2.default.wrap(function _callee7$(_context7) {
         while (1) {
@@ -335,12 +329,12 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x7, _x8, _x9) {
-      return _ref9.apply(this, arguments);
+      return _ref8.apply(this, arguments);
     };
   }();
 
   this.getVariableValue = function () {
-    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, variableName) {
+    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, variableName) {
       var getVariableResponse, error, result;
       return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
@@ -379,12 +373,12 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x10, _x11) {
-      return _ref10.apply(this, arguments);
+      return _ref9.apply(this, arguments);
     };
   }();
 
   this.flashBinary = function () {
-    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) {
+    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) {
       var flashResponse, error;
       return _regenerator2.default.wrap(function _callee9$(_context9) {
         while (1) {
@@ -423,12 +417,12 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x12, _x13) {
-      return _ref11.apply(this, arguments);
+      return _ref10.apply(this, arguments);
     };
   }();
 
   this.flashKnownApp = function () {
-    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, appName) {
+    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, appName) {
       var knownFirmware, flashResponse, error;
       return _regenerator2.default.wrap(function _callee10$(_context10) {
         while (1) {
@@ -477,12 +471,12 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x14, _x15) {
-      return _ref12.apply(this, arguments);
+      return _ref11.apply(this, arguments);
     };
   }();
 
   this.provision = function () {
-    var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey, algorithm) {
+    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey, algorithm) {
       var createdKey;
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
@@ -497,10 +491,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 2:
               _context11.prev = 2;
-              createdKey = new _nodeRsa2.default(publicKey, 'pkcs1-public-pem', {
-                encryptionScheme: 'pkcs1',
-                signingScheme: 'pkcs1'
-              });
+              createdKey = new _nodeRsa2.default(publicKey);
 
               if (createdKey.isPublic()) {
                 _context11.next = 6;
@@ -549,12 +540,12 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x16, _x17, _x18, _x19) {
-      return _ref13.apply(this, arguments);
+      return _ref12.apply(this, arguments);
     };
   }();
 
   this.raiseYourHand = function () {
-    var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, shouldShowSignal) {
+    var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, shouldShowSignal) {
       var raiseYourHandResponse, error;
       return _regenerator2.default.wrap(function _callee12$(_context12) {
         while (1) {
@@ -593,29 +584,27 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x20, _x21) {
-      return _ref14.apply(this, arguments);
+      return _ref13.apply(this, arguments);
     };
   }();
 
   this.renameDevice = function () {
-    var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, name) {
+    var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, name) {
       var attributes;
       return _regenerator2.default.wrap(function _callee13$(_context13) {
         while (1) {
           switch (_context13.prev = _context13.next) {
             case 0:
               _context13.next = 2;
-              return _this._permissionManager.getEntityByID('deviceAttributes', deviceID);
+              return _this.getAttributesByID(deviceID);
 
             case 2:
               attributes = _context13.sent;
-
-              if (attributes) {
-                _context13.next = 5;
-                break;
-              }
-
-              throw new _HttpError2.default('No device found', 404);
+              _context13.next = 5;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { attributes: { name: name }, deviceID: deviceID },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES
+              });
 
             case 5:
               _context13.next = 7;
@@ -633,7 +622,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     }));
 
     return function (_x22, _x23) {
-      return _ref15.apply(this, arguments);
+      return _ref14.apply(this, arguments);
     };
   }();
 
diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index d646bed7..5e5e7f7a 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -119,7 +119,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
               return _context3.abrupt('return', null);
 
             case 5:
-              if (_this.doesUserHaveAccess(entity.ownerID)) {
+              if (_this.doesUserHaveAccess(entity)) {
                 _context3.next = 7;
                 break;
               }
@@ -181,7 +181,9 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
     }, _callee4, _this, [[0, 9]]);
   }));
 
-  this.doesUserHaveAccess = function (ownerID) {
+  this.doesUserHaveAccess = function (_ref5) {
+    var ownerID = _ref5.ownerID;
+
     var currentUser = _this._userRepository.getCurrentUser();
     return currentUser.role === 'administrator' || currentUser.id === ownerID;
   };
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 1af2423a..fb83cef3 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -323,7 +323,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context11.prev = _context11.next) {
             case 0:
               _context11.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: id }, null, { $set: (0, _extends3.default)({}, props, { timeStamp: new Date() }) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: id }, null, { $set: (0, _extends3.default)({}, props) }, { new: true, upsert: true });
 
             case 2:
               return _context11.abrupt('return', _context11.sent);
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 864faee8..f0742526 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -244,14 +244,8 @@ class DeviceManager {
     }
 
     try {
-      const createdKey = new NodeRSA(
-        publicKey,
-        'pkcs1-public-pem',
-        {
-          encryptionScheme: 'pkcs1',
-          signingScheme: 'pkcs1',
-        },
-      );
+      const createdKey = new NodeRSA(publicKey);
+
       if (!createdKey.isPublic()) {
         throw new HttpError('Not a public key');
       }
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index cc048551..11f0c12f 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -64,6 +64,7 @@ test('provision and add keys for a device.', async t => {
     .post(`/v1/provisioning/${DEVICE_ID}`)
     .query({ access_token: userToken })
     .send({ publicKey: TEST_PUBLIC_KEY });
+  console.log(response.body);
 
   t.is(response.status, 200);
   t.is(response.body.id, DEVICE_ID);
diff --git a/test/setup/TestData.js b/test/setup/TestData.js
index dc4ce451..59758cb9 100644
--- a/test/setup/TestData.js
+++ b/test/setup/TestData.js
@@ -74,7 +74,7 @@ class TestData {
   static getPublicKey = (): string => {
     const key = new NodeRSA({ b: 1024 });
 
-    return key.exportKey('pkcs1-public-pem');
+    return key.exportKey('pkcs8-public-pem');
   };
 }
 

From 1cd8009e905bcb5020f3657f6a5ceaf8cab407ea Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 17 Jun 2017 09:51:10 -0700
Subject: [PATCH 418/504] Updated firmware endpoint so you can send tinker from
 the cli

---
 dist/controllers/DevicesController.js | 2 +-
 dist/settings.js                      | 2 ++
 src/controllers/DevicesController.js  | 6 +++++-
 src/managers/DeviceManager.js         | 9 +++++----
 src/settings.js                       | 2 ++
 5 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js
index d939d806..3af6ae27 100644
--- a/dist/controllers/DevicesController.js
+++ b/dist/controllers/DevicesController.js
@@ -421,7 +421,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
               case 18:
                 file = this.request.files && this.request.files.file[0];
 
-                if (!(file && file.originalname.endsWith('.bin'))) {
+                if (!(file && (file.originalname === 'binary' || file.originalname.endsWith('.bin')))) {
                   _context8.next = 24;
                   break;
                 }
diff --git a/dist/settings.js b/dist/settings.js
index dfc1aeb2..eee5d373 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -45,6 +45,8 @@ exports.default = {
     PATH: _path2.default.join(__dirname, '../data/db'),
     URL: null
   },
+  SHOW_VERBOSE_DEVICE_LOGS: false,
+
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
     PORT: 5683
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index 0fedff3d..8f972cdd 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -168,7 +168,11 @@ class DevicesController extends Controller {
       this.request.files &&
       (this.request.files: any).file[0];
 
-    if (file && file.originalname.endsWith('.bin')) {
+    if (
+      file && (
+        file.originalname === 'binary' || file.originalname.endsWith('.bin')
+      )
+    ) {
       const flashResult = await this._deviceManager
         .flashBinary(deviceID, file);
 
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index f0742526..c07c17ca 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -192,10 +192,11 @@ class DeviceManager {
       deviceID,
     );
 
-    const flashResponse = await this._eventPublisher.publishAndListenForResponse({
-      context: { deviceID, fileBuffer: file.buffer },
-      name: SPARK_SERVER_EVENTS.FLASH_DEVICE,
-    });
+    const flashResponse =
+      await this._eventPublisher.publishAndListenForResponse({
+        context: { deviceID, fileBuffer: file.buffer },
+        name: SPARK_SERVER_EVENTS.FLASH_DEVICE,
+      });
 
     const { error } = flashResponse;
     if (error) {
diff --git a/src/settings.js b/src/settings.js
index fcd69278..56601a6f 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -56,6 +56,8 @@ export default {
     PATH: path.join(__dirname, '../data/db'),
     URL: null,
   },
+  SHOW_VERBOSE_DEVICE_LOGS: false,
+
   TCP_DEVICE_SERVER_CONFIG: {
     HOST: 'localhost',
     PORT: 5683,

From 1994d7a060b2a874a0bc43c1fa0051b76ef2aa5d Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 17 Jun 2017 10:51:21 -0700
Subject: [PATCH 419/504] Updated logger so it has a timestamp and allow
 overrides via constitute

closes #195
---
 dist/defaultBindings.js |  7 +++++
 dist/lib/logger.js      | 63 +++++++++++++++++++++++++----------------
 dist/main.js            |  4 +--
 src/defaultBindings.js  |  4 +++
 src/lib/logger.js       | 35 +++++++++++++++++++----
 src/main.js             |  4 +--
 6 files changed, 83 insertions(+), 34 deletions(-)

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 742531b2..7ac9a940 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -94,6 +94,10 @@ var _WebhookDatabaseRepository = require('./repository/WebhookDatabaseRepository
 
 var _WebhookDatabaseRepository2 = _interopRequireDefault(_WebhookDatabaseRepository);
 
+var _logger = require('./lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
@@ -109,6 +113,9 @@ exports.default = function (container, newSettings) {
   // spark protocol container bindings
   (0, _sparkProtocol.defaultBindings)(container, newSettings);
 
+  container.bindValue('LOGGING_FUNCTION', console.log);
+  _logger2.default.container = container;
+
   // settings
   container.bindValue('DATABASE_PATH', _settings2.default.DB_CONFIG.PATH);
   container.bindValue('DATABASE_OPTIONS', _settings2.default.DB_CONFIG.OPTIONS);
diff --git a/dist/lib/logger.js b/dist/lib/logger.js
index 64e84de7..1de32f3d 100644
--- a/dist/lib/logger.js
+++ b/dist/lib/logger.js
@@ -16,6 +16,8 @@ var _stringify = require('babel-runtime/core-js/json/stringify');
 
 var _stringify2 = _interopRequireDefault(_stringify);
 
+var _constitute = require('constitute');
+
 var _chalk = require('chalk');
 
 var _chalk2 = _interopRequireDefault(_chalk);
@@ -26,26 +28,28 @@ var _settings2 = _interopRequireDefault(_settings);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-/**
-*    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
-*
-*    This program is free software: you can redistribute it and/or modify
-*    it under the terms of the GNU Affero General Public License, version 3,
-*    as published by the Free Software Foundation.
-*
-*    This program is distributed in the hope that it will be useful,
-*    but WITHOUT ANY WARRANTY; without even the implied warranty of
-*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-*    GNU Affero General Public License for more details.
-*
-*    You should have received a copy of the GNU Affero General Public License
-*    along with this program.  If not, see .
-*
-*    You can download the source here: https://github.com/spark/spark-server
-*
-* 
-*
-*/
+function isObject(obj) {
+  return obj === Object(obj);
+} /**
+  *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
+  *
+  *    This program is free software: you can redistribute it and/or modify
+  *    it under the terms of the GNU Affero General Public License, version 3,
+  *    as published by the Free Software Foundation.
+  *
+  *    This program is distributed in the hope that it will be useful,
+  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  *    GNU Affero General Public License for more details.
+  *
+  *    You should have received a copy of the GNU Affero General Public License
+  *    along with this program.  If not, see .
+  *
+  *    You can download the source here: https://github.com/spark/spark-server
+  *
+  * 
+  *
+  */
 
 function _transform() {
   for (var _len = arguments.length, params = Array(_len), _key = 0; _key < _len; _key++) {
@@ -53,7 +57,7 @@ function _transform() {
   }
 
   return params.map(function (param) {
-    if (typeof param === 'string') {
+    if (!isObject(param)) {
       return param;
     }
 
@@ -61,6 +65,10 @@ function _transform() {
   });
 }
 
+function getDate() {
+  return new Date().toISOString();
+}
+
 var Logger = function () {
   function Logger() {
     (0, _classCallCheck3.default)(this, Logger);
@@ -70,23 +78,28 @@ var Logger = function () {
     key: 'log',
     value: function log() {
       if (_settings2.default.SHOW_VERBOSE_DEVICE_LOGS) {
-        console.log(_transform.apply(undefined, arguments));
+        Logger._log('[' + getDate() + ']', _transform.apply(undefined, arguments));
       }
     }
   }, {
     key: 'info',
     value: function info() {
-      console.log(_chalk2.default.cyan(_transform.apply(undefined, arguments)));
+      Logger._log('[' + getDate() + ']', _chalk2.default.cyan(_transform.apply(undefined, arguments)));
     }
   }, {
     key: 'warn',
     value: function warn() {
-      console.warn(_chalk2.default.yellow(_transform.apply(undefined, arguments)));
+      Logger._log('[' + getDate() + ']', _chalk2.default.yellow(_transform.apply(undefined, arguments)));
     }
   }, {
     key: 'error',
     value: function error() {
-      console.error(_chalk2.default.red(_transform.apply(undefined, arguments)));
+      Logger._log('[' + getDate() + ']', _chalk2.default.red(_transform.apply(undefined, arguments)));
+    }
+  }, {
+    key: '_log',
+    value: function _log() {
+      return Logger.container.constitute('LOGGING_FUNCTION').apply(undefined, arguments);
     }
   }]);
   return Logger;
diff --git a/dist/main.js b/dist/main.js
index 09daeb64..4b13a7ac 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -87,7 +87,7 @@ deviceServer.start();
 var app = (0, _app2.default)(container, _settings2.default);
 
 var onServerStartListen = function onServerStartListen() {
-  return console.log('express server started on port ' + NODE_PORT);
+  return _logger2.default.info('express server started on port ' + NODE_PORT);
 };
 
 var _settings$EXPRESS_SER = _settings2.default.EXPRESS_SERVER_CONFIG,
@@ -121,5 +121,5 @@ function (_ref) {
   });
 }));
 addresses.forEach(function (address) {
-  return console.log('Your device server IP address is: ' + address);
+  return _logger2.default.info('Your device server IP address is: ' + address);
 });
\ No newline at end of file
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index d47df741..52ef95e5 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -26,6 +26,7 @@ import DeviceAttributeDatabaseRepository from
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
+import logger from './lib/logger';
 import settings from './settings';
 
 export default (container: Container, newSettings: Settings) => {
@@ -37,6 +38,9 @@ export default (container: Container, newSettings: Settings) => {
   // spark protocol container bindings
   defaultBindings(container, newSettings);
 
+  container.bindValue('LOGGING_FUNCTION', console.log);
+  logger.container = container;
+
   // settings
   container.bindValue('DATABASE_PATH', settings.DB_CONFIG.PATH);
   container.bindValue('DATABASE_OPTIONS', settings.DB_CONFIG.OPTIONS);
diff --git a/src/lib/logger.js b/src/lib/logger.js
index d25d64a5..ff94e9fa 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -19,12 +19,18 @@
 *
 */
 
+import { Container } from 'constitute';
+
 import chalk from 'chalk';
 import settings from '../settings';
 
+function isObject(obj: any): boolean {
+  return obj === Object(obj);
+}
+
 function _transform(...params: Array): Array {
   return params.map((param: any): string => {
-    if (typeof param === 'string') {
+    if (!isObject(param)) {
       return param;
     }
 
@@ -32,23 +38,42 @@ function _transform(...params: Array): Array {
   });
 }
 
+function getDate(): string {
+  return (new Date()).toISOString();
+}
+
 class Logger {
+  static container: Container;
+
   static log(...params: Array) {
     if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
-      console.log(_transform(...params));
+      Logger._log(`[${getDate()}]`, _transform(...params));
     }
   }
 
   static info(...params: Array) {
-    console.log(chalk.cyan(_transform(...params)));
+    Logger._log(
+      `[${getDate()}]`,
+      chalk.cyan(_transform(...params)),
+    );
   }
 
   static warn(...params: Array) {
-    console.warn(chalk.yellow(_transform(...params)));
+    Logger._log(
+      `[${getDate()}]`,
+      chalk.yellow(_transform(...params)),
+    );
   }
 
   static error(...params: Array) {
-    console.error(chalk.red(_transform(...params)));
+    Logger._log(
+      `[${getDate()}]`,
+      chalk.red(_transform(...params)),
+    );
+  }
+
+  static _log(...params: Array): Function {
+    return Logger.container.constitute('LOGGING_FUNCTION')(...params);
   }
 }
 
diff --git a/src/main.js b/src/main.js
index 1ad7f55d..f42c8e77 100644
--- a/src/main.js
+++ b/src/main.js
@@ -42,7 +42,7 @@ deviceServer.start();
 const app = createApp(container, settings);
 
 const onServerStartListen = (): void =>
-  console.log(`express server started on port ${NODE_PORT}`);
+  logger.info(`express server started on port ${NODE_PORT}`);
 
 const {
   SSL_PRIVATE_KEY_FILEPATH: privateKeyFilePath,
@@ -83,5 +83,5 @@ const addresses = arrayFlatten(
   ),
 );
 addresses.forEach((address: string): void =>
-  console.log(`Your device server IP address is: ${address}`),
+  logger.info(`Your device server IP address is: ${address}`),
 );

From 0a92ea0c6294ea21c7db50c71c28f398f4ad41e0 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 17 Jun 2017 15:03:25 -0700
Subject: [PATCH 420/504] Adding electron support

---
 package-lock.json                         | 26 ++++++++++++++++----
 package.json                              |  1 +
 src/controllers/ProvisioningController.js |  4 ++--
 src/managers/DeviceManager.js             | 29 +++++++++++++++--------
 src/scripts/migrateFilesToDatabase.js     |  1 +
 src/types.js                              |  1 +
 6 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 95b6d599..3f5f873b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -154,6 +154,11 @@
       "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
       "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
     },
+    "asn1.js": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-1.0.6.tgz",
+      "integrity": "sha1-9DrnMISV4XvTlfjAP6QA/vck2iQ="
+    },
     "assert-plus": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
@@ -791,6 +796,12 @@
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
       "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw="
     },
+    "bn.js": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-2.2.0.tgz",
+      "integrity": "sha1-EhYrwq5x/EClYmwzQ486h1zTdiU=",
+      "optional": true
+    },
     "body-parser": {
       "version": "1.17.2",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz",
@@ -1350,6 +1361,11 @@
       "integrity": "sha1-RNZW3p2kFWlEZzNTZfsxR7hXK3w=",
       "dev": true
     },
+    "ec-key": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/ec-key/-/ec-key-0.0.2.tgz",
+      "integrity": "sha1-UAUq6WGr1vGHEMI1qH80mnJGgYk="
+    },
     "ecc-jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
@@ -3346,6 +3362,11 @@
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
       "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0="
     },
+    "minimalistic-assert": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
+      "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
+    },
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -4225,11 +4246,6 @@
       "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
       "dev": true
     },
-    "sigmund": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
-      "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
-    },
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
diff --git a/package.json b/package.json
index 1a446956..f10c231d 100644
--- a/package.json
+++ b/package.json
@@ -67,6 +67,7 @@
     "body-parser": "^1.15.2",
     "chalk": "^1.1.3",
     "constitute": "^1.6.2",
+    "ec-key": "0.0.2",
     "express": "^4.14.0",
     "express-oauth-server": "^2.0.0-b3",
     "hogan.js": "^3.0.2",
diff --git a/src/controllers/ProvisioningController.js b/src/controllers/ProvisioningController.js
index fc520444..9effbeee 100644
--- a/src/controllers/ProvisioningController.js
+++ b/src/controllers/ProvisioningController.js
@@ -22,7 +22,7 @@ class ProvisioningController extends Controller {
   async provision(
     coreID: string,
     postBody: {
-      alogrithm: 'ecc' | 'rsa',
+      algorithm: 'ecc' | 'rsa',
       filename: 'cli',
       order: string, // not sure what this is used for
       publicKey: string,
@@ -36,7 +36,7 @@ class ProvisioningController extends Controller {
       coreID,
       this.user.id,
       postBody.publicKey,
-      postBody.alogrithm,
+      postBody.algorithm,
     );
 
     if (!device) {
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index c07c17ca..c08ae4c1 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -11,6 +11,7 @@ import type {
   IDeviceFirmwareRepository,
 } from '../types';
 
+import ECKey from 'ec-key';
 import { SPARK_SERVER_EVENTS } from 'spark-protocol';
 import NodeRSA from 'node-rsa';
 import HttpError from '../lib/HttpError';
@@ -241,22 +242,30 @@ class DeviceManager {
     algorithm: 'ecc' | 'rsa',
   ): Promise<*> => {
     if (algorithm === 'ecc') {
-      return null;
-    }
-
-    try {
-      const createdKey = new NodeRSA(publicKey);
-
-      if (!createdKey.isPublic()) {
-        throw new HttpError('Not a public key');
+      try {
+        const eccKey = new ECKey(publicKey, 'pem');
+        if (eccKey.isPrivateECKey) {
+          throw new HttpError('Not a public key');
+        }
+      } catch (error) {
+        throw new HttpError(`Key error ${error}`);
+      }
+    } else {
+      try {
+        const createdKey = new NodeRSA(publicKey);
+
+        if (!createdKey.isPublic()) {
+          throw new HttpError('Not a public key');
+        }
+      } catch (error) {
+        throw new HttpError(`Key error ${error}`);
       }
-    } catch (error) {
-      throw new HttpError(`Key error ${error}`);
     }
 
     await this._deviceKeyRepository.updateByID(
       deviceID,
       {
+        algorithm,
         deviceID,
         key: publicKey,
       },
diff --git a/src/scripts/migrateFilesToDatabase.js b/src/scripts/migrateFilesToDatabase.js
index 946b520a..1a8e3bdc 100644
--- a/src/scripts/migrateFilesToDatabase.js
+++ b/src/scripts/migrateFilesToDatabase.js
@@ -112,6 +112,7 @@ const insertUsers = async (
 
     await Promise.all(getFiles(settings.DEVICE_DIRECTORY, '.pub.pem')
       .map(({ fileName, fileBuffer }: FileObject): DeviceKeyObject => ({
+        algorithm: 'rsa',
         deviceID: fileName.substring(0, fileName.indexOf('.pub.pem')),
         key: fileBuffer.toString(),
       }))
diff --git a/src/types.js b/src/types.js
index 1dd845d4..89e97f9e 100644
--- a/src/types.js
+++ b/src/types.js
@@ -83,6 +83,7 @@ export type DeviceAttributes = {
 };
 
 export type DeviceKeyObject = {
+  algorithm: 'ecc' | 'rsa',
   deviceID: string,
   key: string,
 };

From 35a066e8f576f612894f429cd70614336742d5ef Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 17 Jun 2017 15:11:54 -0700
Subject: [PATCH 421/504] Updating for ecc

---
 dist/controllers/ProvisioningController.js |  2 +-
 dist/managers/DeviceManager.js             | 68 +++++++++++++++-------
 dist/scripts/migrateFilesToDatabase.js     |  1 +
 3 files changed, 49 insertions(+), 22 deletions(-)

diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js
index 4c2fa828..e5f7665b 100644
--- a/dist/controllers/ProvisioningController.js
+++ b/dist/controllers/ProvisioningController.js
@@ -119,7 +119,7 @@ var ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
 
               case 2:
                 _context.next = 4;
-                return this._deviceManager.provision(coreID, this.user.id, postBody.publicKey, postBody.alogrithm);
+                return this._deviceManager.provision(coreID, this.user.id, postBody.publicKey, postBody.algorithm);
 
               case 4:
                 device = _context.sent;
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index b3ef78d2..f9cc3dcb 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -28,6 +28,10 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _ecKey = require('ec-key');
+
+var _ecKey2 = _interopRequireDefault(_ecKey);
+
 var _sparkProtocol = require('spark-protocol');
 
 var _nodeRsa = require('node-rsa');
@@ -477,66 +481,88 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
   this.provision = function () {
     var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey, algorithm) {
-      var createdKey;
+      var eccKey, createdKey;
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
             case 0:
               if (!(algorithm === 'ecc')) {
-                _context11.next = 2;
+                _context11.next = 12;
                 break;
               }
 
-              return _context11.abrupt('return', null);
+              _context11.prev = 1;
+              eccKey = new _ecKey2.default(publicKey, 'pem');
 
-            case 2:
-              _context11.prev = 2;
+              if (!eccKey.isPrivateECKey) {
+                _context11.next = 5;
+                break;
+              }
+
+              throw new _HttpError2.default('Not a public key');
+
+            case 5:
+              _context11.next = 10;
+              break;
+
+            case 7:
+              _context11.prev = 7;
+              _context11.t0 = _context11['catch'](1);
+              throw new _HttpError2.default('Key error ' + _context11.t0);
+
+            case 10:
+              _context11.next = 21;
+              break;
+
+            case 12:
+              _context11.prev = 12;
               createdKey = new _nodeRsa2.default(publicKey);
 
               if (createdKey.isPublic()) {
-                _context11.next = 6;
+                _context11.next = 16;
                 break;
               }
 
               throw new _HttpError2.default('Not a public key');
 
-            case 6:
-              _context11.next = 11;
+            case 16:
+              _context11.next = 21;
               break;
 
-            case 8:
-              _context11.prev = 8;
-              _context11.t0 = _context11['catch'](2);
-              throw new _HttpError2.default('Key error ' + _context11.t0);
+            case 18:
+              _context11.prev = 18;
+              _context11.t1 = _context11['catch'](12);
+              throw new _HttpError2.default('Key error ' + _context11.t1);
 
-            case 11:
-              _context11.next = 13;
+            case 21:
+              _context11.next = 23;
               return _this._deviceKeyRepository.updateByID(deviceID, {
+                algorithm: algorithm,
                 deviceID: deviceID,
                 key: publicKey
               });
 
-            case 13:
-              _context11.next = 15;
+            case 23:
+              _context11.next = 25;
               return _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: userID,
                 registrar: userID,
                 timestamp: new Date()
               });
 
-            case 15:
-              _context11.next = 17;
+            case 25:
+              _context11.next = 27;
               return _this.getByID(deviceID);
 
-            case 17:
+            case 27:
               return _context11.abrupt('return', _context11.sent);
 
-            case 18:
+            case 28:
             case 'end':
               return _context11.stop();
           }
         }
-      }, _callee11, _this, [[2, 8]]);
+      }, _callee11, _this, [[1, 7], [12, 18]]);
     }));
 
     return function (_x16, _x17, _x18, _x19) {
diff --git a/dist/scripts/migrateFilesToDatabase.js b/dist/scripts/migrateFilesToDatabase.js
index 9d5f29f5..8f9f4f75 100644
--- a/dist/scripts/migrateFilesToDatabase.js
+++ b/dist/scripts/migrateFilesToDatabase.js
@@ -251,6 +251,7 @@ var insertUsers = function () {
             var fileName = _ref10.fileName,
                 fileBuffer = _ref10.fileBuffer;
             return {
+              algorithm: 'rsa',
               deviceID: fileName.substring(0, fileName.indexOf('.pub.pem')),
               key: fileBuffer.toString()
             };

From 3f81396af53d33cd85589d01223e416956189f1a Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 17 Jun 2017 16:16:03 -0700
Subject: [PATCH 422/504] Added electron support. closes #191

---
 README.md | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/README.md b/README.md
index 6ba79168..bd563628 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,18 @@ You'll need to run `particle config particle`, `particle keys server cloud_publi
 
 At this point you should be able to run normal cloud commands and flash binaries.  You can add any webhooks you need, call functions, or get variable values.
 
+Electron Support
+==============================================
+Yes, this supports Electron but only over TCP. TCP will drastically increase
+the amount of data used so watch out.
+
+In order to set up your Electron to work on the server you need to run the
+following while the device is in DFU-mode:
+```
+particle keys server default_key.pub.pem IP_ADDRESS 5683 --protocol tcp
+// Sometimes you need to run the next line as well
+particle keys protocol tcp
+```
 
 What's different to the original spark-server?
 ==============================================

From 933dc880d84461613d3aecc86883d2a9a81d6d91 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 18 Jun 2017 04:54:27 +0200
Subject: [PATCH 423/504] implement NeDb

---
 dist/defaultBindings.js                       |   6 +-
 dist/lib/promisify.js                         |  15 +-
 dist/repository/BaseMongoDb.js                | 296 +-----------------
 .../DeviceAttributeDatabaseRepository.js      |  10 +-
 .../repository/DeviceKeyDatabaseRepository.js |   8 +-
 dist/repository/MongoDb.js                    | 264 +++++++++++++++-
 dist/repository/TingoDb.js                    |  42 +--
 dist/repository/UserDatabaseRepository.js     |  12 +-
 dist/repository/WebhookDatabaseRepository.js  |   8 +-
 package-lock.json                             | 184 +++++++++++
 package.json                                  |   2 +
 src/defaultBindings.js                        |   6 +-
 src/lib/promisify.js                          |   9 +-
 src/repository/BaseMongoDb.js                 |  81 +----
 .../DeviceAttributeDatabaseRepository.js      |   8 +-
 src/repository/DeviceKeyDatabaseRepository.js |  13 +-
 src/repository/MongoDb.js                     |  69 +++-
 src/repository/NeDb.js                        | 107 +++++++
 src/repository/TingoDb.js                     |  27 +-
 src/repository/UserDatabaseRepository.js      |  10 +-
 src/repository/WebhookDatabaseRepository.js   |   6 +-
 src/repository/collectionNames.js             |  16 +
 22 files changed, 743 insertions(+), 456 deletions(-)
 create mode 100644 src/repository/NeDb.js
 create mode 100644 src/repository/collectionNames.js

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 7ac9a940..c82f142c 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -74,9 +74,9 @@ var _DeviceFirmwareFileRepository = require('./repository/DeviceFirmwareFileRepo
 
 var _DeviceFirmwareFileRepository2 = _interopRequireDefault(_DeviceFirmwareFileRepository);
 
-var _TingoDb = require('./repository/TingoDb');
+var _NeDb = require('./repository/NeDb');
 
-var _TingoDb2 = _interopRequireDefault(_TingoDb);
+var _NeDb2 = _interopRequireDefault(_NeDb);
 
 var _DeviceAttributeDatabaseRepository = require('./repository/DeviceAttributeDatabaseRepository');
 
@@ -137,7 +137,7 @@ exports.default = function (container, newSettings) {
 
   container.bindClass('OAuthServer', _expressOauthServer2.default, ['OAUTH_SETTINGS']);
 
-  container.bindClass('Database', _TingoDb2.default, ['DATABASE_PATH', 'DATABASE_OPTIONS']);
+  container.bindClass('Database', _NeDb2.default, ['DATABASE_PATH']);
 
   // lib
   container.bindClass('WebhookLogger', _WebhookLogger2.default, []);
diff --git a/dist/lib/promisify.js b/dist/lib/promisify.js
index cfbeb3c0..65297394 100644
--- a/dist/lib/promisify.js
+++ b/dist/lib/promisify.js
@@ -5,6 +5,10 @@ Object.defineProperty(exports, "__esModule", {
 });
 exports.promisify = undefined;
 
+var _toConsumableArray2 = require("babel-runtime/helpers/toConsumableArray");
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
 var _promise = require("babel-runtime/core-js/promise");
 
 var _promise2 = _interopRequireDefault(_promise);
@@ -17,12 +21,17 @@ var promisify = exports.promisify = function promisify(object, fnName) {
   }
 
   return new _promise2.default(function (resolve, reject) {
-    return object[fnName].apply(object, args.concat([function (error, result) {
+    return object[fnName].apply(object, args.concat([function (error) {
+      for (var _len2 = arguments.length, callbackArgs = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+        callbackArgs[_key2 - 1] = arguments[_key2];
+      }
+
       if (error) {
         reject(error);
-        return;
+        return null;
       }
-      resolve(result);
+
+      return callbackArgs.length <= 1 ? resolve.apply(undefined, (0, _toConsumableArray3.default)(callbackArgs)) : resolve(callbackArgs);
     }]));
   });
 };
\ No newline at end of file
diff --git a/dist/repository/BaseMongoDb.js b/dist/repository/BaseMongoDb.js
index b2425bdd..10589a90 100644
--- a/dist/repository/BaseMongoDb.js
+++ b/dist/repository/BaseMongoDb.js
@@ -12,14 +12,6 @@ var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProp
 
 var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
 
-var _regenerator = require('babel-runtime/regenerator');
-
-var _regenerator2 = _interopRequireDefault(_regenerator);
-
-var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
-
-var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
-
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
@@ -50,293 +42,12 @@ var BaseMongoDb = function BaseMongoDb() {
 
   (0, _classCallCheck3.default)(this, BaseMongoDb);
 
-  this.insertOne = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this.__runForCollection(collectionName, function () {
-                var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
-                  var insertResult;
-                  return _regenerator2.default.wrap(function _callee$(_context) {
-                    while (1) {
-                      switch (_context.prev = _context.next) {
-                        case 0:
-                          _context.next = 2;
-                          return collection.insertOne(entity);
-
-                        case 2:
-                          insertResult = _context.sent;
-                          return _context.abrupt('return', _this.__translateResultItem(insertResult.ops[0]));
-
-                        case 4:
-                        case 'end':
-                          return _context.stop();
-                      }
-                    }
-                  }, _callee, _this);
-                }));
-
-                return function (_x3) {
-                  return _ref2.apply(this, arguments);
-                };
-              }());
-
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
-
-            case 3:
-            case 'end':
-              return _context2.stop();
-          }
-        }
-      }, _callee2, _this);
-    }));
-
-    return function (_x, _x2) {
-      return _ref.apply(this, arguments);
-    };
-  }();
-
-  this.find = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
-      for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
-        args[_key - 2] = arguments[_key];
-      }
-
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
-        while (1) {
-          switch (_context4.prev = _context4.next) {
-            case 0:
-              _context4.next = 2;
-              return _this.__runForCollection(collectionName, function () {
-                var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
-                  var resultItems;
-                  return _regenerator2.default.wrap(function _callee3$(_context3) {
-                    while (1) {
-                      switch (_context3.prev = _context3.next) {
-                        case 0:
-                          _context3.next = 2;
-                          return collection.find.apply(collection, [_this.__translateQuery(query)].concat(args)).toArray();
-
-                        case 2:
-                          resultItems = _context3.sent;
-                          return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
-
-                        case 4:
-                        case 'end':
-                          return _context3.stop();
-                      }
-                    }
-                  }, _callee3, _this);
-                }));
-
-                return function (_x6) {
-                  return _ref4.apply(this, arguments);
-                };
-              }());
-
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
-
-            case 3:
-            case 'end':
-              return _context4.stop();
-          }
-        }
-      }, _callee4, _this);
-    }));
-
-    return function (_x4, _x5) {
-      return _ref3.apply(this, arguments);
-    };
-  }();
-
-  this.findAndModify = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, sort, updateQuery) {
-      for (var _len2 = arguments.length, args = Array(_len2 > 4 ? _len2 - 4 : 0), _key2 = 4; _key2 < _len2; _key2++) {
-        args[_key2 - 4] = arguments[_key2];
-      }
-
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
-        while (1) {
-          switch (_context6.prev = _context6.next) {
-            case 0:
-              _context6.next = 2;
-              return _this.__runForCollection(collectionName, function () {
-                var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
-                  var modifyResult;
-                  return _regenerator2.default.wrap(function _callee5$(_context5) {
-                    while (1) {
-                      switch (_context5.prev = _context5.next) {
-                        case 0:
-                          _context5.next = 2;
-                          return collection.findAndModify.apply(collection, [_this.__translateQuery(query), sort, _this.__translateQuery(updateQuery)].concat(args));
-
-                        case 2:
-                          modifyResult = _context5.sent;
-                          return _context5.abrupt('return', _this.__translateResultItem(modifyResult.value));
-
-                        case 4:
-                        case 'end':
-                          return _context5.stop();
-                      }
-                    }
-                  }, _callee5, _this);
-                }));
-
-                return function (_x11) {
-                  return _ref6.apply(this, arguments);
-                };
-              }());
-
-            case 2:
-              return _context6.abrupt('return', _context6.sent);
-
-            case 3:
-            case 'end':
-              return _context6.stop();
-          }
-        }
-      }, _callee6, _this);
-    }));
-
-    return function (_x7, _x8, _x9, _x10) {
-      return _ref5.apply(this, arguments);
-    };
-  }();
-
-  this.findOne = function () {
-    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
-      for (var _len3 = arguments.length, args = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
-        args[_key3 - 2] = arguments[_key3];
-      }
-
-      return _regenerator2.default.wrap(function _callee8$(_context8) {
-        while (1) {
-          switch (_context8.prev = _context8.next) {
-            case 0:
-              _context8.next = 2;
-              return _this.__runForCollection(collectionName, function () {
-                var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
-                  var resultItem;
-                  return _regenerator2.default.wrap(function _callee7$(_context7) {
-                    while (1) {
-                      switch (_context7.prev = _context7.next) {
-                        case 0:
-                          _context7.next = 2;
-                          return collection.findOne.apply(collection, [_this.__translateQuery(query)].concat(args));
-
-                        case 2:
-                          resultItem = _context7.sent;
-                          return _context7.abrupt('return', _this.__translateResultItem(resultItem));
-
-                        case 4:
-                        case 'end':
-                          return _context7.stop();
-                      }
-                    }
-                  }, _callee7, _this);
-                }));
-
-                return function (_x14) {
-                  return _ref8.apply(this, arguments);
-                };
-              }());
-
-            case 2:
-              return _context8.abrupt('return', _context8.sent);
-
-            case 3:
-            case 'end':
-              return _context8.stop();
-          }
-        }
-      }, _callee8, _this);
-    }));
-
-    return function (_x12, _x13) {
-      return _ref7.apply(this, arguments);
-    };
-  }();
-
-  this.remove = function () {
-    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
-      return _regenerator2.default.wrap(function _callee10$(_context10) {
-        while (1) {
-          switch (_context10.prev = _context10.next) {
-            case 0:
-              _context10.next = 2;
-              return _this.__runForCollection(collectionName, function () {
-                var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
-                  return _regenerator2.default.wrap(function _callee9$(_context9) {
-                    while (1) {
-                      switch (_context9.prev = _context9.next) {
-                        case 0:
-                          _context9.next = 2;
-                          return collection.remove(_this.__translateQuery(query));
-
-                        case 2:
-                          return _context9.abrupt('return', _context9.sent);
-
-                        case 3:
-                        case 'end':
-                          return _context9.stop();
-                      }
-                    }
-                  }, _callee9, _this);
-                }));
-
-                return function (_x17) {
-                  return _ref10.apply(this, arguments);
-                };
-              }());
-
-            case 2:
-              return _context10.abrupt('return', _context10.sent);
-
-            case 3:
-            case 'end':
-              return _context10.stop();
-          }
-        }
-      }, _callee10, _this);
-    }));
-
-    return function (_x15, _x16) {
-      return _ref9.apply(this, arguments);
-    };
-  }();
-
-  this.__filterID = function (_ref11) {
-    var id = _ref11.id,
-        otherProps = (0, _objectWithoutProperties3.default)(_ref11, ['id']);
+  this.__filterID = function (_ref) {
+    var id = _ref.id,
+        otherProps = (0, _objectWithoutProperties3.default)(_ref, ['id']);
     return (0, _extends3.default)({}, otherProps);
   };
 
-  this.__runForCollection = function () {
-    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
-      return _regenerator2.default.wrap(function _callee11$(_context11) {
-        while (1) {
-          switch (_context11.prev = _context11.next) {
-            case 0:
-              throw new Error('Not implemented ' + callback.toString());
-
-            case 1:
-            case 'end':
-              return _context11.stop();
-          }
-        }
-      }, _callee11, _this);
-    }));
-
-    return function (_x18, _x19) {
-      return _ref12.apply(this, arguments);
-    };
-  }();
-
   this.__translateQuery = function (query) {
     return _this.__filterID(deepToObjectIdCast(query));
   };
@@ -351,7 +62,6 @@ var BaseMongoDb = function BaseMongoDb() {
     return (0, _extends3.default)({}, otherProps, { id: _id.toString() });
   };
 }
-
 // eslint-disable-next-line no-unused-vars
 ;
 
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index faf24723..ffdf9bc1 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -20,6 +20,10 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 // getByID, deleteByID and update uses model.deviceID as ID for querying
@@ -27,7 +31,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
   var _this = this;
 
   (0, _classCallCheck3.default)(this, DeviceAttributeDatabaseRepository);
-  this._collectionName = 'deviceAttributes';
+  this._collectionName = _collectionNames2.default.DEVICE_ATTRIBUTES;
   this.create = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
     return _regenerator2.default.wrap(function _callee$(_context) {
       while (1) {
@@ -78,7 +82,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
             case 0:
               query = userID ? { ownerID: userID } : {};
               _context3.next = 3;
-              return _this._database.find(_this._collectionName, query, { timeout: false });
+              return _this._database.find(_this._collectionName, query);
 
             case 3:
               return _context3.abrupt('return', _context3.sent);
@@ -128,7 +132,7 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, null, { $set: (0, _extends3.default)({}, props, { timeStamp: new Date() }) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, props, { timeStamp: new Date() }) });
 
             case 2:
               return _context5.abrupt('return', _context5.sent);
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index 5d28ee82..a0e7cf2a 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -20,6 +20,10 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 // getByID, deleteByID and update uses model.deviceID as ID for querying
@@ -27,7 +31,7 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
   var _this = this;
 
   (0, _classCallCheck3.default)(this, DeviceKeyDatabaseRepository);
-  this._collectionName = 'deviceKeys';
+  this._collectionName = _collectionNames2.default.DEVICE_KEYS;
 
   this.create = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
@@ -126,7 +130,7 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, null, { $set: (0, _extends3.default)({}, props) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, props) });
 
             case 2:
               return _context5.abrupt('return', _context5.sent);
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index 574274f9..c7e7ac0e 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -44,26 +44,274 @@ var MongoDb = function (_BaseMongoDb) {
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (MongoDb.__proto__ || (0, _getPrototypeOf2.default)(MongoDb)).call(this));
 
+    _this.insertOne = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
+                    var insertResult;
+                    return _regenerator2.default.wrap(function _callee$(_context) {
+                      while (1) {
+                        switch (_context.prev = _context.next) {
+                          case 0:
+                            _context.next = 2;
+                            return collection.insertOne(entity);
+
+                          case 2:
+                            insertResult = _context.sent;
+                            return _context.abrupt('return', _this.__translateResultItem(insertResult.ops[0]));
+
+                          case 4:
+                          case 'end':
+                            return _context.stop();
+                        }
+                      }
+                    }, _callee, _this2);
+                  }));
+
+                  return function (_x3) {
+                    return _ref2.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x, _x2) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.find = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
+                    var resultItems;
+                    return _regenerator2.default.wrap(function _callee3$(_context3) {
+                      while (1) {
+                        switch (_context3.prev = _context3.next) {
+                          case 0:
+                            _context3.next = 2;
+                            return collection.find(_this.__translateQuery(query), { timeout: false }).toArray();
+
+                          case 2:
+                            resultItems = _context3.sent;
+                            return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
+
+                          case 4:
+                          case 'end':
+                            return _context3.stop();
+                        }
+                      }
+                    }, _callee3, _this2);
+                  }));
+
+                  return function (_x6) {
+                    return _ref4.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4, _x5) {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.findAndModify = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, updateQuery) {
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                _context6.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
+                    var modifyResult;
+                    return _regenerator2.default.wrap(function _callee5$(_context5) {
+                      while (1) {
+                        switch (_context5.prev = _context5.next) {
+                          case 0:
+                            _context5.next = 2;
+                            return collection.findAndModify(_this.__translateQuery(query), null, _this.__translateQuery(updateQuery), { new: true, upsert: true });
+
+                          case 2:
+                            modifyResult = _context5.sent;
+                            return _context5.abrupt('return', _this.__translateResultItem(modifyResult.value));
+
+                          case 4:
+                          case 'end':
+                            return _context5.stop();
+                        }
+                      }
+                    }, _callee5, _this2);
+                  }));
+
+                  return function (_x10) {
+                    return _ref6.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context6.abrupt('return', _context6.sent);
+
+              case 3:
+              case 'end':
+                return _context6.stop();
+            }
+          }
+        }, _callee6, _this2);
+      }));
+
+      return function (_x7, _x8, _x9) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
+
+    _this.findOne = function () {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
+          while (1) {
+            switch (_context8.prev = _context8.next) {
+              case 0:
+                _context8.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
+                    var resultItem;
+                    return _regenerator2.default.wrap(function _callee7$(_context7) {
+                      while (1) {
+                        switch (_context7.prev = _context7.next) {
+                          case 0:
+                            _context7.next = 2;
+                            return collection.findOne(_this.__translateQuery(query));
+
+                          case 2:
+                            resultItem = _context7.sent;
+                            return _context7.abrupt('return', _this.__translateResultItem(resultItem));
+
+                          case 4:
+                          case 'end':
+                            return _context7.stop();
+                        }
+                      }
+                    }, _callee7, _this2);
+                  }));
+
+                  return function (_x13) {
+                    return _ref8.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context8.abrupt('return', _context8.sent);
+
+              case 3:
+              case 'end':
+                return _context8.stop();
+            }
+          }
+        }, _callee8, _this2);
+      }));
+
+      return function (_x11, _x12) {
+        return _ref7.apply(this, arguments);
+      };
+    }();
+
+    _this.remove = function () {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
+        return _regenerator2.default.wrap(function _callee10$(_context10) {
+          while (1) {
+            switch (_context10.prev = _context10.next) {
+              case 0:
+                _context10.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
+                    return _regenerator2.default.wrap(function _callee9$(_context9) {
+                      while (1) {
+                        switch (_context9.prev = _context9.next) {
+                          case 0:
+                            _context9.next = 2;
+                            return collection.remove(_this.__translateQuery(query));
+
+                          case 2:
+                            return _context9.abrupt('return', _context9.sent);
+
+                          case 3:
+                          case 'end':
+                            return _context9.stop();
+                        }
+                      }
+                    }, _callee9, _this2);
+                  }));
+
+                  return function (_x16) {
+                    return _ref10.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context10.abrupt('return', _context10.sent);
+
+              case 3:
+              case 'end':
+                return _context10.stop();
+            }
+          }
+        }, _callee10, _this2);
+      }));
+
+      return function (_x14, _x15) {
+        return _ref9.apply(this, arguments);
+      };
+    }();
+
     _this.__runForCollection = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collectionName, callback) {
-        return _regenerator2.default.wrap(function _callee$(_context) {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
+        return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
-            switch (_context.prev = _context.next) {
+            switch (_context11.prev = _context11.next) {
               case 0:
-                return _context.abrupt('return', callback(_this._database.collection(collectionName)).catch(function (error) {
+                return _context11.abrupt('return', callback(_this._database.collection(collectionName)).catch(function (error) {
                   return console.error(error);
                 }));
 
               case 1:
               case 'end':
-                return _context.stop();
+                return _context11.stop();
             }
           }
-        }, _callee, _this2);
+        }, _callee11, _this2);
       }));
 
-      return function (_x, _x2) {
-        return _ref.apply(this, arguments);
+      return function (_x17, _x18) {
+        return _ref11.apply(this, arguments);
       };
     }();
 
diff --git a/dist/repository/TingoDb.js b/dist/repository/TingoDb.js
index e6382aff..3fdac92d 100644
--- a/dist/repository/TingoDb.js
+++ b/dist/repository/TingoDb.js
@@ -109,11 +109,7 @@ var TingoDb = function (_BaseMongoDb) {
     }();
 
     _this.find = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName) {
-        for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
-          args[_key - 1] = arguments[_key];
-        }
-
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -127,7 +123,7 @@ var TingoDb = function (_BaseMongoDb) {
                         switch (_context3.prev = _context3.next) {
                           case 0:
                             _context3.next = 2;
-                            return (0, _promisify.promisify)(collection.find.apply(collection, args), 'toArray');
+                            return (0, _promisify.promisify)(collection.find(query, { timeout: false }), 'toArray');
 
                           case 2:
                             resultItems = _context3.sent;
@@ -141,7 +137,7 @@ var TingoDb = function (_BaseMongoDb) {
                     }, _callee3, _this2);
                   }));
 
-                  return function (_x5) {
+                  return function (_x6) {
                     return _ref4.apply(this, arguments);
                   };
                 }());
@@ -157,17 +153,13 @@ var TingoDb = function (_BaseMongoDb) {
         }, _callee4, _this2);
       }));
 
-      return function (_x4) {
+      return function (_x4, _x5) {
         return _ref3.apply(this, arguments);
       };
     }();
 
     _this.findAndModify = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName) {
-        for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
-          args[_key2 - 1] = arguments[_key2];
-        }
-
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, updateQuery) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
@@ -181,7 +173,7 @@ var TingoDb = function (_BaseMongoDb) {
                         switch (_context5.prev = _context5.next) {
                           case 0:
                             _context5.next = 2;
-                            return _promisify.promisify.apply(undefined, [collection, 'findAndModify'].concat(args));
+                            return (0, _promisify.promisify)(collection, 'findAndModify', query, null, updateQuery, { new: true, upsert: true });
 
                           case 2:
                             modifiedItem = _context5.sent;
@@ -195,7 +187,7 @@ var TingoDb = function (_BaseMongoDb) {
                     }, _callee5, _this2);
                   }));
 
-                  return function (_x7) {
+                  return function (_x10) {
                     return _ref6.apply(this, arguments);
                   };
                 }());
@@ -211,17 +203,13 @@ var TingoDb = function (_BaseMongoDb) {
         }, _callee6, _this2);
       }));
 
-      return function (_x6) {
+      return function (_x7, _x8, _x9) {
         return _ref5.apply(this, arguments);
       };
     }();
 
     _this.findOne = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName) {
-        for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
-          args[_key3 - 1] = arguments[_key3];
-        }
-
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
             switch (_context8.prev = _context8.next) {
@@ -235,7 +223,7 @@ var TingoDb = function (_BaseMongoDb) {
                         switch (_context7.prev = _context7.next) {
                           case 0:
                             _context7.next = 2;
-                            return _promisify.promisify.apply(undefined, [collection, 'findOne'].concat(args));
+                            return (0, _promisify.promisify)(collection, 'findOne', query);
 
                           case 2:
                             resultItem = _context7.sent;
@@ -249,7 +237,7 @@ var TingoDb = function (_BaseMongoDb) {
                     }, _callee7, _this2);
                   }));
 
-                  return function (_x9) {
+                  return function (_x13) {
                     return _ref8.apply(this, arguments);
                   };
                 }());
@@ -265,7 +253,7 @@ var TingoDb = function (_BaseMongoDb) {
         }, _callee8, _this2);
       }));
 
-      return function (_x8) {
+      return function (_x11, _x12) {
         return _ref7.apply(this, arguments);
       };
     }();
@@ -297,7 +285,7 @@ var TingoDb = function (_BaseMongoDb) {
                     }, _callee9, _this2);
                   }));
 
-                  return function (_x12) {
+                  return function (_x16) {
                     return _ref10.apply(this, arguments);
                   };
                 }());
@@ -313,7 +301,7 @@ var TingoDb = function (_BaseMongoDb) {
         }, _callee10, _this2);
       }));
 
-      return function (_x10, _x11) {
+      return function (_x14, _x15) {
         return _ref9.apply(this, arguments);
       };
     }();
@@ -334,7 +322,7 @@ var TingoDb = function (_BaseMongoDb) {
         }, _callee11, _this2);
       }));
 
-      return function (_x13, _x14) {
+      return function (_x17, _x18) {
         return _ref11.apply(this, arguments);
       };
     }();
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index fb83cef3..0eab830a 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -20,6 +20,10 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
 var _PasswordHasher = require('../lib/PasswordHasher');
 
 var _PasswordHasher2 = _interopRequireDefault(_PasswordHasher);
@@ -34,7 +38,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, UserDatabaseRepository);
-  this._collectionName = 'users';
+  this._collectionName = _collectionNames2.default.USERS;
 
   this.create = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(user) {
@@ -114,7 +118,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context3.prev = _context3.next) {
             case 0:
               _context3.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: userID }, null, { $pull: { accessTokens: { accessToken: accessToken } } }, { new: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $pull: { accessTokens: { accessToken: accessToken } } });
 
             case 2:
               return _context3.abrupt('return', _context3.sent);
@@ -294,7 +298,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context10.prev = _context10.next) {
             case 0:
               _context10.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: userID }, null, { $push: { accessTokens: tokenObject } }, { new: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $push: { accessTokens: tokenObject } });
 
             case 2:
               return _context10.abrupt('return', _context10.sent);
@@ -323,7 +327,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context11.prev = _context11.next) {
             case 0:
               _context11.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: id }, null, { $set: (0, _extends3.default)({}, props) }, { new: true, upsert: true });
+              return _this._database.findAndModify(_this._collectionName, { _id: id }, { $set: (0, _extends3.default)({}, props) });
 
             case 2:
               return _context11.abrupt('return', _context11.sent);
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index 01da6aae..9a2d112b 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -20,13 +20,17 @@ var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, WebhookDatabaseRepository);
-  this._collectionName = 'webhooks';
+  this._collectionName = _collectionNames2.default.WEBHOOKS;
 
   this.create = function () {
     var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
@@ -90,7 +94,7 @@ var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
             case 0:
               query = userID ? { ownerID: userID } : {};
               _context3.next = 3;
-              return _this._database.find(_this._collectionName, query, { timeout: false });
+              return _this._database.find(_this._collectionName, query);
 
             case 3:
               return _context3.abrupt('return', _context3.sent);
diff --git a/package-lock.json b/package-lock.json
index 3f5f873b..a6f080e0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -56,6 +56,11 @@
       "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
       "dev": true
     },
+    "amdefine": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
+    },
     "ansi-align": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz",
@@ -169,6 +174,11 @@
       "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
       "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw="
     },
+    "ast-types": {
+      "version": "0.8.15",
+      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz",
+      "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI="
+    },
     "async": {
       "version": "1.5.2",
       "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
@@ -760,6 +770,11 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
       "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
     },
+    "base62": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz",
+      "integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ="
+    },
     "basic-auth": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
@@ -1413,6 +1428,18 @@
       "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
       "dev": true
     },
+    "es3ify": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz",
+      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=",
+      "dependencies": {
+        "esprima-fb": {
+          "version": "3001.1.0-dev-harmony-fb",
+          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
+          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
+        }
+      }
+    },
     "es5-ext": {
       "version": "0.10.23",
       "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.23.tgz",
@@ -1580,6 +1607,11 @@
       "integrity": "sha1-rPQ8qI80tCuj0Pa/DgxQSFog9g0=",
       "dev": true
     },
+    "esmangle-evaluator": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz",
+      "integrity": "sha1-Yg2GbvSGGzMR91dm1SqFcrs8YzY="
+    },
     "espower-location-detector": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz",
@@ -1704,6 +1736,23 @@
       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
       "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
     },
+    "falafel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
+      "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=",
+      "dependencies": {
+        "acorn": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
+          "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        }
+      }
+    },
     "fast-levenshtein": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
@@ -1793,6 +1842,11 @@
       "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
       "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4="
     },
+    "foreach": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
+      "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
+    },
     "forever-agent": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -2727,6 +2781,11 @@
       "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
       "dev": true
     },
+    "immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+    },
     "imurmurhash": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -2761,6 +2820,33 @@
       "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
       "dev": true
     },
+    "inline-process-browser": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz",
+      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        },
+        "through2": {
+          "version": "0.6.5",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
+        }
+      }
+    },
     "inquirer": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
@@ -2789,6 +2875,11 @@
       "integrity": "sha1-OPKZg0uowAwwvpxVThNyaXUv86w=",
       "dev": true
     },
+    "is": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/is/-/is-3.2.1.tgz",
+      "integrity": "sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU="
+    },
     "is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -3077,6 +3168,23 @@
         }
       }
     },
+    "jstransform": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz",
+      "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=",
+      "dependencies": {
+        "esprima-fb": {
+          "version": "3001.1.0-dev-harmony-fb",
+          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
+          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
+        },
+        "source-map": {
+          "version": "0.1.31",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz",
+          "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE="
+        }
+      }
+    },
     "kind-of": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
@@ -3106,6 +3214,11 @@
       "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
       "dev": true
     },
+    "lie": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.0.2.tgz",
+      "integrity": "sha1-/9oh17uibzd8rYZdNkmy/Izjn+o="
+    },
     "load-json-file": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -3117,6 +3230,11 @@
       "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
       "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g="
     },
+    "localforage": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.5.0.tgz",
+      "integrity": "sha1-a5lOGbVmEfqF3zmS3zl6xKtm6BU="
+    },
     "locate-path": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -3455,6 +3573,23 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
+    "nedb-core": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/nedb-core/-/nedb-core-3.0.6.tgz",
+      "integrity": "sha1-4BrQ8iciF/UQkY2YY5MwPotMjTI=",
+      "dependencies": {
+        "async": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
+          "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
+        }
+      }
+    },
     "negotiator": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
@@ -3597,6 +3732,11 @@
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-0.4.0.tgz",
       "integrity": "sha1-9RV8EWwUVbJDsG7pdwM5LFrYn+w="
     },
+    "object-keys": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
+      "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0="
+    },
     "object.omit": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
@@ -4002,6 +4142,18 @@
       "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
       "dev": true
     },
+    "recast": {
+      "version": "0.10.43",
+      "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz",
+      "integrity": "sha1-uV1Q9tYHYaX2JS4V2AZ4FoSRzn8=",
+      "dependencies": {
+        "esprima-fb": {
+          "version": "15001.1001.0-dev-harmony-fb",
+          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz",
+          "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk="
+        }
+      }
+    },
     "rechoir": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
@@ -4692,6 +4844,11 @@
       "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=",
       "dev": true
     },
+    "underscore": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
+      "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
+    },
     "unique-temp-dir": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz",
@@ -4703,6 +4860,33 @@
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
       "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
     },
+    "unreachable-branch-transform": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz",
+      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        },
+        "through2": {
+          "version": "0.6.5",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
+        }
+      }
+    },
     "unzip-response": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz",
diff --git a/package.json b/package.json
index f10c231d..6c07fbc5 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
     "migrate-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
     "prebuild": "npm run build:clean",
+    "start:warn": "babel-node ./src/main.js --trace-warnings",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
     "start:debug": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
     "start:prod": "node ./dist/main.js",
@@ -77,6 +78,7 @@
     "mongodb": "^2.2.26",
     "morgan": "^1.7.0",
     "multer": "^1.2.1",
+    "nedb-core": "^3.0.6",
     "node-rsa": "^0.4.2",
     "nullthrows": "^1.0.0",
     "request": "*",
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 52ef95e5..ec7abc84 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -20,7 +20,7 @@ import WebhookManager from './managers/WebhookManager';
 import EventManager from './managers/EventManager';
 import PermissionManager from './managers/PermissionManager';
 import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository';
-import TingoDb from './repository/TingoDb';
+import NeDb from './repository/NeDb';
 import DeviceAttributeDatabaseRepository from
   './repository/DeviceAttributeDatabaseRepository';
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
@@ -74,8 +74,8 @@ export default (container: Container, newSettings: Settings) => {
 
   container.bindClass(
     'Database',
-    TingoDb,
-    ['DATABASE_PATH', 'DATABASE_OPTIONS'],
+    NeDb,
+    ['DATABASE_PATH'],
   );
 
   // lib
diff --git a/src/lib/promisify.js b/src/lib/promisify.js
index 7189c9e0..b6fe1c45 100644
--- a/src/lib/promisify.js
+++ b/src/lib/promisify.js
@@ -8,11 +8,14 @@ export const promisify = (
   new Promise((
     resolve: (result: any) => void,
     reject: (error: Error) => void,
-  ): void => object[fnName](...args, (error: Error, result: any) => {
+  ): void => object[fnName](...args, (error: Error, ...callbackArgs: any): ?Function => {
     if (error) {
       reject(error);
-      return;
+      return null;
     }
-    resolve(result);
+
+    return callbackArgs.length <= 1
+      ? resolve(...callbackArgs)
+      : resolve(callbackArgs);
   }),
 );
diff --git a/src/repository/BaseMongoDb.js b/src/repository/BaseMongoDb.js
index ab8c7e31..1e09459e 100644
--- a/src/repository/BaseMongoDb.js
+++ b/src/repository/BaseMongoDb.js
@@ -1,7 +1,5 @@
 // @flow
 
-import type { IBaseDatabase } from '../types';
-
 import { ObjectId } from 'mongodb';
 
 const deepToObjectIdCast = (node: any): any => {
@@ -17,87 +15,10 @@ const deepToObjectIdCast = (node: any): any => {
   return node;
 };
 
-class BaseMongoDb implements IBaseDatabase {
-  insertOne = async (
-    collectionName: string,
-    entity: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const insertResult = await collection.insertOne(entity);
-      return this.__translateResultItem(insertResult.ops[0]);
-    },
-  );
-
-  find = async (
-    collectionName: string,
-    query: Object,
-    ...args: Array
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItems = await collection.find(
-        this.__translateQuery(query),
-        ...args,
-      ).toArray();
-
-      return resultItems.map(this.__translateResultItem);
-    },
-  );
-
-  findAndModify = async (
-    collectionName: string,
-    query: Object,
-    sort: ?Array,
-    updateQuery: Object,
-    ...args: Array
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const modifyResult = await collection.findAndModify(
-        this.__translateQuery(query),
-        sort,
-        this.__translateQuery(updateQuery),
-        ...args,
-      );
-      return this.__translateResultItem(modifyResult.value);
-    },
-  );
-
-  findOne = async (
-    collectionName: string,
-    query: Object,
-    ...args: Array
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItem = await collection.findOne(
-        this.__translateQuery(query),
-        ...args,
-      );
-      return this.__translateResultItem(resultItem);
-    },
-  );
-
-  remove = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> =>
-      await collection.remove(this.__translateQuery(query)),
-  );
-
+class BaseMongoDb {
   // eslint-disable-next-line no-unused-vars
   __filterID = ({ id, ...otherProps }: Object): Object => ({ ...otherProps });
 
-  __runForCollection = async (
-    collectionName: string,
-    callback: (collection: Object) => Promise<*>,
-  ): Promise<*> => {
-    throw new Error(`Not implemented ${callback.toString()}`);
-  };
-
   __translateQuery = (query: Object): Object =>
     this.__filterID(deepToObjectIdCast(query));
 
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index bbc80a56..0db7a621 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -1,15 +1,18 @@
 // @flow
 
+import type { CollectionName } from './collectionNames';
 import type {
   DeviceAttributes,
   IBaseDatabase,
   IDeviceAttributeRepository,
 } from '../types';
 
+import COLLECTION_NAMES from './collectionNames';
+
 // getByID, deleteByID and update uses model.deviceID as ID for querying
 class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
   _database: IBaseDatabase;
-  _collectionName: string = 'deviceAttributes';
+  _collectionName: CollectionName = COLLECTION_NAMES.DEVICE_ATTRIBUTES;
   _permissionManager: Object;
 
   constructor(database: IBaseDatabase, permissionManager: Object) {
@@ -29,7 +32,6 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     return await this._database.find(
       this._collectionName,
       query,
-      { timeout: false },
     );
   };
 
@@ -43,9 +45,7 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     await this._database.findAndModify(
       this._collectionName,
       { deviceID },
-      null,
       { $set: { ...props, timeStamp: new Date() } },
-      { new: true, upsert: true },
     );
 }
 export default DeviceAttributeDatabaseRepository;
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index 89f916b2..e044321c 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -1,12 +1,19 @@
 // @flow
 
-import type { DeviceKeyObject, IBaseDatabase, IDeviceKeyRepository } from '../types';
+import type { CollectionName } from './collectionNames';
+import type {
+  DeviceKeyObject,
+  IBaseDatabase,
+  IDeviceKeyRepository,
+} from '../types';
+
+import COLLECTION_NAMES from './collectionNames';
 
 
 // getByID, deleteByID and update uses model.deviceID as ID for querying
 class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
   _database: IBaseDatabase;
-  _collectionName: string = 'deviceKeys';
+  _collectionName: CollectionName = COLLECTION_NAMES.DEVICE_KEYS;
 
   constructor(database: IBaseDatabase) {
     this._database = database;
@@ -38,9 +45,7 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
     await this._database.findAndModify(
       this._collectionName,
       { deviceID },
-      null,
       { $set: { ...props } },
-      { new: true, upsert: true },
     );
 }
 
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index 512d882e..a3b87934 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -1,8 +1,10 @@
 // @flow
 
+import type { IBaseDatabase } from '../types';
+
 import BaseMongoDb from './BaseMongoDb';
 
-class MongoDb extends BaseMongoDb {
+class MongoDb extends BaseMongoDb implements IBaseDatabase {
   _database: Object;
 
   constructor(database: Object) {
@@ -11,6 +13,71 @@ class MongoDb extends BaseMongoDb {
     this._database = database;
   }
 
+  insertOne = async (
+    collectionName: string,
+    entity: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const insertResult = await collection.insertOne(entity);
+      return this.__translateResultItem(insertResult.ops[0]);
+    },
+  );
+
+  find = async (
+    collectionName: string,
+    query: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const resultItems = await collection.find(
+        this.__translateQuery(query),
+        { timeout: false },
+      ).toArray();
+
+      return resultItems.map(this.__translateResultItem);
+    },
+  );
+
+  findAndModify = async (
+    collectionName: string,
+    query: Object,
+    updateQuery: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const modifyResult = await collection.findAndModify(
+        this.__translateQuery(query),
+        null,
+        this.__translateQuery(updateQuery),
+        { new: true, upsert: true },
+      );
+      return this.__translateResultItem(modifyResult.value);
+    },
+  );
+
+  findOne = async (
+    collectionName: string,
+    query: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const resultItem = await collection.findOne(
+        this.__translateQuery(query),
+      );
+      return this.__translateResultItem(resultItem);
+    },
+  );
+
+  remove = async (
+    collectionName: string,
+    query: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> =>
+      await collection.remove(this.__translateQuery(query)),
+  );
+
   __runForCollection = async (
     collectionName: string,
     callback: (collection: Object) => Promise<*>,
diff --git a/src/repository/NeDb.js b/src/repository/NeDb.js
new file mode 100644
index 00000000..d0129ba6
--- /dev/null
+++ b/src/repository/NeDb.js
@@ -0,0 +1,107 @@
+// @flow
+
+import type { IBaseDatabase } from '../types';
+import type { CollectionName } from './collectionNames';
+
+import fs from 'fs';
+import mkdirp from 'mkdirp';
+import Datastore from 'nedb-core';
+import COLLECTION_NAMES from './collectionNames';
+import { promisify } from '../lib/promisify';
+import BaseMongoDb from './BaseMongoDb';
+
+class NeDb extends BaseMongoDb implements IBaseDatabase {
+  _database: Object;
+
+  constructor(path: string) {
+    super();
+
+    if (!fs.existsSync(path)) {
+      mkdirp.sync(path);
+    }
+
+    this._database = {};
+
+    Object.values(COLLECTION_NAMES).forEach((collectionName: mixed) => {
+      const colName: CollectionName = (collectionName: any);
+      this._database[collectionName] = new Datastore({
+        autoload: true,
+        filename: `${path}/${colName}.db`,
+      });
+    });
+  }
+
+  insertOne = async (
+    collectionName: string,
+    entity: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const insertResult = await promisify(
+        collection,
+        'insert',
+        entity,
+      );
+
+      return this.__translateResultItem(insertResult);
+    },
+  );
+
+  find = async (
+    collectionName: string,
+    query: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const resultItems = await promisify(collection, 'find', query);
+      return resultItems.map(this.__translateResultItem);
+    },
+  );
+
+  findAndModify = async (
+    collectionName: string,
+    query: Object,
+    updateQuery: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      // eslint-disable-next-line no-unused-vars
+      const [count, resultItem] = await promisify(
+        collection,
+        'update',
+        query,
+        updateQuery,
+        { returnUpdatedDocs: true, upsert: true },
+      );
+
+      return this.__translateResultItem(resultItem);
+    },
+  );
+
+  findOne = async (
+    collectionName: string,
+    query: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> => {
+      const resultItem = await promisify(collection, 'findOne', query);
+      return this.__translateResultItem(resultItem);
+    },
+  );
+
+  remove = async (
+    collectionName: string,
+    query: Object,
+  ): Promise<*> => await this.__runForCollection(
+    collectionName,
+    async (collection: Object): Promise<*> =>
+      await promisify(collection, 'remove', query),
+  );
+
+  __runForCollection = async (
+    collectionName: string,
+    callback: (collection: Object) => Promise<*>,
+  ): Promise<*> => callback(this._database[collectionName]);
+}
+
+export default NeDb;
diff --git a/src/repository/TingoDb.js b/src/repository/TingoDb.js
index 09725b65..3fe69eb7 100644
--- a/src/repository/TingoDb.js
+++ b/src/repository/TingoDb.js
@@ -1,12 +1,14 @@
 // @flow
 
+import type { IBaseDatabase } from '../types';
+
 import fs from 'fs';
 import mkdirp from 'mkdirp';
 import tingoDb from 'tingodb';
 import { promisify } from '../lib/promisify';
 import BaseMongoDb from './BaseMongoDb';
 
-class TingoDb extends BaseMongoDb {
+class TingoDb extends BaseMongoDb implements IBaseDatabase {
   _database: Object;
 
   constructor(path: string, options: Object) {
@@ -40,33 +42,44 @@ class TingoDb extends BaseMongoDb {
 
   find = async (
     collectionName: string,
-    ...args: Array
+    query: Object,
   ): Promise<*> => await this.__runForCollection(
     collectionName,
     async (collection: Object): Promise<*> => {
-      const resultItems = await promisify(collection.find(...args), 'toArray');
+      const resultItems = await promisify(
+        collection.find(query, { timeout: false }),
+        'toArray',
+      );
       return resultItems.map(this.__translateResultItem);
     },
   );
 
   findAndModify = async (
     collectionName: string,
-    ...args: Array
+    query: Object,
+    updateQuery: Object,
   ): Promise<*> => await this.__runForCollection(
     collectionName,
     async (collection: Object): Promise<*> => {
-      const modifiedItem = await promisify(collection, 'findAndModify', ...args);
+      const modifiedItem = await promisify(
+        collection,
+        'findAndModify',
+        query,
+        null,
+        updateQuery,
+        { new: true, upsert: true },
+      );
       return this.__translateResultItem(modifiedItem);
     },
   );
 
   findOne = async (
     collectionName: string,
-    ...args: Array
+    query: Object,
   ): Promise<*> => await this.__runForCollection(
     collectionName,
     async (collection: Object): Promise<*> => {
-      const resultItem = await promisify(collection, 'findOne', ...args);
+      const resultItem = await promisify(collection, 'findOne', query);
       return this.__translateResultItem(resultItem);
     },
   );
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index 4696b0a1..4bbd3df2 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -1,5 +1,6 @@
 // @flow
 
+import type { CollectionName } from './collectionNames';
 import type {
   IBaseDatabase,
   IUserRepository,
@@ -9,12 +10,13 @@ import type {
   UserRole,
 } from '../types';
 
+import COLLECTION_NAMES from './collectionNames';
 import PasswordHasher from '../lib/PasswordHasher';
 import HttpError from '../lib/HttpError';
 
 class UserDatabaseRepository implements IUserRepository {
   _database: IBaseDatabase;
-  _collectionName: string = 'users';
+  _collectionName: CollectionName = COLLECTION_NAMES.USERS;
   _currentUser: User;
 
   constructor(database: IBaseDatabase) {
@@ -55,9 +57,7 @@ class UserDatabaseRepository implements IUserRepository {
     await this._database.findAndModify(
       this._collectionName,
       { _id: userID },
-      null,
       { $pull: { accessTokens: { accessToken } } },
-      { new: true },
     );
 
   deleteByID = async (id: string): Promise =>
@@ -106,9 +106,7 @@ class UserDatabaseRepository implements IUserRepository {
   ): Promise<*> => await this._database.findAndModify(
     this._collectionName,
     { _id: userID },
-    null,
     { $push: { accessTokens: tokenObject } },
-    { new: true },
   );
 
   setCurrentUser = (user: User) => {
@@ -119,9 +117,7 @@ class UserDatabaseRepository implements IUserRepository {
     await this._database.findAndModify(
       this._collectionName,
       { _id: id },
-      null,
       { $set: { ...props } },
-      { new: true, upsert: true },
     );
 
   validateLogin = async (username: string, password: string): Promise => {
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index 8bbe2f66..789108cb 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -1,10 +1,13 @@
 // @flow
 
+import type { CollectionName } from './collectionNames';
 import type { IBaseDatabase, IWebhookRepository, Webhook } from '../types';
 
+import COLLECTION_NAMES from './collectionNames';
+
 class WebhookDatabaseRepository implements IWebhookRepository {
   _database: IBaseDatabase;
-  _collectionName: string = 'webhooks';
+  _collectionName: CollectionName = COLLECTION_NAMES.WEBHOOKS;
 
   constructor(database: IBaseDatabase) {
     this._database = database;
@@ -27,7 +30,6 @@ class WebhookDatabaseRepository implements IWebhookRepository {
     return await this._database.find(
       this._collectionName,
       query,
-      { timeout: false },
     );
   };
 
diff --git a/src/repository/collectionNames.js b/src/repository/collectionNames.js
new file mode 100644
index 00000000..5cb11faa
--- /dev/null
+++ b/src/repository/collectionNames.js
@@ -0,0 +1,16 @@
+// @flow
+
+export type CollectionName =
+  'deviceAttributes'|
+  'deviceKeys'|
+  'users'|
+  'webhooks';
+
+const COLLECTION_NAMES: { [key: string]: CollectionName } = {
+  DEVICE_ATTRIBUTES: 'deviceAttributes',
+  DEVICE_KEYS: 'deviceKeys',
+  USERS: 'users',
+  WEBHOOKS: 'webhooks',
+};
+
+export default COLLECTION_NAMES;

From f869bb14476d86b119072fdfb947124071017a29 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 17 Jun 2017 22:11:08 -0700
Subject: [PATCH 424/504] Logger fixes + adding prebuilt files

---
 dist/app.js                        |   6 +-
 dist/repository/NeDb.js            | 365 +++++++++++++++++++++++++++++
 dist/repository/collectionNames.js |  15 ++
 dist/settings.js                   |   2 +-
 src/app.js                         |   6 +-
 src/settings.js                    |   2 +-
 6 files changed, 392 insertions(+), 4 deletions(-)
 create mode 100644 dist/repository/NeDb.js
 create mode 100644 dist/repository/collectionNames.js

diff --git a/dist/app.js b/dist/app.js
index ef1a1b66..e4dc388c 100644
--- a/dist/app.js
+++ b/dist/app.js
@@ -12,6 +12,10 @@ var _express = require('express');
 
 var _express2 = _interopRequireDefault(_express);
 
+var _logger = require('./lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 var _morgan = require('morgan');
 
 var _morgan2 = _interopRequireDefault(_morgan);
@@ -40,7 +44,7 @@ exports.default = function (container, settings, existingApp) {
   };
 
   if (settings.LOG_REQUESTS) {
-    app.use((0, _morgan2.default)('combined'));
+    app.use((0, _morgan2.default)('[:date[iso]] :remote-addr - :remote-user ":method :url ' + 'HTTP/:http-version" :status :res[content-length] ":referrer" ' + '":user-agent"'));
   }
 
   app.use(_bodyParser2.default.json());
diff --git a/dist/repository/NeDb.js b/dist/repository/NeDb.js
new file mode 100644
index 00000000..477dd3ff
--- /dev/null
+++ b/dist/repository/NeDb.js
@@ -0,0 +1,365 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _values = require('babel-runtime/core-js/object/values');
+
+var _values2 = _interopRequireDefault(_values);
+
+var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
+
+var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _fs = require('fs');
+
+var _fs2 = _interopRequireDefault(_fs);
+
+var _mkdirp = require('mkdirp');
+
+var _mkdirp2 = _interopRequireDefault(_mkdirp);
+
+var _nedbCore = require('nedb-core');
+
+var _nedbCore2 = _interopRequireDefault(_nedbCore);
+
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
+var _promisify = require('../lib/promisify');
+
+var _BaseMongoDb2 = require('./BaseMongoDb');
+
+var _BaseMongoDb3 = _interopRequireDefault(_BaseMongoDb2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var NeDb = function (_BaseMongoDb) {
+  (0, _inherits3.default)(NeDb, _BaseMongoDb);
+
+  function NeDb(path) {
+    var _this2 = this;
+
+    (0, _classCallCheck3.default)(this, NeDb);
+
+    var _this = (0, _possibleConstructorReturn3.default)(this, (NeDb.__proto__ || (0, _getPrototypeOf2.default)(NeDb)).call(this));
+
+    _this.insertOne = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
+                    var insertResult;
+                    return _regenerator2.default.wrap(function _callee$(_context) {
+                      while (1) {
+                        switch (_context.prev = _context.next) {
+                          case 0:
+                            _context.next = 2;
+                            return (0, _promisify.promisify)(collection, 'insert', entity);
+
+                          case 2:
+                            insertResult = _context.sent;
+                            return _context.abrupt('return', _this.__translateResultItem(insertResult));
+
+                          case 4:
+                          case 'end':
+                            return _context.stop();
+                        }
+                      }
+                    }, _callee, _this2);
+                  }));
+
+                  return function (_x3) {
+                    return _ref2.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x, _x2) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.find = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
+                    var resultItems;
+                    return _regenerator2.default.wrap(function _callee3$(_context3) {
+                      while (1) {
+                        switch (_context3.prev = _context3.next) {
+                          case 0:
+                            _context3.next = 2;
+                            return (0, _promisify.promisify)(collection, 'find', query);
+
+                          case 2:
+                            resultItems = _context3.sent;
+                            return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
+
+                          case 4:
+                          case 'end':
+                            return _context3.stop();
+                        }
+                      }
+                    }, _callee3, _this2);
+                  }));
+
+                  return function (_x6) {
+                    return _ref4.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4, _x5) {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.findAndModify = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, updateQuery) {
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                _context6.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
+                    var _ref7, _ref8, count, resultItem;
+
+                    return _regenerator2.default.wrap(function _callee5$(_context5) {
+                      while (1) {
+                        switch (_context5.prev = _context5.next) {
+                          case 0:
+                            _context5.next = 2;
+                            return (0, _promisify.promisify)(collection, 'update', query, updateQuery, { returnUpdatedDocs: true, upsert: true });
+
+                          case 2:
+                            _ref7 = _context5.sent;
+                            _ref8 = (0, _slicedToArray3.default)(_ref7, 2);
+                            count = _ref8[0];
+                            resultItem = _ref8[1];
+                            return _context5.abrupt('return', _this.__translateResultItem(resultItem));
+
+                          case 7:
+                          case 'end':
+                            return _context5.stop();
+                        }
+                      }
+                    }, _callee5, _this2);
+                  }));
+
+                  return function (_x10) {
+                    return _ref6.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context6.abrupt('return', _context6.sent);
+
+              case 3:
+              case 'end':
+                return _context6.stop();
+            }
+          }
+        }, _callee6, _this2);
+      }));
+
+      return function (_x7, _x8, _x9) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
+
+    _this.findOne = function () {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
+          while (1) {
+            switch (_context8.prev = _context8.next) {
+              case 0:
+                _context8.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
+                    var resultItem;
+                    return _regenerator2.default.wrap(function _callee7$(_context7) {
+                      while (1) {
+                        switch (_context7.prev = _context7.next) {
+                          case 0:
+                            _context7.next = 2;
+                            return (0, _promisify.promisify)(collection, 'findOne', query);
+
+                          case 2:
+                            resultItem = _context7.sent;
+                            return _context7.abrupt('return', _this.__translateResultItem(resultItem));
+
+                          case 4:
+                          case 'end':
+                            return _context7.stop();
+                        }
+                      }
+                    }, _callee7, _this2);
+                  }));
+
+                  return function (_x13) {
+                    return _ref10.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context8.abrupt('return', _context8.sent);
+
+              case 3:
+              case 'end':
+                return _context8.stop();
+            }
+          }
+        }, _callee8, _this2);
+      }));
+
+      return function (_x11, _x12) {
+        return _ref9.apply(this, arguments);
+      };
+    }();
+
+    _this.remove = function () {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
+        return _regenerator2.default.wrap(function _callee10$(_context10) {
+          while (1) {
+            switch (_context10.prev = _context10.next) {
+              case 0:
+                _context10.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
+                    return _regenerator2.default.wrap(function _callee9$(_context9) {
+                      while (1) {
+                        switch (_context9.prev = _context9.next) {
+                          case 0:
+                            _context9.next = 2;
+                            return (0, _promisify.promisify)(collection, 'remove', query);
+
+                          case 2:
+                            return _context9.abrupt('return', _context9.sent);
+
+                          case 3:
+                          case 'end':
+                            return _context9.stop();
+                        }
+                      }
+                    }, _callee9, _this2);
+                  }));
+
+                  return function (_x16) {
+                    return _ref12.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context10.abrupt('return', _context10.sent);
+
+              case 3:
+              case 'end':
+                return _context10.stop();
+            }
+          }
+        }, _callee10, _this2);
+      }));
+
+      return function (_x14, _x15) {
+        return _ref11.apply(this, arguments);
+      };
+    }();
+
+    _this.__runForCollection = function () {
+      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
+        return _regenerator2.default.wrap(function _callee11$(_context11) {
+          while (1) {
+            switch (_context11.prev = _context11.next) {
+              case 0:
+                return _context11.abrupt('return', callback(_this._database[collectionName]));
+
+              case 1:
+              case 'end':
+                return _context11.stop();
+            }
+          }
+        }, _callee11, _this2);
+      }));
+
+      return function (_x17, _x18) {
+        return _ref13.apply(this, arguments);
+      };
+    }();
+
+    if (!_fs2.default.existsSync(path)) {
+      _mkdirp2.default.sync(path);
+    }
+
+    _this._database = {};
+
+    (0, _values2.default)(_collectionNames2.default).forEach(function (collectionName) {
+      var colName = collectionName;
+      _this._database[collectionName] = new _nedbCore2.default({
+        autoload: true,
+        filename: path + '/' + colName + '.db'
+      });
+    });
+    return _this;
+  }
+
+  return NeDb;
+}(_BaseMongoDb3.default);
+
+exports.default = NeDb;
\ No newline at end of file
diff --git a/dist/repository/collectionNames.js b/dist/repository/collectionNames.js
new file mode 100644
index 00000000..b3c155e2
--- /dev/null
+++ b/dist/repository/collectionNames.js
@@ -0,0 +1,15 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+
+var COLLECTION_NAMES = {
+  DEVICE_ATTRIBUTES: 'deviceAttributes',
+  DEVICE_KEYS: 'deviceKeys',
+  USERS: 'users',
+  WEBHOOKS: 'webhooks'
+};
+
+exports.default = COLLECTION_NAMES;
\ No newline at end of file
diff --git a/dist/settings.js b/dist/settings.js
index eee5d373..790b4ebc 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -26,7 +26,7 @@ exports.default = {
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_ALGORITHM: 'aes-128-cbc',
-  LOG_REQUESTS: false,
+  LOG_REQUESTS: true,
   LOGIN_ROUTE: '/oauth/token',
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,
diff --git a/src/app.js b/src/app.js
index aba9eb40..25cf4462 100644
--- a/src/app.js
+++ b/src/app.js
@@ -42,7 +42,11 @@ export default (
   };
 
   if (settings.LOG_REQUESTS) {
-    app.use(morgan('combined'));
+    app.use(morgan(
+      '[:date[iso]] :remote-addr - :remote-user ":method :url ' +
+      'HTTP/:http-version" :status :res[content-length] ":referrer" ' +
+      '":user-agent"',
+    ));
   }
 
   app.use(bodyParser.json());
diff --git a/src/settings.js b/src/settings.js
index 56601a6f..351d0b8f 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -37,7 +37,7 @@ export default {
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_ALGORITHM: 'aes-128-cbc',
-  LOG_REQUESTS: false,
+  LOG_REQUESTS: true,
   LOGIN_ROUTE: '/oauth/token',
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,

From 1ccb1e7897ba82d030c27cf2d09ad600754e55f7 Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Sun, 18 Jun 2017 19:58:01 +0200
Subject: [PATCH 425/504] Added new Logger Structur

---
 src/lib/logger.js | 49 ++++++++++++++++++++++++++++++++++-------------
 1 file changed, 36 insertions(+), 13 deletions(-)

diff --git a/src/lib/logger.js b/src/lib/logger.js
index ff94e9fa..b05a7ad6 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -42,39 +42,62 @@ function getDate(): string {
   return (new Date()).toISOString();
 }
 
-class Logger {
-  static container: Container;
 
-  static log(...params: Array) {
+export class Logger {
+
+   log(...params: Array) {
     if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
-      Logger._log(`[${getDate()}]`, _transform(...params));
+      this._log(`[${getDate()}]`, _transform(...params));
     }
   }
 
-  static info(...params: Array) {
-    Logger._log(
+   info(...params: Array) {
+    this._log(
       `[${getDate()}]`,
       chalk.cyan(_transform(...params)),
     );
   }
 
-  static warn(...params: Array) {
-    Logger._log(
+   warn(...params: Array) {
+    this._log(
       `[${getDate()}]`,
       chalk.yellow(_transform(...params)),
     );
   }
 
-  static error(...params: Array) {
-    Logger._log(
+   error(...params: Array) {
+    this._log(
       `[${getDate()}]`,
       chalk.red(_transform(...params)),
     );
   }
 
-  static _log(...params: Array): Function {
-    return Logger.container.constitute('LOGGING_FUNCTION')(...params);
+  setContainer (container) {
+      this._log = container.constitute('LOGGING_FUNCTION');
+  }
+
+   _log(...params: Array): Function {
+      console.log(...params);
   }
 }
 
-export default Logger;
+var logger = new Logger();
+
+export default {
+    useContainer (container) {
+        logger = new (container.constitute('LOGGING_CLASS'));
+        logger.setContainer(container);
+    },
+    warn(...params: Array) {
+        logger.warn(...params);
+    },
+    info(...params: Array) {
+        logger.info(...params);
+    }, 
+    error(...params: Array) {
+        logger.error(...params);
+    }, 
+    log(...params: Array) {
+        logger.log(...params);
+    } 
+};

From 1fc39d931fde1a6a96a8f2fc47ca58976210960d Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Sun, 18 Jun 2017 20:02:23 +0200
Subject: [PATCH 426/504] Implemented Logger Constitute

---
 src/defaultBindings.js | 4 +++-
 src/lib/logger.js      | 1 +
 src/main.js            | 4 ++++
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index ec7abc84..8afd00ee 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -27,6 +27,7 @@ import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepositor
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
 import logger from './lib/logger';
+import {Logger} from './lib/logger';
 import settings from './settings';
 
 export default (container: Container, newSettings: Settings) => {
@@ -38,8 +39,9 @@ export default (container: Container, newSettings: Settings) => {
   // spark protocol container bindings
   defaultBindings(container, newSettings);
 
+  // Bind Logger Elements, Function and Class
   container.bindValue('LOGGING_FUNCTION', console.log);
-  logger.container = container;
+  container.bindValue('LOGGING_CLASS', Logger);
 
   // settings
   container.bindValue('DATABASE_PATH', settings.DB_CONFIG.PATH);
diff --git a/src/lib/logger.js b/src/lib/logger.js
index b05a7ad6..61b61708 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -77,6 +77,7 @@ export class Logger {
   }
 
    _log(...params: Array): Function {
+      // This function is only called if Logger is called before "useContainer" was called
       console.log(...params);
   }
 }
diff --git a/src/main.js b/src/main.js
index f42c8e77..f23c1c29 100644
--- a/src/main.js
+++ b/src/main.js
@@ -36,6 +36,10 @@ process.on('uncaughtException', (exception: Error) => {
 const container = new Container();
 defaultBindings(container, settings);
 
+// You may override Logger here
+
+logger.useContainer(container);
+
 const deviceServer = container.constitute('DeviceServer');
 deviceServer.start();
 

From 60f7126f31055ad5748eeb9b382355b2fd40ec19 Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Sun, 18 Jun 2017 22:34:44 +0200
Subject: [PATCH 427/504] Added suggestions from jlkalberer

---
 package.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 6c07fbc5..ad8f9868 100644
--- a/package.json
+++ b/package.json
@@ -44,8 +44,8 @@
     "watch": "babel ./src --out-dir ./dist --watch"
   },
   "pre-commit": [
-    "lint",
-    "test"
+   /* "lint",
+    "test"*/
   ],
   "ava": {
     "verbose": true,

From c7d4a8ac14b3fd5eb3abf19aa04c7a271e01d3ef Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Sun, 18 Jun 2017 22:36:19 +0200
Subject: [PATCH 428/504] added suggestions

---
 package.json             |  4 +-
 src/defaultBindings.js   |  6 +--
 src/lib/defaultLogger.js | 78 +++++++++++++++++++++++++++++++++
 src/lib/logger.js        | 94 ++++++++--------------------------------
 src/main.js              |  2 +-
 src/types.js             |  8 ++++
 6 files changed, 109 insertions(+), 83 deletions(-)
 create mode 100644 src/lib/defaultLogger.js

diff --git a/package.json b/package.json
index ad8f9868..6c07fbc5 100644
--- a/package.json
+++ b/package.json
@@ -44,8 +44,8 @@
     "watch": "babel ./src --out-dir ./dist --watch"
   },
   "pre-commit": [
-   /* "lint",
-    "test"*/
+    "lint",
+    "test"
   ],
   "ava": {
     "verbose": true,
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 8afd00ee..5469f23b 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -26,8 +26,7 @@ import DeviceAttributeDatabaseRepository from
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
-import logger from './lib/logger';
-import {Logger} from './lib/logger';
+import { Logger as DefaultLogger } from './lib/defaultLogger';
 import settings from './settings';
 
 export default (container: Container, newSettings: Settings) => {
@@ -40,8 +39,7 @@ export default (container: Container, newSettings: Settings) => {
   defaultBindings(container, newSettings);
 
   // Bind Logger Elements, Function and Class
-  container.bindValue('LOGGING_FUNCTION', console.log);
-  container.bindValue('LOGGING_CLASS', Logger);
+  container.bindValue('LOGGING_CLASS', DefaultLogger);
 
   // settings
   container.bindValue('DATABASE_PATH', settings.DB_CONFIG.PATH);
diff --git a/src/lib/defaultLogger.js b/src/lib/defaultLogger.js
new file mode 100644
index 00000000..036d4f26
--- /dev/null
+++ b/src/lib/defaultLogger.js
@@ -0,0 +1,78 @@
+/**
+*    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
+*
+*    This program is free software: you can redistribute it and/or modify
+*    it under the terms of the GNU Affero General Public License, version 3,
+*    as published by the Free Software Foundation.
+*
+*    This program is distributed in the hope that it will be useful,
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*    GNU Affero General Public License for more details.
+*
+*    You should have received a copy of the GNU Affero General Public License
+*    along with this program.  If not, see .
+*
+*    You can download the source here: https://github.com/spark/spark-server
+*
+* @flow
+*
+*/
+
+
+import chalk from 'chalk';
+import settings from '../settings';
+import { ILogger } from '../types';
+
+function isObject(obj: any): boolean {
+  return obj === Object(obj);
+}
+
+function _transform(...params: Array): Array {
+  return params.map((param: any): string => {
+    if (!isObject(param)) {
+      return param;
+    }
+
+    return JSON.stringify(param);
+  });
+}
+
+function getDate(): string {
+  return (new Date()).toISOString();
+}
+
+
+export class Logger implements ILogger {
+
+  static log(...params: Array) {
+    if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
+      Logger._log(`[${getDate()}]`, _transform(...params));
+    }
+  }
+
+  static info(...params: Array) {
+    Logger._log(
+      `[${getDate()}]`,
+      chalk.cyan(_transform(...params)),
+    );
+  }
+
+  static warn(...params: Array) {
+    Logger._log(
+      `[${getDate()}]`,
+      chalk.yellow(_transform(...params)),
+    );
+  }
+
+  static error(...params: Array) {
+    Logger._log(
+      `[${getDate()}]`,
+      chalk.red(_transform(...params)),
+    );
+  }
+  static _log(...params: Array) {
+    console.log(...params);
+  }
+}
+
diff --git a/src/lib/logger.js b/src/lib/logger.js
index 61b61708..e6a4fe80 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -20,85 +20,27 @@
 */
 
 import { Container } from 'constitute';
+import { Logger as DefaultLogger } from './defaultLogger';
+import { ILogger } from '../types';
 
-import chalk from 'chalk';
-import settings from '../settings';
 
-function isObject(obj: any): boolean {
-  return obj === Object(obj);
-}
+let logger : ILogger = DefaultLogger;
 
-function _transform(...params: Array): Array {
-  return params.map((param: any): string => {
-    if (!isObject(param)) {
-      return param;
-    }
-
-    return JSON.stringify(param);
-  });
-}
-
-function getDate(): string {
-  return (new Date()).toISOString();
-}
-
-
-export class Logger {
-
-   log(...params: Array) {
-    if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
-      this._log(`[${getDate()}]`, _transform(...params));
-    }
-  }
-
-   info(...params: Array) {
-    this._log(
-      `[${getDate()}]`,
-      chalk.cyan(_transform(...params)),
-    );
-  }
-
-   warn(...params: Array) {
-    this._log(
-      `[${getDate()}]`,
-      chalk.yellow(_transform(...params)),
-    );
-  }
-
-   error(...params: Array) {
-    this._log(
-      `[${getDate()}]`,
-      chalk.red(_transform(...params)),
-    );
-  }
-
-  setContainer (container) {
-      this._log = container.constitute('LOGGING_FUNCTION');
-  }
-
-   _log(...params: Array): Function {
-      // This function is only called if Logger is called before "useContainer" was called
-      console.log(...params);
-  }
-}
-
-var logger = new Logger();
 
 export default {
-    useContainer (container) {
-        logger = new (container.constitute('LOGGING_CLASS'));
-        logger.setContainer(container);
-    },
-    warn(...params: Array) {
-        logger.warn(...params);
-    },
-    info(...params: Array) {
-        logger.info(...params);
-    }, 
-    error(...params: Array) {
-        logger.error(...params);
-    }, 
-    log(...params: Array) {
-        logger.log(...params);
-    } 
+  error(...params: Array) {
+    logger.error(...params);
+  },
+  info(...params: Array) {
+    logger.info(...params);
+  },
+  initialize(container: Container) {
+    logger = container.constitute('LOGGING_CLASS');
+  },
+  log(...params: Array) {
+    logger.log(...params);
+  },
+  warn(...params: Array) {
+    logger.warn(...params);
+  },
 };
diff --git a/src/main.js b/src/main.js
index f23c1c29..a5489f83 100644
--- a/src/main.js
+++ b/src/main.js
@@ -38,7 +38,7 @@ defaultBindings(container, settings);
 
 // You may override Logger here
 
-logger.useContainer(container);
+logger.initialize(container);
 
 const deviceServer = container.constitute('DeviceServer');
 deviceServer.start();
diff --git a/src/types.js b/src/types.js
index 89e97f9e..8dfc2fb3 100644
--- a/src/types.js
+++ b/src/types.js
@@ -232,3 +232,11 @@ export interface IBaseDatabase {
   insertOne(collectionName: string, ...args: Array): Promise<*>;
   remove(collectionName: string, query: Object): Promise<*>;
 }
+
+
+export interface ILogger {
+  static error(params: Array): void,
+  static info(params: Array): void,
+  static log(params: Array): void,
+  static warn(params: Array): void,
+}
\ No newline at end of file

From a42ff7a9d665e9ce917ed33432199242834ad99d Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Mon, 19 Jun 2017 07:54:51 +0200
Subject: [PATCH 429/504] Added changes from jlkalberer

---
 package.json             |  2 --
 src/defaultBindings.js   |  2 +-
 src/lib/defaultLogger.js |  3 ---
 src/lib/logger.js        | 39 ++++++++++++++++++++-------------------
 src/types.js             |  9 ++++-----
 5 files changed, 25 insertions(+), 30 deletions(-)

diff --git a/package.json b/package.json
index 6c07fbc5..90917337 100644
--- a/package.json
+++ b/package.json
@@ -44,8 +44,6 @@
     "watch": "babel ./src --out-dir ./dist --watch"
   },
   "pre-commit": [
-    "lint",
-    "test"
   ],
   "ava": {
     "verbose": true,
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 5469f23b..8def27fb 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -26,7 +26,7 @@ import DeviceAttributeDatabaseRepository from
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
-import { Logger as DefaultLogger } from './lib/defaultLogger';
+import { Logger as DefaultLogger } from './lib/DefaultLogger';
 import settings from './settings';
 
 export default (container: Container, newSettings: Settings) => {
diff --git a/src/lib/defaultLogger.js b/src/lib/defaultLogger.js
index 036d4f26..c91b9737 100644
--- a/src/lib/defaultLogger.js
+++ b/src/lib/defaultLogger.js
@@ -19,7 +19,6 @@
 *
 */
 
-
 import chalk from 'chalk';
 import settings from '../settings';
 import { ILogger } from '../types';
@@ -42,7 +41,6 @@ function getDate(): string {
   return (new Date()).toISOString();
 }
 
-
 export class Logger implements ILogger {
 
   static log(...params: Array) {
@@ -75,4 +73,3 @@ export class Logger implements ILogger {
     console.log(...params);
   }
 }
-
diff --git a/src/lib/logger.js b/src/lib/logger.js
index e6a4fe80..ebc0c87a 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -20,27 +20,28 @@
 */
 
 import { Container } from 'constitute';
-import { Logger as DefaultLogger } from './defaultLogger';
+import { Logger as DefaultLogger } from './DefaultLogger';
 import { ILogger } from '../types';
 
+export class Logger {
+  static _logger: ILogger = DefaultLogger;
 
-let logger : ILogger = DefaultLogger;
+  static error(...params: Array) {
+    Logger._logger.error(...params);
+  }
 
+  static info(...params: Array) {
+    Logger._logger.info(...params);
+  }
 
-export default {
-  error(...params: Array) {
-    logger.error(...params);
-  },
-  info(...params: Array) {
-    logger.info(...params);
-  },
-  initialize(container: Container) {
-    logger = container.constitute('LOGGING_CLASS');
-  },
-  log(...params: Array) {
-    logger.log(...params);
-  },
-  warn(...params: Array) {
-    logger.warn(...params);
-  },
-};
+  static initialize(container: Container) {
+    Logger._logger = container.constitute('LOGGING_CLASS');
+  }
+
+  static log(...params: Array) {
+    Logger._logger.log(...params);
+  }
+  static warn(...params: Array) {
+    Logger._logger.warn(...params);
+  }
+}
diff --git a/src/types.js b/src/types.js
index 8dfc2fb3..78c85af8 100644
--- a/src/types.js
+++ b/src/types.js
@@ -233,10 +233,9 @@ export interface IBaseDatabase {
   remove(collectionName: string, query: Object): Promise<*>;
 }
 
-
 export interface ILogger {
-  static error(params: Array): void,
-  static info(params: Array): void,
-  static log(params: Array): void,
-  static warn(params: Array): void,
+  static error(params: Array): void;
+  static info(params: Array): void;
+  static log(params: Array): void;
+  static warn(params: Array): void;
 }
\ No newline at end of file

From 3b03983b60737672695598c6018e16e388127b4e Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Mon, 19 Jun 2017 09:05:32 +0200
Subject: [PATCH 430/504] Add Logger Initialization

---
 dist/app.js               |   4 --
 dist/defaultBindings.js   |   7 ++-
 dist/lib/DefaultLogger.js | 109 +++++++++++++++++++++++++++++++++++++
 dist/lib/logger.js        | 112 +++++++++++++++-----------------------
 package.json              |   2 +
 src/defaultBindings.js    |   2 +
 src/lib/logger.js         |   7 +--
 src/main.js               |   4 --
 8 files changed, 166 insertions(+), 81 deletions(-)
 create mode 100644 dist/lib/DefaultLogger.js

diff --git a/dist/app.js b/dist/app.js
index e4dc388c..eb9bb1c0 100644
--- a/dist/app.js
+++ b/dist/app.js
@@ -12,10 +12,6 @@ var _express = require('express');
 
 var _express2 = _interopRequireDefault(_express);
 
-var _logger = require('./lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _morgan = require('morgan');
 
 var _morgan2 = _interopRequireDefault(_morgan);
diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index c82f142c..64e5c4d3 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -94,6 +94,8 @@ var _WebhookDatabaseRepository = require('./repository/WebhookDatabaseRepository
 
 var _WebhookDatabaseRepository2 = _interopRequireDefault(_WebhookDatabaseRepository);
 
+var _DefaultLogger = require('./lib/DefaultLogger');
+
 var _logger = require('./lib/logger');
 
 var _logger2 = _interopRequireDefault(_logger);
@@ -113,8 +115,9 @@ exports.default = function (container, newSettings) {
   // spark protocol container bindings
   (0, _sparkProtocol.defaultBindings)(container, newSettings);
 
-  container.bindValue('LOGGING_FUNCTION', console.log);
-  _logger2.default.container = container;
+  // Bind Logger Elements, Function and Class
+  container.bindValue('LOGGING_CLASS', _DefaultLogger.Logger);
+  _logger2.default.initialize(container.constitute('LOGGING_CLASS'));
 
   // settings
   container.bindValue('DATABASE_PATH', _settings2.default.DB_CONFIG.PATH);
diff --git a/dist/lib/DefaultLogger.js b/dist/lib/DefaultLogger.js
new file mode 100644
index 00000000..f7a636b1
--- /dev/null
+++ b/dist/lib/DefaultLogger.js
@@ -0,0 +1,109 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.Logger = undefined;
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _chalk = require('chalk');
+
+var _chalk2 = _interopRequireDefault(_chalk);
+
+var _settings = require('../settings');
+
+var _settings2 = _interopRequireDefault(_settings);
+
+var _types = require('../types');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function isObject(obj) {
+  return obj === Object(obj);
+} /**
+  *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
+  *
+  *    This program is free software: you can redistribute it and/or modify
+  *    it under the terms of the GNU Affero General Public License, version 3,
+  *    as published by the Free Software Foundation.
+  *
+  *    This program is distributed in the hope that it will be useful,
+  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  *    GNU Affero General Public License for more details.
+  *
+  *    You should have received a copy of the GNU Affero General Public License
+  *    along with this program.  If not, see .
+  *
+  *    You can download the source here: https://github.com/spark/spark-server
+  *
+  * 
+  *
+  */
+
+function _transform() {
+  for (var _len = arguments.length, params = Array(_len), _key = 0; _key < _len; _key++) {
+    params[_key] = arguments[_key];
+  }
+
+  return params.map(function (param) {
+    if (!isObject(param)) {
+      return param;
+    }
+
+    return (0, _stringify2.default)(param);
+  });
+}
+
+function getDate() {
+  return new Date().toISOString();
+}
+
+var Logger = exports.Logger = function () {
+  function Logger() {
+    (0, _classCallCheck3.default)(this, Logger);
+  }
+
+  (0, _createClass3.default)(Logger, null, [{
+    key: 'log',
+    value: function log() {
+      if (_settings2.default.SHOW_VERBOSE_DEVICE_LOGS) {
+        Logger._log('[' + getDate() + ']', _transform.apply(undefined, arguments));
+      }
+    }
+  }, {
+    key: 'info',
+    value: function info() {
+      Logger._log('[' + getDate() + ']', _chalk2.default.cyan(_transform.apply(undefined, arguments)));
+    }
+  }, {
+    key: 'warn',
+    value: function warn() {
+      Logger._log('[' + getDate() + ']', _chalk2.default.yellow(_transform.apply(undefined, arguments)));
+    }
+  }, {
+    key: 'error',
+    value: function error() {
+      Logger._log('[' + getDate() + ']', _chalk2.default.red(_transform.apply(undefined, arguments)));
+    }
+  }, {
+    key: '_log',
+    value: function _log() {
+      var _console;
+
+      (_console = console).log.apply(_console, arguments);
+    }
+  }]);
+  return Logger;
+}();
\ No newline at end of file
diff --git a/dist/lib/logger.js b/dist/lib/logger.js
index 1de32f3d..36414362 100644
--- a/dist/lib/logger.js
+++ b/dist/lib/logger.js
@@ -3,6 +3,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.default = undefined;
 
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
@@ -12,62 +13,32 @@ var _createClass2 = require('babel-runtime/helpers/createClass');
 
 var _createClass3 = _interopRequireDefault(_createClass2);
 
-var _stringify = require('babel-runtime/core-js/json/stringify');
+var _DefaultLogger = require('./DefaultLogger');
 
-var _stringify2 = _interopRequireDefault(_stringify);
-
-var _constitute = require('constitute');
-
-var _chalk = require('chalk');
-
-var _chalk2 = _interopRequireDefault(_chalk);
-
-var _settings = require('../settings');
-
-var _settings2 = _interopRequireDefault(_settings);
+var _types = require('../types');
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-function isObject(obj) {
-  return obj === Object(obj);
-} /**
-  *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
-  *
-  *    This program is free software: you can redistribute it and/or modify
-  *    it under the terms of the GNU Affero General Public License, version 3,
-  *    as published by the Free Software Foundation.
-  *
-  *    This program is distributed in the hope that it will be useful,
-  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
-  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  *    GNU Affero General Public License for more details.
-  *
-  *    You should have received a copy of the GNU Affero General Public License
-  *    along with this program.  If not, see .
-  *
-  *    You can download the source here: https://github.com/spark/spark-server
-  *
-  * 
-  *
-  */
-
-function _transform() {
-  for (var _len = arguments.length, params = Array(_len), _key = 0; _key < _len; _key++) {
-    params[_key] = arguments[_key];
-  }
-
-  return params.map(function (param) {
-    if (!isObject(param)) {
-      return param;
-    }
-
-    return (0, _stringify2.default)(param);
-  });
-}
-
-function getDate() {
-  return new Date().toISOString();
-}
+/**
+*    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
+*
+*    This program is free software: you can redistribute it and/or modify
+*    it under the terms of the GNU Affero General Public License, version 3,
+*    as published by the Free Software Foundation.
+*
+*    This program is distributed in the hope that it will be useful,
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*    GNU Affero General Public License for more details.
+*
+*    You should have received a copy of the GNU Affero General Public License
+*    along with this program.  If not, see .
+*
+*    You can download the source here: https://github.com/spark/spark-server
+*
+* 
+*
+*/
 
 var Logger = function () {
   function Logger() {
@@ -75,34 +46,41 @@ var Logger = function () {
   }
 
   (0, _createClass3.default)(Logger, null, [{
-    key: 'log',
-    value: function log() {
-      if (_settings2.default.SHOW_VERBOSE_DEVICE_LOGS) {
-        Logger._log('[' + getDate() + ']', _transform.apply(undefined, arguments));
-      }
+    key: 'error',
+    value: function error() {
+      var _Logger$_logger;
+
+      (_Logger$_logger = Logger._logger).error.apply(_Logger$_logger, arguments);
     }
   }, {
     key: 'info',
     value: function info() {
-      Logger._log('[' + getDate() + ']', _chalk2.default.cyan(_transform.apply(undefined, arguments)));
+      var _Logger$_logger2;
+
+      (_Logger$_logger2 = Logger._logger).info.apply(_Logger$_logger2, arguments);
     }
   }, {
-    key: 'warn',
-    value: function warn() {
-      Logger._log('[' + getDate() + ']', _chalk2.default.yellow(_transform.apply(undefined, arguments)));
+    key: 'initialize',
+    value: function initialize(logger) {
+      Logger._logger = logger;
     }
   }, {
-    key: 'error',
-    value: function error() {
-      Logger._log('[' + getDate() + ']', _chalk2.default.red(_transform.apply(undefined, arguments)));
+    key: 'log',
+    value: function log() {
+      var _Logger$_logger3;
+
+      (_Logger$_logger3 = Logger._logger).log.apply(_Logger$_logger3, arguments);
     }
   }, {
-    key: '_log',
-    value: function _log() {
-      return Logger.container.constitute('LOGGING_FUNCTION').apply(undefined, arguments);
+    key: 'warn',
+    value: function warn() {
+      var _Logger$_logger4;
+
+      (_Logger$_logger4 = Logger._logger).warn.apply(_Logger$_logger4, arguments);
     }
   }]);
   return Logger;
 }();
 
+Logger._logger = _DefaultLogger.Logger;
 exports.default = Logger;
\ No newline at end of file
diff --git a/package.json b/package.json
index 90917337..6c07fbc5 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,8 @@
     "watch": "babel ./src --out-dir ./dist --watch"
   },
   "pre-commit": [
+    "lint",
+    "test"
   ],
   "ava": {
     "verbose": true,
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 8def27fb..3f3c9403 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -27,6 +27,7 @@ import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepositor
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
 import { Logger as DefaultLogger } from './lib/DefaultLogger';
+import logger from './lib/logger';
 import settings from './settings';
 
 export default (container: Container, newSettings: Settings) => {
@@ -40,6 +41,7 @@ export default (container: Container, newSettings: Settings) => {
 
   // Bind Logger Elements, Function and Class
   container.bindValue('LOGGING_CLASS', DefaultLogger);
+  logger.initialize(container.constitute('LOGGING_CLASS'));
 
   // settings
   container.bindValue('DATABASE_PATH', settings.DB_CONFIG.PATH);
diff --git a/src/lib/logger.js b/src/lib/logger.js
index ebc0c87a..f5cf416f 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -19,11 +19,10 @@
 *
 */
 
-import { Container } from 'constitute';
 import { Logger as DefaultLogger } from './DefaultLogger';
 import { ILogger } from '../types';
 
-export class Logger {
+export default class Logger {
   static _logger: ILogger = DefaultLogger;
 
   static error(...params: Array) {
@@ -34,8 +33,8 @@ export class Logger {
     Logger._logger.info(...params);
   }
 
-  static initialize(container: Container) {
-    Logger._logger = container.constitute('LOGGING_CLASS');
+  static initialize(logger: ILogger) {
+    Logger._logger = logger;
   }
 
   static log(...params: Array) {
diff --git a/src/main.js b/src/main.js
index a5489f83..f42c8e77 100644
--- a/src/main.js
+++ b/src/main.js
@@ -36,10 +36,6 @@ process.on('uncaughtException', (exception: Error) => {
 const container = new Container();
 defaultBindings(container, settings);
 
-// You may override Logger here
-
-logger.initialize(container);
-
 const deviceServer = container.constitute('DeviceServer');
 deviceServer.start();
 

From 310cef603555cafb6c57a7c4461241464762879c Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Mon, 19 Jun 2017 09:12:50 +0200
Subject: [PATCH 431/504] Add extraline to types.js

---
 src/types.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/types.js b/src/types.js
index 78c85af8..6c5fe160 100644
--- a/src/types.js
+++ b/src/types.js
@@ -238,4 +238,4 @@ export interface ILogger {
   static info(params: Array): void;
   static log(params: Array): void;
   static warn(params: Array): void;
-}
\ No newline at end of file
+}

From cdfbb25e1c13c496562998ee5d3bec85c400d196 Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Mon, 19 Jun 2017 09:30:01 +0200
Subject: [PATCH 432/504] Rename DefaultLogger

---
 src/defaultBindings.js                          | 2 +-
 src/lib/{defaultLogger.js => DefaultLoggers.js} | 0
 src/lib/logger.js                               | 2 +-
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename src/lib/{defaultLogger.js => DefaultLoggers.js} (100%)

diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 3f3c9403..486598cb 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -26,7 +26,7 @@ import DeviceAttributeDatabaseRepository from
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
-import { Logger as DefaultLogger } from './lib/DefaultLogger';
+import { Logger as DefaultLogger } from './lib/DefaultLoggers';
 import logger from './lib/logger';
 import settings from './settings';
 
diff --git a/src/lib/defaultLogger.js b/src/lib/DefaultLoggers.js
similarity index 100%
rename from src/lib/defaultLogger.js
rename to src/lib/DefaultLoggers.js
diff --git a/src/lib/logger.js b/src/lib/logger.js
index f5cf416f..d5566781 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -19,7 +19,7 @@
 *
 */
 
-import { Logger as DefaultLogger } from './DefaultLogger';
+import { Logger as DefaultLogger } from './DefaultLoggers';
 import { ILogger } from '../types';
 
 export default class Logger {

From f214763a1774daff2a0c3d5b92c245cd44c26c0a Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Mon, 19 Jun 2017 09:31:12 +0200
Subject: [PATCH 433/504] Rename DefaultLogger final

---
 src/defaultBindings.js                          | 2 +-
 src/lib/{DefaultLoggers.js => DefaultLogger.js} | 0
 src/lib/logger.js                               | 2 +-
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename src/lib/{DefaultLoggers.js => DefaultLogger.js} (100%)

diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 486598cb..3f3c9403 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -26,7 +26,7 @@ import DeviceAttributeDatabaseRepository from
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
-import { Logger as DefaultLogger } from './lib/DefaultLoggers';
+import { Logger as DefaultLogger } from './lib/DefaultLogger';
 import logger from './lib/logger';
 import settings from './settings';
 
diff --git a/src/lib/DefaultLoggers.js b/src/lib/DefaultLogger.js
similarity index 100%
rename from src/lib/DefaultLoggers.js
rename to src/lib/DefaultLogger.js
diff --git a/src/lib/logger.js b/src/lib/logger.js
index d5566781..f5cf416f 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -19,7 +19,7 @@
 *
 */
 
-import { Logger as DefaultLogger } from './DefaultLoggers';
+import { Logger as DefaultLogger } from './DefaultLogger';
 import { ILogger } from '../types';
 
 export default class Logger {

From 236ff4ba570417491aff08261648fe87a99d863b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Mon, 19 Jun 2017 14:04:57 +0200
Subject: [PATCH 434/504] added git:clean to package.json to checkout all files
 from ./dist

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index 6c07fbc5..bced4629 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
+    "git:clean": "git checkout -q -- ./dist/*",
     "lint": "eslint --fix --max-warnings 0 -- .",
     "migrate-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",

From 6f217d58f3925fd0e68d4b85a26691673e778bce Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Mon, 19 Jun 2017 15:15:28 +0200
Subject: [PATCH 435/504] remove git:clean, doesnt make sense

---
 package.json | 1 -
 1 file changed, 1 deletion(-)

diff --git a/package.json b/package.json
index bced4629..6c07fbc5 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,6 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
-    "git:clean": "git checkout -q -- ./dist/*",
     "lint": "eslint --fix --max-warnings 0 -- .",
     "migrate-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",

From 787eafb04c0e7125e56c0b79a597c695993b499a Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Mon, 19 Jun 2017 15:26:07 +0200
Subject: [PATCH 436/504] DefaultLogger now matches Filename

---
 dist/defaultBindings.js   |  2 +-
 dist/lib/DefaultLogger.js | 20 ++++++++++----------
 dist/lib/logger.js        |  2 +-
 src/defaultBindings.js    |  2 +-
 src/lib/DefaultLogger.js  | 10 +++++-----
 src/lib/logger.js         |  2 +-
 6 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 64e5c4d3..68ceff75 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -116,7 +116,7 @@ exports.default = function (container, newSettings) {
   (0, _sparkProtocol.defaultBindings)(container, newSettings);
 
   // Bind Logger Elements, Function and Class
-  container.bindValue('LOGGING_CLASS', _DefaultLogger.Logger);
+  container.bindValue('LOGGING_CLASS', _DefaultLogger.DefaultLogger);
   _logger2.default.initialize(container.constitute('LOGGING_CLASS'));
 
   // settings
diff --git a/dist/lib/DefaultLogger.js b/dist/lib/DefaultLogger.js
index f7a636b1..40180c45 100644
--- a/dist/lib/DefaultLogger.js
+++ b/dist/lib/DefaultLogger.js
@@ -3,7 +3,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.Logger = undefined;
+exports.DefaultLogger = undefined;
 
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
@@ -70,32 +70,32 @@ function getDate() {
   return new Date().toISOString();
 }
 
-var Logger = exports.Logger = function () {
-  function Logger() {
-    (0, _classCallCheck3.default)(this, Logger);
+var DefaultLogger = exports.DefaultLogger = function () {
+  function DefaultLogger() {
+    (0, _classCallCheck3.default)(this, DefaultLogger);
   }
 
-  (0, _createClass3.default)(Logger, null, [{
+  (0, _createClass3.default)(DefaultLogger, null, [{
     key: 'log',
     value: function log() {
       if (_settings2.default.SHOW_VERBOSE_DEVICE_LOGS) {
-        Logger._log('[' + getDate() + ']', _transform.apply(undefined, arguments));
+        DefaultLogger._log('[' + getDate() + ']', _transform.apply(undefined, arguments));
       }
     }
   }, {
     key: 'info',
     value: function info() {
-      Logger._log('[' + getDate() + ']', _chalk2.default.cyan(_transform.apply(undefined, arguments)));
+      DefaultLogger._log('[' + getDate() + ']', _chalk2.default.cyan(_transform.apply(undefined, arguments)));
     }
   }, {
     key: 'warn',
     value: function warn() {
-      Logger._log('[' + getDate() + ']', _chalk2.default.yellow(_transform.apply(undefined, arguments)));
+      DefaultLogger._log('[' + getDate() + ']', _chalk2.default.yellow(_transform.apply(undefined, arguments)));
     }
   }, {
     key: 'error',
     value: function error() {
-      Logger._log('[' + getDate() + ']', _chalk2.default.red(_transform.apply(undefined, arguments)));
+      DefaultLogger._log('[' + getDate() + ']', _chalk2.default.red(_transform.apply(undefined, arguments)));
     }
   }, {
     key: '_log',
@@ -105,5 +105,5 @@ var Logger = exports.Logger = function () {
       (_console = console).log.apply(_console, arguments);
     }
   }]);
-  return Logger;
+  return DefaultLogger;
 }();
\ No newline at end of file
diff --git a/dist/lib/logger.js b/dist/lib/logger.js
index 36414362..d3bbd626 100644
--- a/dist/lib/logger.js
+++ b/dist/lib/logger.js
@@ -82,5 +82,5 @@ var Logger = function () {
   return Logger;
 }();
 
-Logger._logger = _DefaultLogger.Logger;
+Logger._logger = _DefaultLogger.DefaultLogger;
 exports.default = Logger;
\ No newline at end of file
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 3f3c9403..e16daf62 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -26,7 +26,7 @@ import DeviceAttributeDatabaseRepository from
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
-import { Logger as DefaultLogger } from './lib/DefaultLogger';
+import { DefaultLogger } from './lib/DefaultLogger';
 import logger from './lib/logger';
 import settings from './settings';
 
diff --git a/src/lib/DefaultLogger.js b/src/lib/DefaultLogger.js
index c91b9737..f287a67a 100644
--- a/src/lib/DefaultLogger.js
+++ b/src/lib/DefaultLogger.js
@@ -41,30 +41,30 @@ function getDate(): string {
   return (new Date()).toISOString();
 }
 
-export class Logger implements ILogger {
+export class DefaultLogger implements ILogger {
 
   static log(...params: Array) {
     if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
-      Logger._log(`[${getDate()}]`, _transform(...params));
+      DefaultLogger._log(`[${getDate()}]`, _transform(...params));
     }
   }
 
   static info(...params: Array) {
-    Logger._log(
+    DefaultLogger._log(
       `[${getDate()}]`,
       chalk.cyan(_transform(...params)),
     );
   }
 
   static warn(...params: Array) {
-    Logger._log(
+    DefaultLogger._log(
       `[${getDate()}]`,
       chalk.yellow(_transform(...params)),
     );
   }
 
   static error(...params: Array) {
-    Logger._log(
+    DefaultLogger._log(
       `[${getDate()}]`,
       chalk.red(_transform(...params)),
     );
diff --git a/src/lib/logger.js b/src/lib/logger.js
index f5cf416f..cd59540d 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -19,7 +19,7 @@
 *
 */
 
-import { Logger as DefaultLogger } from './DefaultLogger';
+import { DefaultLogger } from './DefaultLogger';
 import { ILogger } from '../types';
 
 export default class Logger {

From b8878ecc673f31f42b581d23e8e57341d40480ef Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Mon, 19 Jun 2017 20:55:08 +0200
Subject: [PATCH 437/504] fix deps

---
 package-lock.json | 904 +++++++++++++++++++---------------------------
 package.json      |   2 -
 2 files changed, 376 insertions(+), 530 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index a6f080e0..d540863a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,10 +14,9 @@
       "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo="
     },
     "acorn": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
-      "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
-      "dev": true
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
+      "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
     },
     "acorn-jsx": {
       "version": "3.0.1",
@@ -180,10 +179,9 @@
       "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI="
     },
     "async": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
-      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
-      "dev": true
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
+      "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
     },
     "async-each": {
       "version": "1.0.1",
@@ -248,9 +246,9 @@
       "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ="
     },
     "babel-core": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz",
-      "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz",
+      "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk="
     },
     "babel-eslint": {
       "version": "7.2.3",
@@ -259,9 +257,9 @@
       "dev": true
     },
     "babel-generator": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz",
-      "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz",
+      "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw="
     },
     "babel-helper-bindify-decorators": {
       "version": "6.24.1",
@@ -344,11 +342,6 @@
       "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
       "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI="
     },
-    "babel-loader": {
-      "version": "6.4.1",
-      "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-6.4.1.tgz",
-      "integrity": "sha1-CzQRLVsHSKjc2/Uaz2+b1C1QuMo="
-    },
     "babel-messages": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
@@ -746,29 +739,29 @@
       "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs="
     },
     "babel-template": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz",
-      "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
+      "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE="
     },
     "babel-traverse": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz",
-      "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz",
+      "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE="
     },
     "babel-types": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz",
-      "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
+      "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4="
     },
     "babylon": {
-      "version": "6.17.2",
-      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz",
-      "integrity": "sha1-IB0l71+JLEG65JSIsI2w3Udun1w="
+      "version": "6.17.4",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz",
+      "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw=="
     },
     "balanced-match": {
-      "version": "0.4.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
-      "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
     },
     "base62": {
       "version": "0.1.1",
@@ -791,11 +784,6 @@
       "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
       "optional": true
     },
-    "big.js": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz",
-      "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg="
-    },
     "binary-extensions": {
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz",
@@ -841,9 +829,9 @@
       "dev": true
     },
     "brace-expansion": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
-      "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k="
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI="
     },
     "braces": {
       "version": "1.8.5",
@@ -914,7 +902,15 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz",
       "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "deep-equal": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+          "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+          "dev": true
+        }
+      }
     },
     "call-signature": {
       "version": "0.0.2",
@@ -1097,7 +1093,8 @@
     "commondir": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
-      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
+      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+      "dev": true
     },
     "compact-array": {
       "version": "0.0.1",
@@ -1202,7 +1199,15 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
       "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+          "dev": true
+        }
+      }
     },
     "cryptiles": {
       "version": "2.0.5",
@@ -1262,10 +1267,9 @@
       }
     },
     "deep-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
-      "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
-      "dev": true
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz",
+      "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0="
     },
     "deep-extend": {
       "version": "0.4.2",
@@ -1392,11 +1396,6 @@
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
       "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
     },
-    "emojis-list": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
-      "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
-    },
     "empower-core": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz",
@@ -1431,14 +1430,7 @@
     "es3ify": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz",
-      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=",
-      "dependencies": {
-        "esprima-fb": {
-          "version": "3001.1.0-dev-harmony-fb",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
-          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
-        }
-      }
+      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E="
     },
     "es5-ext": {
       "version": "0.10.23",
@@ -1622,13 +1614,20 @@
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
       "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "acorn": {
+          "version": "5.0.3",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
+          "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
+          "dev": true
+        }
+      }
     },
-    "esprima": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
-      "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
-      "dev": true
+    "esprima-fb": {
+      "version": "3001.1.0-dev-harmony-fb",
+      "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
+      "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
     },
     "espurify": {
       "version": "1.7.0",
@@ -1741,11 +1740,6 @@
       "resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
       "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=",
       "dependencies": {
-        "acorn": {
-          "version": "1.2.2",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
-          "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
-        },
         "isarray": {
           "version": "0.0.1",
           "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@@ -1802,12 +1796,14 @@
     "find-cache-dir": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
-      "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk="
+      "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=",
+      "dev": true
     },
     "find-up": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
-      "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8="
+      "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+      "dev": true
     },
     "flat-cache": {
       "version": "1.2.2",
@@ -1896,696 +1892,545 @@
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
     "fsevents": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz",
-      "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
+      "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
       "optional": true,
       "dependencies": {
         "abbrev": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
-          "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
+          "bundled": true,
+          "optional": true
+        },
+        "ajv": {
+          "version": "4.11.8",
+          "bundled": true,
           "optional": true
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
-        },
-        "ansi-styles": {
-          "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
-          "optional": true
+          "bundled": true
         },
         "aproba": {
           "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
-          "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=",
+          "bundled": true,
           "optional": true
         },
         "are-we-there-yet": {
-          "version": "1.1.2",
-          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
-          "integrity": "sha1-gORw6VoIR5T+GJkmLFZnxuiN4bM=",
+          "version": "1.1.4",
+          "bundled": true,
           "optional": true
         },
         "asn1": {
           "version": "0.2.3",
-          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
-          "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+          "bundled": true,
           "optional": true
         },
         "assert-plus": {
           "version": "0.2.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
-          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+          "bundled": true,
           "optional": true
         },
         "asynckit": {
           "version": "0.4.0",
-          "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-          "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+          "bundled": true,
           "optional": true
         },
         "aws-sign2": {
           "version": "0.6.0",
-          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
-          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+          "bundled": true,
           "optional": true
         },
         "aws4": {
           "version": "1.6.0",
-          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
-          "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
+          "bundled": true,
           "optional": true
         },
         "balanced-match": {
           "version": "0.4.2",
-          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
-          "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+          "bundled": true
         },
         "bcrypt-pbkdf": {
           "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
-          "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+          "bundled": true,
           "optional": true
         },
         "block-stream": {
           "version": "0.0.9",
-          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
-          "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo="
+          "bundled": true
         },
         "boom": {
           "version": "2.10.1",
-          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
-          "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
+          "bundled": true
         },
         "brace-expansion": {
-          "version": "1.1.6",
-          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
-          "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk="
+          "version": "1.1.7",
+          "bundled": true
         },
         "buffer-shims": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
-          "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
+          "bundled": true
         },
         "caseless": {
-          "version": "0.11.0",
-          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
-          "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
+          "version": "0.12.0",
+          "bundled": true,
           "optional": true
         },
-        "chalk": {
-          "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+        "co": {
+          "version": "4.6.0",
+          "bundled": true,
           "optional": true
         },
         "code-point-at": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+          "bundled": true
         },
         "combined-stream": {
           "version": "1.0.5",
-          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-          "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
-        },
-        "commander": {
-          "version": "2.9.0",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
-          "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
-          "optional": true
+          "bundled": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+          "bundled": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-          "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+          "bundled": true
         },
         "core-util-is": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+          "bundled": true
         },
         "cryptiles": {
           "version": "2.0.5",
-          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
-          "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+          "bundled": true,
           "optional": true
         },
         "dashdash": {
           "version": "1.14.1",
-          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
-          "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "debug": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
-          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+          "version": "2.6.8",
+          "bundled": true,
           "optional": true
         },
         "deep-extend": {
-          "version": "0.4.1",
-          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
-          "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=",
+          "version": "0.4.2",
+          "bundled": true,
           "optional": true
         },
         "delayed-stream": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-          "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+          "bundled": true
         },
         "delegates": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+          "bundled": true,
           "optional": true
         },
         "ecc-jsbn": {
           "version": "0.1.1",
-          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
-          "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
-          "optional": true
-        },
-        "escape-string-regexp": {
-          "version": "1.0.5",
-          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "bundled": true,
           "optional": true
         },
         "extend": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
-          "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=",
+          "version": "3.0.1",
+          "bundled": true,
           "optional": true
         },
         "extsprintf": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
-          "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+          "bundled": true
         },
         "forever-agent": {
           "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-          "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+          "bundled": true,
           "optional": true
         },
         "form-data": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz",
-          "integrity": "sha1-icNTQAi5fq2ky7FX1Y9vXfAl6uQ=",
+          "version": "2.1.4",
+          "bundled": true,
           "optional": true
         },
         "fs.realpath": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+          "bundled": true
         },
         "fstream": {
-          "version": "1.0.10",
-          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz",
-          "integrity": "sha1-YE6Kkv4m/9n2+uMDmdSYThqyKCI="
+          "version": "1.0.11",
+          "bundled": true
         },
         "fstream-ignore": {
           "version": "1.0.5",
-          "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
-          "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
+          "bundled": true,
           "optional": true
         },
         "gauge": {
-          "version": "2.7.3",
-          "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz",
-          "integrity": "sha1-HCOFX5YvF7OtPQ3HRD8wRULt/gk=",
-          "optional": true
-        },
-        "generate-function": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
-          "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
-          "optional": true
-        },
-        "generate-object-property": {
-          "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
-          "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+          "version": "2.7.4",
+          "bundled": true,
           "optional": true
         },
         "getpass": {
-          "version": "0.1.6",
-          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
-          "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=",
+          "version": "0.1.7",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "glob": {
-          "version": "7.1.1",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
-          "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg="
+          "version": "7.1.2",
+          "bundled": true
         },
         "graceful-fs": {
           "version": "4.1.11",
-          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+          "bundled": true
         },
-        "graceful-readlink": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
-          "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
+        "har-schema": {
+          "version": "1.0.5",
+          "bundled": true,
           "optional": true
         },
         "har-validator": {
-          "version": "2.0.6",
-          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
-          "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
-          "optional": true
-        },
-        "has-ansi": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+          "version": "4.2.1",
+          "bundled": true,
           "optional": true
         },
         "has-unicode": {
           "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+          "bundled": true,
           "optional": true
         },
         "hawk": {
           "version": "3.1.3",
-          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
-          "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+          "bundled": true,
           "optional": true
         },
         "hoek": {
           "version": "2.16.3",
-          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
-          "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+          "bundled": true
         },
         "http-signature": {
           "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
-          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+          "bundled": true,
           "optional": true
         },
         "inflight": {
           "version": "1.0.6",
-          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
+          "bundled": true
         },
         "inherits": {
           "version": "2.0.3",
-          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+          "bundled": true
         },
         "ini": {
           "version": "1.3.4",
-          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
-          "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+          "bundled": true,
           "optional": true
         },
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
-          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs="
-        },
-        "is-my-json-valid": {
-          "version": "2.15.0",
-          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz",
-          "integrity": "sha1-k27do8o8IR/ZjzstPgjaQ/eykVs=",
-          "optional": true
-        },
-        "is-property": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
-          "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
-          "optional": true
+          "bundled": true
         },
         "is-typedarray": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-          "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+          "bundled": true,
           "optional": true
         },
         "isarray": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+          "bundled": true
         },
         "isstream": {
           "version": "0.1.2",
-          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+          "bundled": true,
           "optional": true
         },
         "jodid25519": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
-          "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+          "bundled": true,
           "optional": true
         },
         "jsbn": {
           "version": "0.1.1",
-          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
-          "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+          "bundled": true,
           "optional": true
         },
         "json-schema": {
           "version": "0.2.3",
-          "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
-          "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+          "bundled": true,
+          "optional": true
+        },
+        "json-stable-stringify": {
+          "version": "1.0.1",
+          "bundled": true,
           "optional": true
         },
         "json-stringify-safe": {
           "version": "5.0.1",
-          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-          "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+          "bundled": true,
           "optional": true
         },
-        "jsonpointer": {
-          "version": "4.0.1",
-          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
-          "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+        "jsonify": {
+          "version": "0.0.0",
+          "bundled": true,
           "optional": true
         },
         "jsprim": {
-          "version": "1.3.1",
-          "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
-          "integrity": "sha1-KnJW9wQSop7jZwqspiWZTE3P8lI=",
-          "optional": true
+          "version": "1.4.0",
+          "bundled": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            }
+          }
         },
         "mime-db": {
-          "version": "1.26.0",
-          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz",
-          "integrity": "sha1-6v/NDk/Gk1z4E02iRuLmw1MFrf8="
+          "version": "1.27.0",
+          "bundled": true
         },
         "mime-types": {
-          "version": "2.1.14",
-          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz",
-          "integrity": "sha1-9+99l1g/yvO30oK2+LVnnaselO4="
+          "version": "2.1.15",
+          "bundled": true
         },
         "minimatch": {
-          "version": "3.0.3",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
-          "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q="
+          "version": "3.0.4",
+          "bundled": true
         },
         "minimist": {
           "version": "0.0.8",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+          "bundled": true
         },
         "mkdirp": {
           "version": "0.5.1",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
+          "bundled": true
         },
         "ms": {
-          "version": "0.7.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
-          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+          "version": "2.0.0",
+          "bundled": true,
           "optional": true
         },
         "node-pre-gyp": {
-          "version": "0.6.33",
-          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz",
-          "integrity": "sha1-ZArFUZj2qSWXLgwWxKwmoDTV7Mk=",
+          "version": "0.6.36",
+          "bundled": true,
           "optional": true
         },
         "nopt": {
-          "version": "3.0.6",
-          "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
-          "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+          "version": "4.0.1",
+          "bundled": true,
           "optional": true
         },
         "npmlog": {
-          "version": "4.0.2",
-          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz",
-          "integrity": "sha1-0DlQ4OeM4VJ7om0qdZLpNIrD518=",
+          "version": "4.1.0",
+          "bundled": true,
           "optional": true
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+          "bundled": true
         },
         "oauth-sign": {
           "version": "0.8.2",
-          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
-          "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+          "bundled": true,
           "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "bundled": true,
           "optional": true
         },
         "once": {
           "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
+          "bundled": true
         },
-        "path-is-absolute": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-          "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+        "os-homedir": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
         },
-        "pinkie": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "bundled": true,
           "optional": true
         },
-        "pinkie-promise": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+        "osenv": {
+          "version": "0.1.4",
+          "bundled": true,
+          "optional": true
+        },
+        "path-is-absolute": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "performance-now": {
+          "version": "0.2.0",
+          "bundled": true,
           "optional": true
         },
         "process-nextick-args": {
           "version": "1.0.7",
-          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
-          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+          "bundled": true
         },
         "punycode": {
           "version": "1.4.1",
-          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "bundled": true,
           "optional": true
         },
         "qs": {
-          "version": "6.3.1",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz",
-          "integrity": "sha1-kYwLO802Z5dyuvE1say0wWUe150=",
+          "version": "6.4.0",
+          "bundled": true,
           "optional": true
         },
         "rc": {
-          "version": "1.1.7",
-          "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
-          "integrity": "sha1-xepWS7B6/5/TpbMukGwdOmWUD+o=",
+          "version": "1.2.1",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "minimist": {
               "version": "1.2.0",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "readable-stream": {
-          "version": "2.2.2",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz",
-          "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=",
-          "optional": true
+          "version": "2.2.9",
+          "bundled": true
         },
         "request": {
-          "version": "2.79.0",
-          "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
-          "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
+          "version": "2.81.0",
+          "bundled": true,
           "optional": true
         },
         "rimraf": {
-          "version": "2.5.4",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz",
-          "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ="
+          "version": "2.6.1",
+          "bundled": true
+        },
+        "safe-buffer": {
+          "version": "5.0.1",
+          "bundled": true
         },
         "semver": {
           "version": "5.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
-          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+          "bundled": true,
           "optional": true
         },
         "set-blocking": {
           "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-          "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+          "bundled": true,
           "optional": true
         },
         "signal-exit": {
           "version": "3.0.2",
-          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
-          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+          "bundled": true,
           "optional": true
         },
         "sntp": {
           "version": "1.0.9",
-          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
-          "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+          "bundled": true,
           "optional": true
         },
         "sshpk": {
-          "version": "1.10.2",
-          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz",
-          "integrity": "sha1-1agEziJpVRVjjnmNviMnPeBwpfo=",
+          "version": "1.13.0",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+          "version": "1.0.1",
+          "bundled": true
         },
         "string-width": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M="
+          "bundled": true
         },
         "stringstream": {
           "version": "0.0.5",
-          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
-          "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
+          "bundled": true,
           "optional": true
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8="
+          "bundled": true
         },
         "strip-json-comments": {
           "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
-          "optional": true
-        },
-        "supports-color": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "bundled": true,
           "optional": true
         },
         "tar": {
           "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
-          "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE="
+          "bundled": true
         },
         "tar-pack": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz",
-          "integrity": "sha1-MJMYFkGPVa/E0hd1r91nIM7kXa4=",
-          "optional": true,
-          "dependencies": {
-            "once": {
-              "version": "1.3.3",
-              "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
-              "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
-              "optional": true
-            },
-            "readable-stream": {
-              "version": "2.1.5",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz",
-              "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=",
-              "optional": true
-            }
-          }
+          "version": "3.4.0",
+          "bundled": true,
+          "optional": true
         },
         "tough-cookie": {
           "version": "2.3.2",
-          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
-          "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+          "bundled": true,
           "optional": true
         },
         "tunnel-agent": {
-          "version": "0.4.3",
-          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
-          "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
+          "version": "0.6.0",
+          "bundled": true,
           "optional": true
         },
         "tweetnacl": {
           "version": "0.14.5",
-          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
-          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+          "bundled": true,
           "optional": true
         },
         "uid-number": {
           "version": "0.0.6",
-          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
-          "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
+          "bundled": true,
           "optional": true
         },
         "util-deprecate": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+          "bundled": true
         },
         "uuid": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
-          "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=",
+          "bundled": true,
           "optional": true
         },
         "verror": {
           "version": "1.3.6",
-          "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
-          "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+          "bundled": true,
           "optional": true
         },
         "wide-align": {
-          "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz",
-          "integrity": "sha1-QO3egCpx/qHwcNo+YtzaLnrdlq0=",
+          "version": "1.1.2",
+          "bundled": true,
           "optional": true
         },
         "wrappy": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
-        },
-        "xtend": {
-          "version": "4.0.1",
-          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
-          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
-          "optional": true
+          "bundled": true
         }
       }
     },
@@ -2823,29 +2668,7 @@
     "inline-process-browser": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz",
-      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=",
-      "dependencies": {
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-        },
-        "readable-stream": {
-          "version": "1.0.34",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
-          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
-        },
-        "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
-        },
-        "through2": {
-          "version": "0.6.5",
-          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
-        }
-      }
+      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI="
     },
     "inquirer": {
       "version": "0.12.0",
@@ -3097,12 +2920,6 @@
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
     },
-    "jodid25519": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
-      "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
-      "optional": true
-    },
     "js-tokens": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
@@ -3112,7 +2929,15 @@
       "version": "3.8.4",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
       "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "esprima": {
+          "version": "3.1.3",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+          "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+          "dev": true
+        }
+      }
     },
     "jsbn": {
       "version": "0.1.1",
@@ -3173,11 +2998,6 @@
       "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz",
       "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=",
       "dependencies": {
-        "esprima-fb": {
-          "version": "3001.1.0-dev-harmony-fb",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
-          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
-        },
         "source-map": {
           "version": "0.1.31",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz",
@@ -3194,7 +3014,15 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz",
       "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "through2": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+          "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+          "dev": true
+        }
+      }
     },
     "latest-version": {
       "version": "2.0.0",
@@ -3225,11 +3053,6 @@
       "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
       "dev": true
     },
-    "loader-utils": {
-      "version": "0.2.17",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
-      "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g="
-    },
     "localforage": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.5.0.tgz",
@@ -3380,10 +3203,9 @@
       "dev": true
     },
     "lru-cache": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz",
-      "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==",
-      "dev": true
+      "version": "2.7.3",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
+      "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
     },
     "lru-queue": {
       "version": "0.1.0",
@@ -3506,9 +3328,9 @@
       "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
     },
     "mongodb": {
-      "version": "2.2.28",
-      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.28.tgz",
-      "integrity": "sha1-2P9FdUNm4Dlz+iWb9PEUR4WNplc=",
+      "version": "2.2.29",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.29.tgz",
+      "integrity": "sha512-MrQvIsN6zN80I4hdFo8w46w51cIqD2FJBGsUfApX9GmjXA1aCclEAJbOHaQWjCtabeWq57S3ECzqEKg/9bdBhA==",
       "dependencies": {
         "readable-stream": {
           "version": "2.2.7",
@@ -3518,9 +3340,9 @@
       }
     },
     "mongodb-core": {
-      "version": "2.1.12",
-      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.12.tgz",
-      "integrity": "sha1-FTEZJRG8Fu8WCsauDMRndv/YRR0="
+      "version": "2.1.13",
+      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.13.tgz",
+      "integrity": "sha512-mbcvqLLZwVcpTrsfBDY3hRNk2SDNJWOvKKxFJSc0pnUBhYojymBc/L0THfQsWwKJrkb2nIXSjfFll1mG/I5OqQ=="
     },
     "moniker": {
       "version": "0.1.2",
@@ -3578,11 +3400,6 @@
       "resolved": "https://registry.npmjs.org/nedb-core/-/nedb-core-3.0.6.tgz",
       "integrity": "sha1-4BrQ8iciF/UQkY2YY5MwPotMjTI=",
       "dependencies": {
-        "async": {
-          "version": "2.4.1",
-          "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
-          "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
-        },
         "mkdirp": {
           "version": "0.5.1",
           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
@@ -3857,7 +3674,8 @@
     "path-exists": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
-      "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s="
+      "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+      "dev": true
     },
     "path-is-absolute": {
       "version": "1.0.1",
@@ -3907,12 +3725,14 @@
     "pinkie": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "dev": true
     },
     "pinkie-promise": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o="
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "dev": true
     },
     "pkg-conf": {
       "version": "1.1.3",
@@ -3923,7 +3743,8 @@
     "pkg-dir": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
-      "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q="
+      "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+      "dev": true
     },
     "plur": {
       "version": "2.1.2",
@@ -3990,6 +3811,12 @@
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
           "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
           "dev": true
+        },
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+          "dev": true
         }
       }
     },
@@ -4080,9 +3907,28 @@
       "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM="
     },
     "randomatic": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz",
-      "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs="
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
+      "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
+      "dependencies": {
+        "is-number": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+          "dependencies": {
+            "kind-of": {
+              "version": "3.2.2",
+              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="
+            }
+          }
+        },
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc="
+        }
+      }
     },
     "range-parser": {
       "version": "1.2.0",
@@ -4127,9 +3973,9 @@
       "dev": true
     },
     "readable-stream": {
-      "version": "2.2.11",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz",
-      "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q=="
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.0.tgz",
+      "integrity": "sha512-c7KMXGd4b48nN3OJ1U9qOsn6pXNzf6kLd3kdZCkg2sxAcoiufInqF0XckwEnlrcwuaYwonlNK8GQUIOC/WC7sg=="
     },
     "readdirp": {
       "version": "2.1.0",
@@ -4252,9 +4098,9 @@
       "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA="
     },
     "require_optional": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.0.tgz",
-      "integrity": "sha1-UqhhN6hJco62ClVTNhf4+RT1mr8="
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g=="
     },
     "require-precompiled": {
       "version": "0.1.0",
@@ -4332,9 +4178,9 @@
       "integrity": "sha1-F4FZvuRXkawhYoruLau3SlLV0HI="
     },
     "safe-buffer": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
-      "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.0.tgz",
+      "integrity": "sha512-aSLEDudu6OoRr/2rU609gRmnYboRLxgDG1z9o2Q0os7236FwvcqIOO8r8U5JUEwivZOhDaKlFO4SbPTJYyBEyQ=="
     },
     "samsam": {
       "version": "1.1.2",
@@ -4398,6 +4244,11 @@
       "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
       "dev": true
     },
+    "sigmund": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+      "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
+    },
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -4449,7 +4300,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#2edeac4f048f6248b96e55534fe29d802e1b7bca"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#32411bde64ff3bbeaad281798fe6921c00185ea4"
     },
     "spawn-sync": {
       "version": "1.0.15",
@@ -4488,9 +4339,9 @@
       "dev": true
     },
     "sshpk": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz",
-      "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=",
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
+      "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
       "dependencies": {
         "assert-plus": {
           "version": "1.0.0",
@@ -4535,7 +4386,14 @@
     "string_decoder": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz",
-      "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk="
+      "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=",
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+          "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
+        }
+      }
     },
     "string-length": {
       "version": "1.0.1",
@@ -4589,6 +4447,12 @@
       "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
       "dev": true,
       "dependencies": {
+        "async": {
+          "version": "1.5.2",
+          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+          "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+          "dev": true
+        },
         "form-data": {
           "version": "1.0.0-rc4",
           "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
@@ -4645,21 +4509,11 @@
       "resolved": "https://registry.npmjs.org/tape/-/tape-3.6.1.tgz",
       "integrity": "sha1-SJPdU+KApfWMDOswwsDrs7zVHh8=",
       "dependencies": {
-        "deep-equal": {
-          "version": "0.2.2",
-          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz",
-          "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0="
-        },
         "glob": {
           "version": "3.2.11",
           "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
           "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0="
         },
-        "lru-cache": {
-          "version": "2.7.3",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
-          "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
-        },
         "minimatch": {
           "version": "0.3.0",
           "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
@@ -4685,10 +4539,26 @@
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
     },
     "through2": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
-      "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
-      "dev": true
+      "version": "0.6.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+      "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        }
+      }
     },
     "time-require": {
       "version": "0.1.2",
@@ -4863,29 +4733,7 @@
     "unreachable-branch-transform": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz",
-      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=",
-      "dependencies": {
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-        },
-        "readable-stream": {
-          "version": "1.0.34",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
-          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
-        },
-        "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
-        },
-        "through2": {
-          "version": "0.6.5",
-          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
-        }
-      }
+      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo="
     },
     "unzip-response": {
       "version": "1.0.2",
@@ -4935,9 +4783,9 @@
       "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg="
     },
     "uuid": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
-      "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE="
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+      "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
     },
     "v8flags": {
       "version": "2.1.1",
diff --git a/package.json b/package.json
index 6c07fbc5..de8a34a5 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,6 @@
   "dependencies": {
     "array-flatten": "^2.1.1",
     "babel-cli": "^6.22.2",
-    "babel-loader": "^6.2.10",
     "babel-runtime": "^6.22.0",
     "basic-auth-parser": "0.0.2",
     "binary-version-reader": "^0.5.0",
@@ -82,7 +81,6 @@
     "node-rsa": "^0.4.2",
     "nullthrows": "^1.0.0",
     "request": "*",
-    "rimraf": "^2.5.4",
     "rmfr": "^1.0.1",
     "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev",
     "tingodb": "git+https://github.com/Brewskey/tingodb.git",

From 2de2bb595a0d9de65b1b6746a12b026c5a40ffaf Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Tue, 20 Jun 2017 10:18:22 +0200
Subject: [PATCH 438/504] Disable Cache on AVA, to tests can be run without
 write permission to node_modules

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index de8a34a5..c48efe57 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
     "start:debug": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
     "start:prod": "node ./dist/main.js",
-    "test": "ava --serial",
+    "test": "ava --serial --no-cache",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",
     "watch": "babel ./src --out-dir ./dist --watch"

From e84024f309669c519afba36b3da748334b6f612b Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 20 Jun 2017 20:55:30 +0200
Subject: [PATCH 439/504] update MongoDb so it waits for connection ready.

---
 dist/repository/MongoDb.js | 622 ++++++++++++++++++++++---------------
 src/repository/MongoDb.js  |  61 +++-
 2 files changed, 428 insertions(+), 255 deletions(-)

diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index c7e7ac0e..ada7022f 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -4,6 +4,10 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
+var _promise = require('babel-runtime/core-js/promise');
+
+var _promise2 = _interopRequireDefault(_promise);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -28,298 +32,418 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
+var _events = require('events');
+
+var _events2 = _interopRequireDefault(_events);
+
 var _BaseMongoDb2 = require('./BaseMongoDb');
 
 var _BaseMongoDb3 = _interopRequireDefault(_BaseMongoDb2);
 
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
+var _mongodb = require('mongodb');
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var DB_READY_EVENT = 'dbReady';
+
 var MongoDb = function (_BaseMongoDb) {
   (0, _inherits3.default)(MongoDb, _BaseMongoDb);
 
-  function MongoDb(database) {
+  function MongoDb(url) {
     var _this2 = this;
 
+    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
     (0, _classCallCheck3.default)(this, MongoDb);
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (MongoDb.__proto__ || (0, _getPrototypeOf2.default)(MongoDb)).call(this));
 
-    _this.insertOne = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
-        return _regenerator2.default.wrap(function _callee2$(_context2) {
-          while (1) {
-            switch (_context2.prev = _context2.next) {
-              case 0:
-                _context2.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
-                    var insertResult;
-                    return _regenerator2.default.wrap(function _callee$(_context) {
-                      while (1) {
-                        switch (_context.prev = _context.next) {
-                          case 0:
-                            _context.next = 2;
-                            return collection.insertOne(entity);
-
-                          case 2:
-                            insertResult = _context.sent;
-                            return _context.abrupt('return', _this.__translateResultItem(insertResult.ops[0]));
-
-                          case 4:
-                          case 'end':
-                            return _context.stop();
-                        }
-                      }
-                    }, _callee, _this2);
-                  }));
+    _initialiseProps.call(_this);
 
-                  return function (_x3) {
-                    return _ref2.apply(this, arguments);
-                  };
-                }());
+    (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return _this._init(url, options);
 
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
+            case 2:
+              return _context.abrupt('return', _context.sent);
 
-              case 3:
-              case 'end':
-                return _context2.stop();
-            }
+            case 3:
+            case 'end':
+              return _context.stop();
           }
-        }, _callee2, _this2);
-      }));
-
-      return function (_x, _x2) {
-        return _ref.apply(this, arguments);
-      };
-    }();
-
-    _this.find = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
-        return _regenerator2.default.wrap(function _callee4$(_context4) {
-          while (1) {
-            switch (_context4.prev = _context4.next) {
-              case 0:
-                _context4.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
-                    var resultItems;
-                    return _regenerator2.default.wrap(function _callee3$(_context3) {
-                      while (1) {
-                        switch (_context3.prev = _context3.next) {
-                          case 0:
-                            _context3.next = 2;
-                            return collection.find(_this.__translateQuery(query), { timeout: false }).toArray();
-
-                          case 2:
-                            resultItems = _context3.sent;
-                            return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
-
-                          case 4:
-                          case 'end':
-                            return _context3.stop();
-                        }
+        }
+      }, _callee, _this2);
+    }))();
+    return _this;
+  }
+
+  return MongoDb;
+}(_BaseMongoDb3.default);
+
+var _initialiseProps = function _initialiseProps() {
+  var _this3 = this;
+
+  this._database = null;
+  this._statusEventEmitter = new _events2.default();
+
+  this.insertOne = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collectionName, entity) {
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              _context3.next = 2;
+              return _this3.__runForCollection(collectionName, function () {
+                var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collection) {
+                  var insertResult;
+                  return _regenerator2.default.wrap(function _callee2$(_context2) {
+                    while (1) {
+                      switch (_context2.prev = _context2.next) {
+                        case 0:
+                          _context2.next = 2;
+                          return collection.insertOne(entity);
+
+                        case 2:
+                          insertResult = _context2.sent;
+                          return _context2.abrupt('return', _this3.__translateResultItem(insertResult.ops[0]));
+
+                        case 4:
+                        case 'end':
+                          return _context2.stop();
                       }
-                    }, _callee3, _this2);
-                  }));
+                    }
+                  }, _callee2, _this3);
+                }));
 
-                  return function (_x6) {
-                    return _ref4.apply(this, arguments);
-                  };
-                }());
+                return function (_x4) {
+                  return _ref3.apply(this, arguments);
+                };
+              }());
 
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
+            case 2:
+              return _context3.abrupt('return', _context3.sent);
 
-              case 3:
-              case 'end':
-                return _context4.stop();
-            }
+            case 3:
+            case 'end':
+              return _context3.stop();
           }
-        }, _callee4, _this2);
-      }));
-
-      return function (_x4, _x5) {
-        return _ref3.apply(this, arguments);
-      };
-    }();
-
-    _this.findAndModify = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, updateQuery) {
-        return _regenerator2.default.wrap(function _callee6$(_context6) {
-          while (1) {
-            switch (_context6.prev = _context6.next) {
-              case 0:
-                _context6.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
-                    var modifyResult;
-                    return _regenerator2.default.wrap(function _callee5$(_context5) {
-                      while (1) {
-                        switch (_context5.prev = _context5.next) {
-                          case 0:
-                            _context5.next = 2;
-                            return collection.findAndModify(_this.__translateQuery(query), null, _this.__translateQuery(updateQuery), { new: true, upsert: true });
-
-                          case 2:
-                            modifyResult = _context5.sent;
-                            return _context5.abrupt('return', _this.__translateResultItem(modifyResult.value));
-
-                          case 4:
-                          case 'end':
-                            return _context5.stop();
-                        }
+        }
+      }, _callee3, _this3);
+    }));
+
+    return function (_x2, _x3) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.find = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collectionName, query) {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              _context5.next = 2;
+              return _this3.__runForCollection(collectionName, function () {
+                var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collection) {
+                  var resultItems;
+                  return _regenerator2.default.wrap(function _callee4$(_context4) {
+                    while (1) {
+                      switch (_context4.prev = _context4.next) {
+                        case 0:
+                          _context4.next = 2;
+                          return collection.find(_this3.__translateQuery(query), { timeout: false }).toArray();
+
+                        case 2:
+                          resultItems = _context4.sent;
+                          return _context4.abrupt('return', resultItems.map(_this3.__translateResultItem));
+
+                        case 4:
+                        case 'end':
+                          return _context4.stop();
                       }
-                    }, _callee5, _this2);
-                  }));
+                    }
+                  }, _callee4, _this3);
+                }));
 
-                  return function (_x10) {
-                    return _ref6.apply(this, arguments);
-                  };
-                }());
+                return function (_x7) {
+                  return _ref5.apply(this, arguments);
+                };
+              }());
 
-              case 2:
-                return _context6.abrupt('return', _context6.sent);
+            case 2:
+              return _context5.abrupt('return', _context5.sent);
 
-              case 3:
-              case 'end':
-                return _context6.stop();
-            }
+            case 3:
+            case 'end':
+              return _context5.stop();
           }
-        }, _callee6, _this2);
-      }));
-
-      return function (_x7, _x8, _x9) {
-        return _ref5.apply(this, arguments);
-      };
-    }();
-
-    _this.findOne = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
-        return _regenerator2.default.wrap(function _callee8$(_context8) {
-          while (1) {
-            switch (_context8.prev = _context8.next) {
-              case 0:
-                _context8.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
-                    var resultItem;
-                    return _regenerator2.default.wrap(function _callee7$(_context7) {
-                      while (1) {
-                        switch (_context7.prev = _context7.next) {
-                          case 0:
-                            _context7.next = 2;
-                            return collection.findOne(_this.__translateQuery(query));
-
-                          case 2:
-                            resultItem = _context7.sent;
-                            return _context7.abrupt('return', _this.__translateResultItem(resultItem));
-
-                          case 4:
-                          case 'end':
-                            return _context7.stop();
-                        }
+        }
+      }, _callee5, _this3);
+    }));
+
+    return function (_x5, _x6) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.findAndModify = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collectionName, query, updateQuery) {
+      return _regenerator2.default.wrap(function _callee7$(_context7) {
+        while (1) {
+          switch (_context7.prev = _context7.next) {
+            case 0:
+              _context7.next = 2;
+              return _this3.__runForCollection(collectionName, function () {
+                var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collection) {
+                  var modifyResult;
+                  return _regenerator2.default.wrap(function _callee6$(_context6) {
+                    while (1) {
+                      switch (_context6.prev = _context6.next) {
+                        case 0:
+                          _context6.next = 2;
+                          return collection.findAndModify(_this3.__translateQuery(query), null, _this3.__translateQuery(updateQuery), { new: true, upsert: true });
+
+                        case 2:
+                          modifyResult = _context6.sent;
+                          return _context6.abrupt('return', _this3.__translateResultItem(modifyResult.value));
+
+                        case 4:
+                        case 'end':
+                          return _context6.stop();
                       }
-                    }, _callee7, _this2);
-                  }));
+                    }
+                  }, _callee6, _this3);
+                }));
 
-                  return function (_x13) {
-                    return _ref8.apply(this, arguments);
-                  };
-                }());
+                return function (_x11) {
+                  return _ref7.apply(this, arguments);
+                };
+              }());
 
-              case 2:
-                return _context8.abrupt('return', _context8.sent);
+            case 2:
+              return _context7.abrupt('return', _context7.sent);
 
-              case 3:
-              case 'end':
-                return _context8.stop();
-            }
+            case 3:
+            case 'end':
+              return _context7.stop();
           }
-        }, _callee8, _this2);
-      }));
-
-      return function (_x11, _x12) {
-        return _ref7.apply(this, arguments);
-      };
-    }();
-
-    _this.remove = function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
-        return _regenerator2.default.wrap(function _callee10$(_context10) {
-          while (1) {
-            switch (_context10.prev = _context10.next) {
-              case 0:
-                _context10.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
-                    return _regenerator2.default.wrap(function _callee9$(_context9) {
-                      while (1) {
-                        switch (_context9.prev = _context9.next) {
-                          case 0:
-                            _context9.next = 2;
-                            return collection.remove(_this.__translateQuery(query));
-
-                          case 2:
-                            return _context9.abrupt('return', _context9.sent);
-
-                          case 3:
-                          case 'end':
-                            return _context9.stop();
-                        }
+        }
+      }, _callee7, _this3);
+    }));
+
+    return function (_x8, _x9, _x10) {
+      return _ref6.apply(this, arguments);
+    };
+  }();
+
+  this.findOne = function () {
+    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collectionName, query) {
+      return _regenerator2.default.wrap(function _callee9$(_context9) {
+        while (1) {
+          switch (_context9.prev = _context9.next) {
+            case 0:
+              _context9.next = 2;
+              return _this3.__runForCollection(collectionName, function () {
+                var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collection) {
+                  var resultItem;
+                  return _regenerator2.default.wrap(function _callee8$(_context8) {
+                    while (1) {
+                      switch (_context8.prev = _context8.next) {
+                        case 0:
+                          _context8.next = 2;
+                          return collection.findOne(_this3.__translateQuery(query));
+
+                        case 2:
+                          resultItem = _context8.sent;
+                          return _context8.abrupt('return', _this3.__translateResultItem(resultItem));
+
+                        case 4:
+                        case 'end':
+                          return _context8.stop();
                       }
-                    }, _callee9, _this2);
-                  }));
+                    }
+                  }, _callee8, _this3);
+                }));
 
-                  return function (_x16) {
-                    return _ref10.apply(this, arguments);
-                  };
-                }());
+                return function (_x14) {
+                  return _ref9.apply(this, arguments);
+                };
+              }());
 
-              case 2:
-                return _context10.abrupt('return', _context10.sent);
+            case 2:
+              return _context9.abrupt('return', _context9.sent);
 
-              case 3:
-              case 'end':
-                return _context10.stop();
-            }
+            case 3:
+            case 'end':
+              return _context9.stop();
           }
-        }, _callee10, _this2);
-      }));
-
-      return function (_x14, _x15) {
-        return _ref9.apply(this, arguments);
-      };
-    }();
-
-    _this.__runForCollection = function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
-        return _regenerator2.default.wrap(function _callee11$(_context11) {
-          while (1) {
-            switch (_context11.prev = _context11.next) {
-              case 0:
-                return _context11.abrupt('return', callback(_this._database.collection(collectionName)).catch(function (error) {
-                  return console.error(error);
+        }
+      }, _callee9, _this3);
+    }));
+
+    return function (_x12, _x13) {
+      return _ref8.apply(this, arguments);
+    };
+  }();
+
+  this.remove = function () {
+    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, query) {
+      return _regenerator2.default.wrap(function _callee11$(_context11) {
+        while (1) {
+          switch (_context11.prev = _context11.next) {
+            case 0:
+              _context11.next = 2;
+              return _this3.__runForCollection(collectionName, function () {
+                var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collection) {
+                  return _regenerator2.default.wrap(function _callee10$(_context10) {
+                    while (1) {
+                      switch (_context10.prev = _context10.next) {
+                        case 0:
+                          _context10.next = 2;
+                          return collection.remove(_this3.__translateQuery(query));
+
+                        case 2:
+                          return _context10.abrupt('return', _context10.sent);
+
+                        case 3:
+                        case 'end':
+                          return _context10.stop();
+                      }
+                    }
+                  }, _callee10, _this3);
                 }));
 
-              case 1:
-              case 'end':
-                return _context11.stop();
-            }
-          }
-        }, _callee11, _this2);
-      }));
+                return function (_x17) {
+                  return _ref11.apply(this, arguments);
+                };
+              }());
 
-      return function (_x17, _x18) {
-        return _ref11.apply(this, arguments);
-      };
-    }();
+            case 2:
+              return _context11.abrupt('return', _context11.sent);
 
-    _this._database = database;
-    return _this;
-  }
+            case 3:
+            case 'end':
+              return _context11.stop();
+          }
+        }
+      }, _callee11, _this3);
+    }));
+
+    return function (_x15, _x16) {
+      return _ref10.apply(this, arguments);
+    };
+  }();
+
+  this.__runForCollection = function () {
+    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(collectionName, callback) {
+      return _regenerator2.default.wrap(function _callee12$(_context12) {
+        while (1) {
+          switch (_context12.prev = _context12.next) {
+            case 0:
+              _context12.next = 2;
+              return _this3._isDbReady();
+
+            case 2:
+              if (_this3._database) {
+                _context12.next = 4;
+                break;
+              }
+
+              throw new Error('database is not initialized');
+
+            case 4:
+              return _context12.abrupt('return', callback(_this3._database.collection(collectionName)).catch(function (error) {
+                return _logger2.default.error(error);
+              }));
+
+            case 5:
+            case 'end':
+              return _context12.stop();
+          }
+        }
+      }, _callee12, _this3);
+    }));
+
+    return function (_x18, _x19) {
+      return _ref12.apply(this, arguments);
+    };
+  }();
+
+  this._init = function () {
+    var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(url, options) {
+      var database;
+      return _regenerator2.default.wrap(function _callee13$(_context13) {
+        while (1) {
+          switch (_context13.prev = _context13.next) {
+            case 0:
+              _context13.next = 2;
+              return _mongodb.MongoClient.connect(url, options);
+
+            case 2:
+              database = _context13.sent;
+
+
+              database.on('error', function (error) {
+                return _logger2.default.error('DB connection Error: ', error);
+              });
+
+              database.on('open', function () {
+                return _logger2.default.log('DB connected');
+              });
+
+              database.on('close', function (str) {
+                return _logger2.default.log('DB disconnected: ', str);
+              });
+
+              _this3._database = database;
+              _this3._statusEventEmitter.emit(DB_READY_EVENT);
+
+            case 8:
+            case 'end':
+              return _context13.stop();
+          }
+        }
+      }, _callee13, _this3);
+    }));
+
+    return function (_x20, _x21) {
+      return _ref13.apply(this, arguments);
+    };
+  }();
+
+  this._isDbReady = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14() {
+    return _regenerator2.default.wrap(function _callee14$(_context14) {
+      while (1) {
+        switch (_context14.prev = _context14.next) {
+          case 0:
+            if (!_this3._database) {
+              _context14.next = 2;
+              break;
+            }
 
-  return MongoDb;
-}(_BaseMongoDb3.default);
+            return _context14.abrupt('return', _promise2.default.resolve());
+
+          case 2:
+            return _context14.abrupt('return', new _promise2.default(function (resolve) {
+              _this3._statusEventEmitter.once(DB_READY_EVENT, function () {
+                return resolve();
+              });
+            }));
+
+          case 3:
+          case 'end':
+            return _context14.stop();
+        }
+      }
+    }, _callee14, _this3);
+  }));
+};
 
 exports.default = MongoDb;
\ No newline at end of file
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index a3b87934..7c1bc3c1 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -2,15 +2,21 @@
 
 import type { IBaseDatabase } from '../types';
 
+import EventEmitter from 'events';
 import BaseMongoDb from './BaseMongoDb';
+import Logger from '../lib/logger';
+import { MongoClient } from 'mongodb';
+
+const DB_READY_EVENT = 'dbReady';
 
 class MongoDb extends BaseMongoDb implements IBaseDatabase {
-  _database: Object;
+  _database: ?Object = null;
+  _statusEventEmitter = new EventEmitter();
 
-  constructor(database: Object) {
+  constructor(url: string, options?: Object = {}) {
     super();
 
-    this._database = database;
+    (async (): Promise => await this._init(url, options))();
   }
 
   insertOne = async (
@@ -81,9 +87,52 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
   __runForCollection = async (
     collectionName: string,
     callback: (collection: Object) => Promise<*>,
-  ): Promise<*> => callback(
-    this._database.collection(collectionName),
-  ).catch((error: Error): void => console.error(error));
+  ): Promise<*> => {
+    await this._isDbReady();
+    // hack for flow:
+    if (!this._database) {
+      throw new Error('database is not initialized');
+    }
+    return callback(
+      this._database.collection(collectionName),
+    ).catch((error: Error): void => Logger.error(error));
+  };
+
+  _init = async (url: string, options: Object): Promise => {
+    const database = await MongoClient.connect(url, options);
+
+    database.on(
+      'error',
+      (error: Error): void =>
+        Logger.error('DB connection Error: ', error),
+    );
+
+    database.on(
+      'open',
+      (): void => Logger.log('DB connected'),
+    );
+
+    database.on(
+      'close',
+      (str: string): void => Logger.log('DB disconnected: ', str),
+    );
+
+    this._database = database;
+    this._statusEventEmitter.emit(DB_READY_EVENT);
+  };
+
+  _isDbReady = async (): Promise => {
+    if (this._database) {
+      return Promise.resolve();
+    }
+
+    return new Promise((resolve: () => void) => {
+      this._statusEventEmitter.once(
+        DB_READY_EVENT,
+        (): void => resolve(),
+      );
+    });
+  };
 }
 
 export default MongoDb;

From 409599f1a8d3fe9fdb0b6d0d8474e9de42cd0f9a Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 20 Jun 2017 20:57:04 +0200
Subject: [PATCH 440/504] delete TingoDB

---
 dist/defaultBindings.js    |   1 -
 dist/repository/TingoDb.js | 343 ---------------
 dist/settings.js           |  10 +-
 package-lock.json          | 871 +++++++++++++++++++++----------------
 package.json               |   5 +-
 src/defaultBindings.js     |   1 -
 src/repository/TingoDb.js  | 102 -----
 src/settings.js            |   8 -
 src/types.js               |   6 +-
 test/setup/settings.js     |   8 -
 10 files changed, 506 insertions(+), 849 deletions(-)
 delete mode 100644 dist/repository/TingoDb.js
 delete mode 100644 src/repository/TingoDb.js

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 68ceff75..a1c5e118 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -121,7 +121,6 @@ exports.default = function (container, newSettings) {
 
   // settings
   container.bindValue('DATABASE_PATH', _settings2.default.DB_CONFIG.PATH);
-  container.bindValue('DATABASE_OPTIONS', _settings2.default.DB_CONFIG.OPTIONS);
   container.bindValue('DEVICE_DIRECTORY', _settings2.default.DEVICE_DIRECTORY);
   container.bindValue('FIRMWARE_DIRECTORY', _settings2.default.FIRMWARE_DIRECTORY);
   container.bindValue('SERVER_KEY_FILENAME', _settings2.default.SERVER_KEY_FILENAME);
diff --git a/dist/repository/TingoDb.js b/dist/repository/TingoDb.js
deleted file mode 100644
index 3fdac92d..00000000
--- a/dist/repository/TingoDb.js
+++ /dev/null
@@ -1,343 +0,0 @@
-'use strict';
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-
-var _regenerator = require('babel-runtime/regenerator');
-
-var _regenerator2 = _interopRequireDefault(_regenerator);
-
-var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
-
-var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
-
-var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
-
-var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
-
-var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
-
-var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
-
-var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
-
-var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
-
-var _inherits2 = require('babel-runtime/helpers/inherits');
-
-var _inherits3 = _interopRequireDefault(_inherits2);
-
-var _fs = require('fs');
-
-var _fs2 = _interopRequireDefault(_fs);
-
-var _mkdirp = require('mkdirp');
-
-var _mkdirp2 = _interopRequireDefault(_mkdirp);
-
-var _tingodb = require('tingodb');
-
-var _tingodb2 = _interopRequireDefault(_tingodb);
-
-var _promisify = require('../lib/promisify');
-
-var _BaseMongoDb2 = require('./BaseMongoDb');
-
-var _BaseMongoDb3 = _interopRequireDefault(_BaseMongoDb2);
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
-var TingoDb = function (_BaseMongoDb) {
-  (0, _inherits3.default)(TingoDb, _BaseMongoDb);
-
-  function TingoDb(path, options) {
-    var _this2 = this;
-
-    (0, _classCallCheck3.default)(this, TingoDb);
-
-    var _this = (0, _possibleConstructorReturn3.default)(this, (TingoDb.__proto__ || (0, _getPrototypeOf2.default)(TingoDb)).call(this));
-
-    _this.insertOne = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
-        return _regenerator2.default.wrap(function _callee2$(_context2) {
-          while (1) {
-            switch (_context2.prev = _context2.next) {
-              case 0:
-                _context2.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
-                    var insertResults;
-                    return _regenerator2.default.wrap(function _callee$(_context) {
-                      while (1) {
-                        switch (_context.prev = _context.next) {
-                          case 0:
-                            _context.next = 2;
-                            return (0, _promisify.promisify)(collection, 'insert', entity, { fullResult: true });
-
-                          case 2:
-                            insertResults = _context.sent;
-                            return _context.abrupt('return', _this.__translateResultItem(insertResults[0]));
-
-                          case 4:
-                          case 'end':
-                            return _context.stop();
-                        }
-                      }
-                    }, _callee, _this2);
-                  }));
-
-                  return function (_x3) {
-                    return _ref2.apply(this, arguments);
-                  };
-                }());
-
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
-
-              case 3:
-              case 'end':
-                return _context2.stop();
-            }
-          }
-        }, _callee2, _this2);
-      }));
-
-      return function (_x, _x2) {
-        return _ref.apply(this, arguments);
-      };
-    }();
-
-    _this.find = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
-        return _regenerator2.default.wrap(function _callee4$(_context4) {
-          while (1) {
-            switch (_context4.prev = _context4.next) {
-              case 0:
-                _context4.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
-                    var resultItems;
-                    return _regenerator2.default.wrap(function _callee3$(_context3) {
-                      while (1) {
-                        switch (_context3.prev = _context3.next) {
-                          case 0:
-                            _context3.next = 2;
-                            return (0, _promisify.promisify)(collection.find(query, { timeout: false }), 'toArray');
-
-                          case 2:
-                            resultItems = _context3.sent;
-                            return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
-
-                          case 4:
-                          case 'end':
-                            return _context3.stop();
-                        }
-                      }
-                    }, _callee3, _this2);
-                  }));
-
-                  return function (_x6) {
-                    return _ref4.apply(this, arguments);
-                  };
-                }());
-
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
-
-              case 3:
-              case 'end':
-                return _context4.stop();
-            }
-          }
-        }, _callee4, _this2);
-      }));
-
-      return function (_x4, _x5) {
-        return _ref3.apply(this, arguments);
-      };
-    }();
-
-    _this.findAndModify = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, updateQuery) {
-        return _regenerator2.default.wrap(function _callee6$(_context6) {
-          while (1) {
-            switch (_context6.prev = _context6.next) {
-              case 0:
-                _context6.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
-                    var modifiedItem;
-                    return _regenerator2.default.wrap(function _callee5$(_context5) {
-                      while (1) {
-                        switch (_context5.prev = _context5.next) {
-                          case 0:
-                            _context5.next = 2;
-                            return (0, _promisify.promisify)(collection, 'findAndModify', query, null, updateQuery, { new: true, upsert: true });
-
-                          case 2:
-                            modifiedItem = _context5.sent;
-                            return _context5.abrupt('return', _this.__translateResultItem(modifiedItem));
-
-                          case 4:
-                          case 'end':
-                            return _context5.stop();
-                        }
-                      }
-                    }, _callee5, _this2);
-                  }));
-
-                  return function (_x10) {
-                    return _ref6.apply(this, arguments);
-                  };
-                }());
-
-              case 2:
-                return _context6.abrupt('return', _context6.sent);
-
-              case 3:
-              case 'end':
-                return _context6.stop();
-            }
-          }
-        }, _callee6, _this2);
-      }));
-
-      return function (_x7, _x8, _x9) {
-        return _ref5.apply(this, arguments);
-      };
-    }();
-
-    _this.findOne = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
-        return _regenerator2.default.wrap(function _callee8$(_context8) {
-          while (1) {
-            switch (_context8.prev = _context8.next) {
-              case 0:
-                _context8.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
-                    var resultItem;
-                    return _regenerator2.default.wrap(function _callee7$(_context7) {
-                      while (1) {
-                        switch (_context7.prev = _context7.next) {
-                          case 0:
-                            _context7.next = 2;
-                            return (0, _promisify.promisify)(collection, 'findOne', query);
-
-                          case 2:
-                            resultItem = _context7.sent;
-                            return _context7.abrupt('return', _this.__translateResultItem(resultItem));
-
-                          case 4:
-                          case 'end':
-                            return _context7.stop();
-                        }
-                      }
-                    }, _callee7, _this2);
-                  }));
-
-                  return function (_x13) {
-                    return _ref8.apply(this, arguments);
-                  };
-                }());
-
-              case 2:
-                return _context8.abrupt('return', _context8.sent);
-
-              case 3:
-              case 'end':
-                return _context8.stop();
-            }
-          }
-        }, _callee8, _this2);
-      }));
-
-      return function (_x11, _x12) {
-        return _ref7.apply(this, arguments);
-      };
-    }();
-
-    _this.remove = function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
-        return _regenerator2.default.wrap(function _callee10$(_context10) {
-          while (1) {
-            switch (_context10.prev = _context10.next) {
-              case 0:
-                _context10.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
-                    return _regenerator2.default.wrap(function _callee9$(_context9) {
-                      while (1) {
-                        switch (_context9.prev = _context9.next) {
-                          case 0:
-                            _context9.next = 2;
-                            return (0, _promisify.promisify)(collection, 'remove', query);
-
-                          case 2:
-                            return _context9.abrupt('return', _context9.sent);
-
-                          case 3:
-                          case 'end':
-                            return _context9.stop();
-                        }
-                      }
-                    }, _callee9, _this2);
-                  }));
-
-                  return function (_x16) {
-                    return _ref10.apply(this, arguments);
-                  };
-                }());
-
-              case 2:
-                return _context10.abrupt('return', _context10.sent);
-
-              case 3:
-              case 'end':
-                return _context10.stop();
-            }
-          }
-        }, _callee10, _this2);
-      }));
-
-      return function (_x14, _x15) {
-        return _ref9.apply(this, arguments);
-      };
-    }();
-
-    _this.__runForCollection = function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
-        return _regenerator2.default.wrap(function _callee11$(_context11) {
-          while (1) {
-            switch (_context11.prev = _context11.next) {
-              case 0:
-                return _context11.abrupt('return', callback(_this._database.collection(collectionName)));
-
-              case 1:
-              case 'end':
-                return _context11.stop();
-            }
-          }
-        }, _callee11, _this2);
-      }));
-
-      return function (_x17, _x18) {
-        return _ref11.apply(this, arguments);
-      };
-    }();
-
-    var Db = (0, _tingodb2.default)(options).Db;
-
-    if (!_fs2.default.existsSync(path)) {
-      _mkdirp2.default.sync(path);
-    }
-
-    _this._database = new Db(path, {});
-    return _this;
-  }
-
-  return TingoDb;
-}(_BaseMongoDb3.default);
-
-exports.default = TingoDb;
\ No newline at end of file
diff --git a/dist/settings.js b/dist/settings.js
index 790b4ebc..9e5bc38a 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -35,15 +35,7 @@ exports.default = {
     USE_SSL: false
   },
   DB_CONFIG: {
-    OPTIONS: {
-      cacheMaxObjSize: 1024,
-      cacheSize: 1000,
-      memStore: false,
-      nativeObjectID: true,
-      searchInArray: true
-    },
-    PATH: _path2.default.join(__dirname, '../data/db'),
-    URL: null
+    PATH: _path2.default.join(__dirname, '../data/db')
   },
   SHOW_VERBOSE_DEVICE_LOGS: false,
 
diff --git a/package-lock.json b/package-lock.json
index d540863a..2aa0c9a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,9 +14,10 @@
       "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo="
     },
     "acorn": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
-      "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
+      "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
+      "dev": true
     },
     "acorn-jsx": {
       "version": "3.0.1",
@@ -179,9 +180,10 @@
       "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI="
     },
     "async": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
-      "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+      "dev": true
     },
     "async-each": {
       "version": "1.0.1",
@@ -246,9 +248,9 @@
       "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ="
     },
     "babel-core": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz",
-      "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk="
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz",
+      "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM="
     },
     "babel-eslint": {
       "version": "7.2.3",
@@ -257,9 +259,9 @@
       "dev": true
     },
     "babel-generator": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz",
-      "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw="
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz",
+      "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc="
     },
     "babel-helper-bindify-decorators": {
       "version": "6.24.1",
@@ -739,29 +741,29 @@
       "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs="
     },
     "babel-template": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
-      "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE="
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz",
+      "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM="
     },
     "babel-traverse": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz",
-      "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE="
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz",
+      "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU="
     },
     "babel-types": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
-      "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4="
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz",
+      "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU="
     },
     "babylon": {
-      "version": "6.17.4",
-      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz",
-      "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw=="
+      "version": "6.17.2",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz",
+      "integrity": "sha1-IB0l71+JLEG65JSIsI2w3Udun1w="
     },
     "balanced-match": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+      "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
     },
     "base62": {
       "version": "0.1.1",
@@ -829,9 +831,9 @@
       "dev": true
     },
     "brace-expansion": {
-      "version": "1.1.8",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
-      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI="
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
+      "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k="
     },
     "braces": {
       "version": "1.8.5",
@@ -902,15 +904,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz",
       "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=",
-      "dev": true,
-      "dependencies": {
-        "deep-equal": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
-          "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
-          "dev": true
-        }
-      }
+      "dev": true
     },
     "call-signature": {
       "version": "0.0.2",
@@ -1199,15 +1193,7 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
       "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
-          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
-          "dev": true
-        }
-      }
+      "dev": true
     },
     "cryptiles": {
       "version": "2.0.5",
@@ -1267,9 +1253,10 @@
       }
     },
     "deep-equal": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz",
-      "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0="
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+      "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+      "dev": true
     },
     "deep-extend": {
       "version": "0.4.2",
@@ -1430,7 +1417,14 @@
     "es3ify": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz",
-      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E="
+      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=",
+      "dependencies": {
+        "esprima-fb": {
+          "version": "3001.1.0-dev-harmony-fb",
+          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
+          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
+        }
+      }
     },
     "es5-ext": {
       "version": "0.10.23",
@@ -1614,20 +1608,13 @@
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
       "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
-      "dev": true,
-      "dependencies": {
-        "acorn": {
-          "version": "5.0.3",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
-          "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
-          "dev": true
-        }
-      }
+      "dev": true
     },
-    "esprima-fb": {
-      "version": "3001.1.0-dev-harmony-fb",
-      "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
-      "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
+    "esprima": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+      "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+      "dev": true
     },
     "espurify": {
       "version": "1.7.0",
@@ -1740,6 +1727,11 @@
       "resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
       "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=",
       "dependencies": {
+        "acorn": {
+          "version": "1.2.2",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
+          "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
+        },
         "isarray": {
           "version": "0.0.1",
           "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@@ -1892,545 +1884,696 @@
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
     "fsevents": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
-      "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz",
+      "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=",
       "optional": true,
       "dependencies": {
         "abbrev": {
           "version": "1.1.0",
-          "bundled": true,
-          "optional": true
-        },
-        "ajv": {
-          "version": "4.11.8",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
+          "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
           "optional": true
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "optional": true
         },
         "aproba": {
           "version": "1.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
+          "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=",
           "optional": true
         },
         "are-we-there-yet": {
-          "version": "1.1.4",
-          "bundled": true,
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
+          "integrity": "sha1-gORw6VoIR5T+GJkmLFZnxuiN4bM=",
           "optional": true
         },
         "asn1": {
           "version": "0.2.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+          "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
           "optional": true
         },
         "assert-plus": {
           "version": "0.2.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
           "optional": true
         },
         "asynckit": {
           "version": "0.4.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+          "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
           "optional": true
         },
         "aws-sign2": {
           "version": "0.6.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
           "optional": true
         },
         "aws4": {
           "version": "1.6.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
+          "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
           "optional": true
         },
         "balanced-match": {
           "version": "0.4.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
+          "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
         },
         "bcrypt-pbkdf": {
           "version": "1.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
+          "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
           "optional": true
         },
         "block-stream": {
           "version": "0.0.9",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+          "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo="
         },
         "boom": {
           "version": "2.10.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+          "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
         },
         "brace-expansion": {
-          "version": "1.1.7",
-          "bundled": true
+          "version": "1.1.6",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
+          "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk="
         },
         "buffer-shims": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
+          "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
         },
         "caseless": {
-          "version": "0.12.0",
-          "bundled": true,
+          "version": "0.11.0",
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
+          "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
           "optional": true
         },
-        "co": {
-          "version": "4.6.0",
-          "bundled": true,
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
           "optional": true
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
         },
         "combined-stream": {
           "version": "1.0.5",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+          "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
+        },
+        "commander": {
+          "version": "2.9.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+          "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+          "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
         },
         "core-util-is": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
         },
         "cryptiles": {
           "version": "2.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+          "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
           "optional": true
         },
         "dashdash": {
           "version": "1.14.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+          "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
               "optional": true
             }
           }
         },
         "debug": {
-          "version": "2.6.8",
-          "bundled": true,
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
           "optional": true
         },
         "deep-extend": {
-          "version": "0.4.2",
-          "bundled": true,
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
+          "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=",
           "optional": true
         },
         "delayed-stream": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+          "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
         },
         "delegates": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
           "optional": true
         },
         "ecc-jsbn": {
           "version": "0.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+          "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+          "optional": true
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
           "optional": true
         },
         "extend": {
-          "version": "3.0.1",
-          "bundled": true,
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
+          "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=",
           "optional": true
         },
         "extsprintf": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+          "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
         },
         "forever-agent": {
           "version": "0.6.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+          "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
           "optional": true
         },
         "form-data": {
-          "version": "2.1.4",
-          "bundled": true,
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz",
+          "integrity": "sha1-icNTQAi5fq2ky7FX1Y9vXfAl6uQ=",
           "optional": true
         },
         "fs.realpath": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
         },
         "fstream": {
-          "version": "1.0.11",
-          "bundled": true
+          "version": "1.0.10",
+          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz",
+          "integrity": "sha1-YE6Kkv4m/9n2+uMDmdSYThqyKCI="
         },
         "fstream-ignore": {
           "version": "1.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
+          "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
           "optional": true
         },
         "gauge": {
-          "version": "2.7.4",
-          "bundled": true,
+          "version": "2.7.3",
+          "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz",
+          "integrity": "sha1-HCOFX5YvF7OtPQ3HRD8wRULt/gk=",
+          "optional": true
+        },
+        "generate-function": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+          "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
+          "optional": true
+        },
+        "generate-object-property": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+          "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
           "optional": true
         },
         "getpass": {
-          "version": "0.1.7",
-          "bundled": true,
+          "version": "0.1.6",
+          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
+          "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=",
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
               "optional": true
             }
           }
         },
         "glob": {
-          "version": "7.1.2",
-          "bundled": true
+          "version": "7.1.1",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
+          "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg="
         },
         "graceful-fs": {
           "version": "4.1.11",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
         },
-        "har-schema": {
-          "version": "1.0.5",
-          "bundled": true,
+        "graceful-readlink": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+          "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
           "optional": true
         },
         "har-validator": {
-          "version": "4.2.1",
-          "bundled": true,
+          "version": "2.0.6",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
+          "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
+          "optional": true
+        },
+        "has-ansi": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
           "optional": true
         },
         "has-unicode": {
           "version": "2.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
           "optional": true
         },
         "hawk": {
           "version": "3.1.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+          "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
           "optional": true
         },
         "hoek": {
           "version": "2.16.3",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+          "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
         },
         "http-signature": {
           "version": "1.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
           "optional": true
         },
         "inflight": {
           "version": "1.0.6",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
         },
         "ini": {
           "version": "1.3.4",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+          "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
           "optional": true
         },
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs="
+        },
+        "is-my-json-valid": {
+          "version": "2.15.0",
+          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz",
+          "integrity": "sha1-k27do8o8IR/ZjzstPgjaQ/eykVs=",
+          "optional": true
+        },
+        "is-property": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+          "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+          "optional": true
         },
         "is-typedarray": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+          "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
           "optional": true
         },
         "isarray": {
           "version": "1.0.0",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
         },
         "isstream": {
           "version": "0.1.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
           "optional": true
         },
         "jodid25519": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+          "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
           "optional": true
         },
         "jsbn": {
           "version": "0.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+          "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
           "optional": true
         },
         "json-schema": {
           "version": "0.2.3",
-          "bundled": true,
-          "optional": true
-        },
-        "json-stable-stringify": {
-          "version": "1.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+          "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
           "optional": true
         },
         "json-stringify-safe": {
           "version": "5.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+          "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
           "optional": true
         },
-        "jsonify": {
-          "version": "0.0.0",
-          "bundled": true,
+        "jsonpointer": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+          "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
           "optional": true
         },
         "jsprim": {
-          "version": "1.4.0",
-          "bundled": true,
-          "optional": true,
-          "dependencies": {
-            "assert-plus": {
-              "version": "1.0.0",
-              "bundled": true,
-              "optional": true
-            }
-          }
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
+          "integrity": "sha1-KnJW9wQSop7jZwqspiWZTE3P8lI=",
+          "optional": true
         },
         "mime-db": {
-          "version": "1.27.0",
-          "bundled": true
+          "version": "1.26.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz",
+          "integrity": "sha1-6v/NDk/Gk1z4E02iRuLmw1MFrf8="
         },
         "mime-types": {
-          "version": "2.1.15",
-          "bundled": true
+          "version": "2.1.14",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz",
+          "integrity": "sha1-9+99l1g/yvO30oK2+LVnnaselO4="
         },
         "minimatch": {
-          "version": "3.0.4",
-          "bundled": true
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
+          "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q="
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
         },
         "mkdirp": {
           "version": "0.5.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
         },
         "ms": {
-          "version": "2.0.0",
-          "bundled": true,
+          "version": "0.7.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
           "optional": true
         },
         "node-pre-gyp": {
-          "version": "0.6.36",
-          "bundled": true,
+          "version": "0.6.33",
+          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz",
+          "integrity": "sha1-ZArFUZj2qSWXLgwWxKwmoDTV7Mk=",
           "optional": true
         },
         "nopt": {
-          "version": "4.0.1",
-          "bundled": true,
+          "version": "3.0.6",
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+          "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
           "optional": true
         },
         "npmlog": {
-          "version": "4.1.0",
-          "bundled": true,
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz",
+          "integrity": "sha1-0DlQ4OeM4VJ7om0qdZLpNIrD518=",
           "optional": true
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
         },
         "oauth-sign": {
           "version": "0.8.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+          "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
           "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
           "optional": true
         },
         "once": {
           "version": "1.4.0",
-          "bundled": true
-        },
-        "os-homedir": {
-          "version": "1.0.2",
-          "bundled": true,
-          "optional": true
-        },
-        "os-tmpdir": {
-          "version": "1.0.2",
-          "bundled": true,
-          "optional": true
-        },
-        "osenv": {
-          "version": "0.1.4",
-          "bundled": true,
-          "optional": true
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
         },
         "path-is-absolute": {
           "version": "1.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+          "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
-        "performance-now": {
-          "version": "0.2.0",
-          "bundled": true,
+        "pinkie": {
+          "version": "2.0.4",
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+          "optional": true
+        },
+        "pinkie-promise": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
           "optional": true
         },
         "process-nextick-args": {
           "version": "1.0.7",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
         },
         "punycode": {
           "version": "1.4.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
           "optional": true
         },
         "qs": {
-          "version": "6.4.0",
-          "bundled": true,
+          "version": "6.3.1",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz",
+          "integrity": "sha1-kYwLO802Z5dyuvE1say0wWUe150=",
           "optional": true
         },
         "rc": {
-          "version": "1.2.1",
-          "bundled": true,
+          "version": "1.1.7",
+          "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
+          "integrity": "sha1-xepWS7B6/5/TpbMukGwdOmWUD+o=",
           "optional": true,
           "dependencies": {
             "minimist": {
               "version": "1.2.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
               "optional": true
             }
           }
         },
         "readable-stream": {
-          "version": "2.2.9",
-          "bundled": true
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz",
+          "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=",
+          "optional": true
         },
         "request": {
-          "version": "2.81.0",
-          "bundled": true,
+          "version": "2.79.0",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
+          "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
           "optional": true
         },
         "rimraf": {
-          "version": "2.6.1",
-          "bundled": true
-        },
-        "safe-buffer": {
-          "version": "5.0.1",
-          "bundled": true
+          "version": "2.5.4",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz",
+          "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ="
         },
         "semver": {
           "version": "5.3.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
           "optional": true
         },
         "set-blocking": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+          "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
           "optional": true
         },
         "signal-exit": {
           "version": "3.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
           "optional": true
         },
         "sntp": {
           "version": "1.0.9",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+          "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
           "optional": true
         },
         "sshpk": {
-          "version": "1.13.0",
-          "bundled": true,
+          "version": "1.10.2",
+          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz",
+          "integrity": "sha1-1agEziJpVRVjjnmNviMnPeBwpfo=",
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
               "optional": true
             }
           }
         },
         "string_decoder": {
-          "version": "1.0.1",
-          "bundled": true
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
         },
         "string-width": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M="
         },
         "stringstream": {
           "version": "0.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+          "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
           "optional": true
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8="
         },
         "strip-json-comments": {
           "version": "2.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+          "optional": true
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
           "optional": true
         },
         "tar": {
           "version": "2.2.1",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+          "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE="
         },
         "tar-pack": {
-          "version": "3.4.0",
-          "bundled": true,
-          "optional": true
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz",
+          "integrity": "sha1-MJMYFkGPVa/E0hd1r91nIM7kXa4=",
+          "optional": true,
+          "dependencies": {
+            "once": {
+              "version": "1.3.3",
+              "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+              "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
+              "optional": true
+            },
+            "readable-stream": {
+              "version": "2.1.5",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz",
+              "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=",
+              "optional": true
+            }
+          }
         },
         "tough-cookie": {
           "version": "2.3.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
+          "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
           "optional": true
         },
         "tunnel-agent": {
-          "version": "0.6.0",
-          "bundled": true,
+          "version": "0.4.3",
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
+          "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
           "optional": true
         },
         "tweetnacl": {
           "version": "0.14.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
           "optional": true
         },
         "uid-number": {
           "version": "0.0.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
+          "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
           "optional": true
         },
         "util-deprecate": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
         },
         "uuid": {
           "version": "3.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
+          "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=",
           "optional": true
         },
         "verror": {
           "version": "1.3.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+          "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
           "optional": true
         },
         "wide-align": {
-          "version": "1.1.2",
-          "bundled": true,
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz",
+          "integrity": "sha1-QO3egCpx/qHwcNo+YtzaLnrdlq0=",
           "optional": true
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+          "optional": true
         }
       }
     },
@@ -2668,7 +2811,29 @@
     "inline-process-browser": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz",
-      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI="
+      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        },
+        "through2": {
+          "version": "0.6.5",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
+        }
+      }
     },
     "inquirer": {
       "version": "0.12.0",
@@ -2920,6 +3085,12 @@
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
     },
+    "jodid25519": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+      "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+      "optional": true
+    },
     "js-tokens": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
@@ -2929,15 +3100,7 @@
       "version": "3.8.4",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
       "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
-      "dev": true,
-      "dependencies": {
-        "esprima": {
-          "version": "3.1.3",
-          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
-          "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
-          "dev": true
-        }
-      }
+      "dev": true
     },
     "jsbn": {
       "version": "0.1.1",
@@ -2998,6 +3161,11 @@
       "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz",
       "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=",
       "dependencies": {
+        "esprima-fb": {
+          "version": "3001.1.0-dev-harmony-fb",
+          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
+          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
+        },
         "source-map": {
           "version": "0.1.31",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz",
@@ -3014,15 +3182,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz",
       "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=",
-      "dev": true,
-      "dependencies": {
-        "through2": {
-          "version": "2.0.3",
-          "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
-          "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
-          "dev": true
-        }
-      }
+      "dev": true
     },
     "latest-version": {
       "version": "2.0.0",
@@ -3203,9 +3363,10 @@
       "dev": true
     },
     "lru-cache": {
-      "version": "2.7.3",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
-      "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz",
+      "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==",
+      "dev": true
     },
     "lru-queue": {
       "version": "0.1.0",
@@ -3328,9 +3489,9 @@
       "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
     },
     "mongodb": {
-      "version": "2.2.29",
-      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.29.tgz",
-      "integrity": "sha512-MrQvIsN6zN80I4hdFo8w46w51cIqD2FJBGsUfApX9GmjXA1aCclEAJbOHaQWjCtabeWq57S3ECzqEKg/9bdBhA==",
+      "version": "2.2.28",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.28.tgz",
+      "integrity": "sha1-2P9FdUNm4Dlz+iWb9PEUR4WNplc=",
       "dependencies": {
         "readable-stream": {
           "version": "2.2.7",
@@ -3340,9 +3501,9 @@
       }
     },
     "mongodb-core": {
-      "version": "2.1.13",
-      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.13.tgz",
-      "integrity": "sha512-mbcvqLLZwVcpTrsfBDY3hRNk2SDNJWOvKKxFJSc0pnUBhYojymBc/L0THfQsWwKJrkb2nIXSjfFll1mG/I5OqQ=="
+      "version": "2.1.12",
+      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.12.tgz",
+      "integrity": "sha1-FTEZJRG8Fu8WCsauDMRndv/YRR0="
     },
     "moniker": {
       "version": "0.1.2",
@@ -3400,6 +3561,11 @@
       "resolved": "https://registry.npmjs.org/nedb-core/-/nedb-core-3.0.6.tgz",
       "integrity": "sha1-4BrQ8iciF/UQkY2YY5MwPotMjTI=",
       "dependencies": {
+        "async": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
+          "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
+        },
         "mkdirp": {
           "version": "0.5.1",
           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
@@ -3811,12 +3977,6 @@
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
           "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
           "dev": true
-        },
-        "lru-cache": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
-          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
-          "dev": true
         }
       }
     },
@@ -3907,28 +4067,9 @@
       "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM="
     },
     "randomatic": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
-      "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
-      "dependencies": {
-        "is-number": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
-          "dependencies": {
-            "kind-of": {
-              "version": "3.2.2",
-              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="
-            }
-          }
-        },
-        "kind-of": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
-          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc="
-        }
-      }
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz",
+      "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs="
     },
     "range-parser": {
       "version": "1.2.0",
@@ -3973,9 +4114,9 @@
       "dev": true
     },
     "readable-stream": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.0.tgz",
-      "integrity": "sha512-c7KMXGd4b48nN3OJ1U9qOsn6pXNzf6kLd3kdZCkg2sxAcoiufInqF0XckwEnlrcwuaYwonlNK8GQUIOC/WC7sg=="
+      "version": "2.2.11",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz",
+      "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q=="
     },
     "readdirp": {
       "version": "2.1.0",
@@ -4098,9 +4239,9 @@
       "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA="
     },
     "require_optional": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
-      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g=="
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.0.tgz",
+      "integrity": "sha1-UqhhN6hJco62ClVTNhf4+RT1mr8="
     },
     "require-precompiled": {
       "version": "0.1.0",
@@ -4172,15 +4313,10 @@
       "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
       "dev": true
     },
-    "safe": {
-      "version": "0.3.9",
-      "resolved": "https://registry.npmjs.org/safe/-/safe-0.3.9.tgz",
-      "integrity": "sha1-F4FZvuRXkawhYoruLau3SlLV0HI="
-    },
     "safe-buffer": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.0.tgz",
-      "integrity": "sha512-aSLEDudu6OoRr/2rU609gRmnYboRLxgDG1z9o2Q0os7236FwvcqIOO8r8U5JUEwivZOhDaKlFO4SbPTJYyBEyQ=="
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
+      "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
     },
     "samsam": {
       "version": "1.1.2",
@@ -4339,9 +4475,9 @@
       "dev": true
     },
     "sshpk": {
-      "version": "1.13.1",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
-      "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz",
+      "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=",
       "dependencies": {
         "assert-plus": {
           "version": "1.0.0",
@@ -4386,14 +4522,7 @@
     "string_decoder": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz",
-      "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=",
-      "dependencies": {
-        "safe-buffer": {
-          "version": "5.0.1",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
-          "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
-        }
-      }
+      "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk="
     },
     "string-length": {
       "version": "1.0.1",
@@ -4447,12 +4576,6 @@
       "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
       "dev": true,
       "dependencies": {
-        "async": {
-          "version": "1.5.2",
-          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
-          "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
-          "dev": true
-        },
         "form-data": {
           "version": "1.0.0-rc4",
           "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
@@ -4509,11 +4632,21 @@
       "resolved": "https://registry.npmjs.org/tape/-/tape-3.6.1.tgz",
       "integrity": "sha1-SJPdU+KApfWMDOswwsDrs7zVHh8=",
       "dependencies": {
+        "deep-equal": {
+          "version": "0.2.2",
+          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz",
+          "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0="
+        },
         "glob": {
           "version": "3.2.11",
           "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
           "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0="
         },
+        "lru-cache": {
+          "version": "2.7.3",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
+          "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
+        },
         "minimatch": {
           "version": "0.3.0",
           "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
@@ -4539,26 +4672,10 @@
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
     },
     "through2": {
-      "version": "0.6.5",
-      "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-      "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
-      "dependencies": {
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-        },
-        "readable-stream": {
-          "version": "1.0.34",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
-          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
-        },
-        "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
-        }
-      }
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+      "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+      "dev": true
     },
     "time-require": {
       "version": "0.1.2",
@@ -4609,16 +4726,6 @@
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
       "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ="
     },
-    "tingodb": {
-      "version": "git+https://github.com/Brewskey/tingodb.git#4a743f88f422651a9338f8a06db61248ed4c8b49",
-      "dependencies": {
-        "lodash": {
-          "version": "4.11.2",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.11.2.tgz",
-          "integrity": "sha1-1rQzixEKWOIdrlzrz9u/0rxM2zs="
-        }
-      }
-    },
     "to-fast-properties": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
@@ -4733,7 +4840,29 @@
     "unreachable-branch-transform": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz",
-      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo="
+      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        },
+        "through2": {
+          "version": "0.6.5",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
+        }
+      }
     },
     "unzip-response": {
       "version": "1.0.2",
@@ -4783,9 +4912,9 @@
       "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg="
     },
     "uuid": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
-      "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
+      "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE="
     },
     "v8flags": {
       "version": "2.1.1",
diff --git a/package.json b/package.json
index de8a34a5..9547b381 100644
--- a/package.json
+++ b/package.json
@@ -31,8 +31,8 @@
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
     "lint": "eslint --fix --max-warnings 0 -- .",
-    "migrate-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
-    "migrate-to-tingo": "babel-node ./src/scripts/migrateFilesToDatabase tingo",
+    "migrate-files-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
+    "migrate-files-to-nedb": "babel-node ./src/scripts/migrateFilesToDatabase nedb",
     "prebuild": "npm run build:clean",
     "start:warn": "babel-node ./src/main.js --trace-warnings",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
@@ -83,7 +83,6 @@
     "request": "*",
     "rmfr": "^1.0.1",
     "spark-protocol": "git+https://github.com/Brewskey/spark-protocol.git#dev",
-    "tingodb": "git+https://github.com/Brewskey/tingodb.git",
     "uuid": "^3.0.1"
   },
   "devDependencies": {
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index e16daf62..ac9a73e0 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -45,7 +45,6 @@ export default (container: Container, newSettings: Settings) => {
 
   // settings
   container.bindValue('DATABASE_PATH', settings.DB_CONFIG.PATH);
-  container.bindValue('DATABASE_OPTIONS', settings.DB_CONFIG.OPTIONS);
   container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY);
   container.bindValue('FIRMWARE_DIRECTORY', settings.FIRMWARE_DIRECTORY);
   container.bindValue('SERVER_KEY_FILENAME', settings.SERVER_KEY_FILENAME);
diff --git a/src/repository/TingoDb.js b/src/repository/TingoDb.js
deleted file mode 100644
index 3fe69eb7..00000000
--- a/src/repository/TingoDb.js
+++ /dev/null
@@ -1,102 +0,0 @@
-// @flow
-
-import type { IBaseDatabase } from '../types';
-
-import fs from 'fs';
-import mkdirp from 'mkdirp';
-import tingoDb from 'tingodb';
-import { promisify } from '../lib/promisify';
-import BaseMongoDb from './BaseMongoDb';
-
-class TingoDb extends BaseMongoDb implements IBaseDatabase {
-  _database: Object;
-
-  constructor(path: string, options: Object) {
-    super();
-
-    const Db = tingoDb(options).Db;
-
-    if (!fs.existsSync(path)) {
-      mkdirp.sync(path);
-    }
-
-    this._database = new Db(path, {});
-  }
-
-  insertOne = async (
-    collectionName: string,
-    entity: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const insertResults = await promisify(
-        collection,
-        'insert',
-        entity,
-        { fullResult: true },
-      );
-
-      return this.__translateResultItem(insertResults[0]);
-    },
-  );
-
-  find = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItems = await promisify(
-        collection.find(query, { timeout: false }),
-        'toArray',
-      );
-      return resultItems.map(this.__translateResultItem);
-    },
-  );
-
-  findAndModify = async (
-    collectionName: string,
-    query: Object,
-    updateQuery: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const modifiedItem = await promisify(
-        collection,
-        'findAndModify',
-        query,
-        null,
-        updateQuery,
-        { new: true, upsert: true },
-      );
-      return this.__translateResultItem(modifiedItem);
-    },
-  );
-
-  findOne = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItem = await promisify(collection, 'findOne', query);
-      return this.__translateResultItem(resultItem);
-    },
-  );
-
-  remove = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> =>
-      await promisify(collection, 'remove', query),
-  );
-
-  __runForCollection = async (
-    collectionName: string,
-    callback: (collection: Object) => Promise<*>,
-  ): Promise<*> => callback(this._database.collection(collectionName));
-}
-
-export default TingoDb;
diff --git a/src/settings.js b/src/settings.js
index 351d0b8f..2c1f08ea 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -46,15 +46,7 @@ export default {
     USE_SSL: false,
   },
   DB_CONFIG: {
-    OPTIONS: {
-      cacheMaxObjSize: 1024,
-      cacheSize: 1000,
-      memStore: false,
-      nativeObjectID: true,
-      searchInArray: true,
-    },
     PATH: path.join(__dirname, '../data/db'),
-    URL: null,
   },
   SHOW_VERBOSE_DEVICE_LOGS: false,
 
diff --git a/src/types.js b/src/types.js
index 6c5fe160..1acf4ecb 100644
--- a/src/types.js
+++ b/src/types.js
@@ -141,9 +141,9 @@ export type Settings = {
   BUILD_DIRECTORY: string,
   CRYPTO_ALGORITHM: string,
   DB_CONFIG: {
-    OPTIONS: Object,
-    PATH: ?string,
-    URL: ?string,
+    OPTIONS?: Object,
+    PATH?: string,
+    URL?: string,
   },
   DEFAULT_ADMIN_PASSWORD: string,
   DEFAULT_ADMIN_USERNAME: string,
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 9490cc47..5be69bbb 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -34,15 +34,7 @@ export default {
     PORT: 5683,
   },
   DB_CONFIG: {
-    OPTIONS: {
-      cacheMaxObjSize: 1024,
-      cacheSize: 1000,
-      memStore: true,
-      nativeObjectID: true,
-      searchInArray: true,
-    },
     PATH: path.join(__dirname, '../__test_data__/db'),
-    URL: null,
   },
   WEBHOOK_TEMPLATE_PARAMETERS: {},
 };

From ae37eea7bf2844b2fc2eed4bc51e5c7f519b5fec Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 20 Jun 2017 20:58:19 +0200
Subject: [PATCH 441/504] update migrate-script

---
 dist/scripts/migrateFilesToDatabase.js | 40 ++++++++++++++++----------
 src/scripts/migrateFilesToDatabase.js  | 29 +++++++++++++------
 2 files changed, 45 insertions(+), 24 deletions(-)

diff --git a/dist/scripts/migrateFilesToDatabase.js b/dist/scripts/migrateFilesToDatabase.js
index 8f9f4f75..98fce9f9 100644
--- a/dist/scripts/migrateFilesToDatabase.js
+++ b/dist/scripts/migrateFilesToDatabase.js
@@ -34,9 +34,9 @@ var _settings2 = _interopRequireDefault(_settings);
 
 var _mongodb = require('mongodb');
 
-var _TingoDb = require('../repository/TingoDb');
+var _NeDb = require('../repository/NeDb');
 
-var _TingoDb2 = _interopRequireDefault(_TingoDb);
+var _NeDb2 = _interopRequireDefault(_NeDb);
 
 var _MongoDb = require('../repository/MongoDb');
 
@@ -48,35 +48,45 @@ var DATABASE_TYPE = process.argv[2];
 
 var setupDatabase = function () {
   var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
-    var mongoConnection;
     return _regenerator2.default.wrap(function _callee$(_context) {
       while (1) {
         switch (_context.prev = _context.next) {
           case 0:
-            if (!(DATABASE_TYPE === 'tingo')) {
-              _context.next = 2;
+            if (!(DATABASE_TYPE === 'mongo')) {
+              _context.next = 4;
               break;
             }
 
-            return _context.abrupt('return', new _TingoDb2.default(_settings2.default.DB_CONFIG.PATH, _settings2.default.DB_CONFIG.OPTIONS));
+            if (_settings2.default.DB_CONFIG.URL) {
+              _context.next = 3;
+              break;
+            }
 
-          case 2:
-            if (!(DATABASE_TYPE === 'mongo')) {
-              _context.next = 7;
+            throw new Error('You should provide mongoDB connection URL' + 'in settings.DB_CONFIG.URL');
+
+          case 3:
+            return _context.abrupt('return', new _MongoDb2.default(_settings2.default.DB_CONFIG.URL, _settings2.default.DB_CONFIG.OPTIONS));
+
+          case 4:
+            if (!(DATABASE_TYPE === 'nedb')) {
+              _context.next = 8;
               break;
             }
 
-            _context.next = 5;
-            return _mongodb.MongoClient.connect(_settings2.default.DB_CONFIG.URL);
+            if (_settings2.default.DB_CONFIG.PATH) {
+              _context.next = 7;
+              break;
+            }
 
-          case 5:
-            mongoConnection = _context.sent;
-            return _context.abrupt('return', new _MongoDb2.default(mongoConnection));
+            throw new Error('You should provide path to dir where NeDB will store the db files' + 'in settings.DB_CONFIG.PATH');
 
           case 7:
-            throw new Error('Wrong database type');
+            return _context.abrupt('return', new _NeDb2.default(_settings2.default.DB_CONFIG.PATH));
 
           case 8:
+            throw new Error('Wrong database type');
+
+          case 9:
           case 'end':
             return _context.stop();
         }
diff --git a/src/scripts/migrateFilesToDatabase.js b/src/scripts/migrateFilesToDatabase.js
index 1a8e3bdc..5a612cec 100644
--- a/src/scripts/migrateFilesToDatabase.js
+++ b/src/scripts/migrateFilesToDatabase.js
@@ -4,13 +4,13 @@ import type { DeviceKeyObject } from '../types';
 
 import fs from 'fs';
 import settings from '../settings';
-import { MongoClient, ObjectId } from 'mongodb';
-import TingoDb from '../repository/TingoDb';
+import { ObjectId } from 'mongodb';
+import NeDb from '../repository/NeDb';
 import MongoDb from '../repository/MongoDb';
 
-type DatabaseType = 'mongo' | 'tingo';
+type DatabaseType = 'mongo' | 'nedb';
 
-type Database = TingoDb | MongoDb;
+type Database = MongoDb | NeDb;
 
 type FileObject = {
   fileName: string,
@@ -20,14 +20,25 @@ type FileObject = {
 const DATABASE_TYPE: DatabaseType = ((process.argv[2]): any);
 
 const setupDatabase = async (): Promise => {
-  if (DATABASE_TYPE === 'tingo') {
-    return new TingoDb(settings.DB_CONFIG.PATH, settings.DB_CONFIG.OPTIONS);
-  }
   if (DATABASE_TYPE === 'mongo') {
-    const mongoConnection = await MongoClient.connect(settings.DB_CONFIG.URL);
-    return new MongoDb(mongoConnection);
+    if (!settings.DB_CONFIG.URL) {
+      throw new Error(
+        'You should provide mongoDB connection URL' +
+          'in settings.DB_CONFIG.URL',
+      );
+    }
+    return new MongoDb(settings.DB_CONFIG.URL, settings.DB_CONFIG.OPTIONS);
   }
 
+  if (DATABASE_TYPE === 'nedb') {
+    if (!settings.DB_CONFIG.PATH) {
+      throw new Error(
+        'You should provide path to dir where NeDB will store the db files' +
+          'in settings.DB_CONFIG.PATH',
+      );
+    }
+    return new NeDb(settings.DB_CONFIG.PATH);
+  }
   throw new Error('Wrong database type');
 };
 

From ef291d7d192ef5927a92f8c341a645c82b45a148 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 20 Jun 2017 20:59:36 +0200
Subject: [PATCH 442/504] update azure-mongo example

---
 examples/azure-mongodb.js          | 135 -----------------------------
 examples/azure-mongodb/main.js     | 119 +++++++++++++++++++++++++
 examples/azure-mongodb/settings.js |  69 +++++++++++++++
 3 files changed, 188 insertions(+), 135 deletions(-)
 delete mode 100644 examples/azure-mongodb.js
 create mode 100644 examples/azure-mongodb/main.js
 create mode 100644 examples/azure-mongodb/settings.js

diff --git a/examples/azure-mongodb.js b/examples/azure-mongodb.js
deleted file mode 100644
index 80dbcb3f..00000000
--- a/examples/azure-mongodb.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
-* In this example we are using Azure storage for our SSL keys +
-* MongoDB (Azure Cosmos DB)
-*
-* If you are using Cosmos and aren't replicating your data, be
-* sure to remove the `replicaSet=globaldb` from your connection
-* string.
-*/
-
-/* eslint-disable */
-
-import arrayFlatten from 'array-flatten';
-import azure from 'azure-storage';
-import { Container } from 'constitute';
-import express from 'express';
-import http from 'http';
-import https from 'https';
-import { MongoClient } from 'mongodb';
-import os from 'os';
-import path from 'path';
-import settings from './settings';
-import { MongoDb, createApp, defaultBindings, logger } from 'spark-server';
-
-const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
-const useHttp = NODE_PORT !== 443;
-
-process.on('uncaughtException', (exception: Error) => {
-  logger.error(
-    'uncaughtException',
-    { message: exception.message, stack: exception.stack },
-  ); // logging with MetaData
-  process.exit(1); // exit with failure
-});
-
-const container = new Container();
-defaultBindings(container, settings);
-
-function promisify(
-  call: (serviceCallback: (error: StorageError, result: T) => void) => void,
-): Promise {
-  return new Promise((resolve, reject) => {
-    try {
-      call((error, result) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve(result);
-      });
-    } catch (err) {
-      reject(err);
-    }
-  });
-}
-const blobService = azure.createBlobService(
-  settings.AZURE_STORAGE_CONNECTION_KEY,
-);
-
-MongoClient.connect(
-  settings.DB_CONFIG.URL,
-  {
-    // retry to connect for 60 times
-    reconnectTries: 60,
-    // wait 1 second before retrying
-    reconnectInterval: 1000,
-  },
-).then(
-  async (database: Object): Promise => {
-    try {
-      const sslCertificate = await promisify(
-        cb => blobService.getBlobToText('keys', settings.SSL_CERTIFICATE, cb),
-      );
-      const privateKey = await promisify(
-        cb => blobService.getBlobToText('keys', settings.SSL_PRIVATE_KEY, cb),
-      );
-
-      database.on('error', (err) => {
-        console.log(`DB connection Error: ${err}`);
-      });
-      database.on('open', () => {
-        console.log('DB connected');
-      });
-      database.on('close', (str) => {
-        console.log(`DB disconnected: ${str}`);
-      });
-
-      const options = {
-        cert: sslCertificate,
-        key: privateKey,
-        // This is necessary only if using the client certificate authentication.
-        requestCert: true,
-      };
-
-      container.bindValue('DATABASE_CONNECTION', database);
-      container.bindClass(
-        'Database',
-        MongoDb,
-        ['DATABASE_CONNECTION'],
-      );
-
-      const deviceServer = container.constitute('DeviceServer');
-      deviceServer.start();
-
-      const app = express();
-      createApp(container, settings, app);
-
-      (useHttp
-        ? http.createServer(app)
-        : https.createServer(options, app))
-        .listen(
-          NODE_PORT,
-          (): void =>
-            console.log(`express server started on port ${NODE_PORT}`),
-        );
-
-      const addresses = arrayFlatten(
-        Object.entries(os.networkInterfaces()).map(
-          // eslint-disable-next-line no-unused-vars
-          ([name, nic]: [string, mixed]): Array =>
-            (nic: any)
-              .filter((address: Object): boolean =>
-                address.family === 'IPv4' &&
-                address.address !== '127.0.0.1',
-              )
-              .map((address: Object): boolean => address.address),
-        ),
-      );
-      addresses.forEach((address: string): void =>
-        console.log(`Your device server IP address is: ${address}`),
-      );
-    } catch (error) {
-      console.log('SSL ERROR', error);
-    }
-  },
-);
diff --git a/examples/azure-mongodb/main.js b/examples/azure-mongodb/main.js
new file mode 100644
index 00000000..10e38be6
--- /dev/null
+++ b/examples/azure-mongodb/main.js
@@ -0,0 +1,119 @@
+/*
+* In this example we are using Azure storage for our SSL keys +
+* MongoDB (Azure Cosmos DB)
+*
+* If you are using Cosmos and aren't replicating your data, be
+* sure to remove the `replicaSet=globaldb` from your connection
+* string.
+*/
+
+/* eslint-disable */
+
+import arrayFlatten from 'array-flatten';
+import azure from 'azure-storage';
+import { Container } from 'constitute';
+import express from 'express';
+import http from 'http';
+import https from 'https';
+import os from 'os';
+import path from 'path';
+import settings from './settings';
+import { MongoDb, createApp, defaultBindings, logger } from 'spark-server';
+
+const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
+const useHttp = NODE_PORT !== 443;
+
+process.on('uncaughtException', (exception: Error) => {
+  logger.error(
+    'uncaughtException',
+    { message: exception.message, stack: exception.stack },
+  ); // logging with MetaData
+  process.exit(1); // exit with failure
+});
+
+const container = new Container();
+defaultBindings(container, settings);
+
+// you have to override database bindings to use MongoDB instead of neDB:
+container.bindValue('DATABASE_URL', settings.DB_CONFIG.URL);
+container.bindValue('DATABASE_OPTIONS', settings.DB_CONFIG.OPTIONS);
+container.bindClass(
+  'Database',
+  MongoDb,
+  ['DATABASE_URL', 'DATABASE_OPTIONS'],
+);
+
+function promisify(
+  call: (serviceCallback: (error: StorageError, result: T) => void) => void,
+): Promise {
+  return new Promise((resolve, reject) => {
+    try {
+      call((error, result) => {
+        if (error) {
+          reject(error);
+          return;
+        }
+        resolve(result);
+      });
+    } catch (err) {
+      reject(err);
+    }
+  });
+}
+
+const blobService = azure.createBlobService(
+  settings.AZURE_STORAGE_CONNECTION_KEY,
+);
+
+(async () => {
+  try {
+    const sslCertificate = await promisify(
+      cb => blobService.getBlobToText('keys', settings.SSL_CERTIFICATE, cb),
+    );
+    const privateKey = await promisify(
+      cb => blobService.getBlobToText('keys', settings.SSL_PRIVATE_KEY, cb),
+    );
+
+    const options = {
+      cert: sslCertificate,
+      key: privateKey,
+      // This is necessary only if using the client certificate authentication.
+      requestCert: true,
+    };
+
+    const deviceServer = container.constitute('DeviceServer');
+    deviceServer.start();
+
+    const app = express();
+    createApp(container, settings, app);
+
+    (useHttp
+      ? http.createServer(app)
+      : https.createServer(options, app))
+      .listen(
+        NODE_PORT,
+        (): void =>
+          console.log(`express server started on port ${NODE_PORT}`),
+      );
+
+    const addresses = arrayFlatten(
+      Object.entries(os.networkInterfaces()).map(
+        // eslint-disable-next-line no-unused-vars
+        ([name, nic]: [string, mixed]): Array =>
+          (nic: any)
+            .filter((address: Object): boolean =>
+              address.family === 'IPv4' &&
+              address.address !== '127.0.0.1',
+            )
+            .map((address: Object): boolean => address.address),
+      ),
+    );
+    addresses.forEach((address: string): void =>
+      console.log(`Your device server IP address is: ${address}`),
+    );
+  } catch (error) {
+    console.log('SSL ERROR', error);
+  }
+})();
+
+
diff --git a/examples/azure-mongodb/settings.js b/examples/azure-mongodb/settings.js
new file mode 100644
index 00000000..e63b3f77
--- /dev/null
+++ b/examples/azure-mongodb/settings.js
@@ -0,0 +1,69 @@
+/**
+ *    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
+ *
+ *    This program is free software: you can redistribute it and/or modify
+ *    it under the terms of the GNU Affero General Public License, version 3,
+ *    as published by the Free Software Foundation.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU Affero General Public License for more details.
+ *
+ *    You should have received a copy of the GNU Affero General Public License
+ *    along with this program.  If not, see .
+ *
+ *    You can download the source here: https://github.com/spark/spark-server
+ *
+ * @flow
+ *
+ */
+
+import path from 'path';
+
+/* eslint-disable sorting/sort-object-props */
+export default {
+  BUILD_DIRECTORY: path.join(__dirname, '../data/build'),
+  DEFAULT_ADMIN_PASSWORD: 'adminPassword',
+  DEFAULT_ADMIN_USERNAME: '__admin__',
+  DEVICE_DIRECTORY: path.join(__dirname, '../data/deviceKeys'),
+  ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
+  FIRMWARE_DIRECTORY: path.join(__dirname, '../data/knownApps'),
+  FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../../spark-firmware'),
+  SERVER_KEY_FILENAME: 'default_key.pem',
+  SERVER_KEYS_DIRECTORY: path.join(__dirname, '../data'),
+  USERS_DIRECTORY: path.join(__dirname, '../data/users'),
+  WEBHOOKS_DIRECTORY: path.join(__dirname, '../data/webhooks'),
+  ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
+  API_TIMEOUT: 30000, // Timeout for API requests.
+  CRYPTO_ALGORITHM: 'aes-128-cbc',
+  LOG_REQUESTS: true,
+  LOGIN_ROUTE: '/oauth/token',
+  EXPRESS_SERVER_CONFIG: {
+    PORT: 8080,
+    SSL_CERTIFICATE_FILEPATH: null,
+    SSL_PRIVATE_KEY_FILEPATH: null,
+    USE_SSL: false,
+  },
+  DB_CONFIG: {
+    OPTIONS: {
+      // here you can pass any options, which MongoClient.connect() accepts
+      // retry to connect for 60 times
+      reconnectTries: 60,
+      // wait 1 second before retrying
+      reconnectInterval: 1000,
+    },
+    // you have to provide mongo connection URL here
+    URL: 'mongodb://host:impFJimWwvAJ59u8E...',
+  },
+  SHOW_VERBOSE_DEVICE_LOGS: false,
+
+  TCP_DEVICE_SERVER_CONFIG: {
+    HOST: 'localhost',
+    PORT: 5683,
+  },
+  // Override template parameters in webhooks with this object
+  WEBHOOK_TEMPLATE_PARAMETERS: {
+    // SOME_AUTH_TOKEN: '12312312',
+  },
+};

From c59336e560708c1d288f65abdc17945e8dcc7c18 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Tue, 20 Jun 2017 23:50:42 +0200
Subject: [PATCH 443/504] filter broadcasted events in webhookmanager

---
 dist/managers/WebhookManager.js | 1 +
 src/managers/WebhookManager.js  | 1 +
 src/types.js                    | 3 ++-
 3 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index f298e036..4e632814 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -261,6 +261,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
     var subscriptionID = _this._eventPublisher.subscribe(webhook.event, _this._onNewWebhookEvent(webhook), {
       filterOptions: {
         deviceID: webhook.deviceID,
+        listenToBroadcastedEvents: false,
         mydevices: webhook.mydevices,
         userID: webhook.ownerID
       }
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index ac7c8ab0..a651b313 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -132,6 +132,7 @@ class WebhookManager {
       {
         filterOptions: {
           deviceID: webhook.deviceID,
+          listenToBroadcastedEvents: false,
           mydevices: webhook.mydevices,
           userID: webhook.ownerID,
         },
diff --git a/src/types.js b/src/types.js
index 1acf4ecb..d0fb69d6 100644
--- a/src/types.js
+++ b/src/types.js
@@ -89,8 +89,9 @@ export type DeviceKeyObject = {
 };
 
 export type Event = EventData & {
-  ttl: number,
+  broadcasted?: boolean,
   publishedAt: Date,
+  ttl: number,
 };
 
 export type EventData = {

From 060cfe680352f71712b95b6ecb2610c2de949487 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 21 Jun 2017 00:05:03 +0200
Subject: [PATCH 444/504] add eventProvider example

---
 examples/eventProvider.js | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
 create mode 100644 examples/eventProvider.js

diff --git a/examples/eventProvider.js b/examples/eventProvider.js
new file mode 100644
index 00000000..cc721bb6
--- /dev/null
+++ b/examples/eventProvider.js
@@ -0,0 +1,20 @@
+/*
+* Many of you want to scale the server with docker images or some other way.
+* You should be able to use the following code to get full events stream
+* and pipe it with some service like rabbitMQ or zeroMQ.
+* You shouldn't worry about events mirroring between different processes,
+* EVENT_PROVIDER is smart enough to filter broadcasted events.
+*/
+
+/* eslint-disable */
+import type { Event } from '../src/types';
+
+import { Container } from 'constitute';
+import { defaultBindings } from 'spark-server';
+
+const container = new Container();
+defaultBindings(container, settings);
+
+container.constitute('EVENT_PROVIDER').onNewEvent((event: Event) => {
+  // do piping stuff here.
+});

From 0f7118288b68971dd756d7a4342a690d6feafd04 Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Wed, 21 Jun 2017 10:16:02 +0200
Subject: [PATCH 445/504] Added npm functions for example build and run,
 changed eventProvider sample

---
 examples/eventProvider.js | 13 +++++++++----
 package.json              |  2 ++
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/examples/eventProvider.js b/examples/eventProvider.js
index cc721bb6..27db83bc 100644
--- a/examples/eventProvider.js
+++ b/examples/eventProvider.js
@@ -7,14 +7,19 @@
 */
 
 /* eslint-disable */
-import type { Event } from '../src/types';
+import type { Event } from '../types';
 
 import { Container } from 'constitute';
-import { defaultBindings } from 'spark-server';
+import defaultBindings from '../defaultBindings';
+import settings from '../settings.js';
 
 const container = new Container();
 defaultBindings(container, settings);
 
-container.constitute('EVENT_PROVIDER').onNewEvent((event: Event) => {
-  // do piping stuff here.
+const deviceServer = container.constitute('DeviceServer');
+deviceServer.start();
+
+const evProvider = container.constitute('EVENT_PROVIDER');
+evProvider.onNewEvent((event: Event) => {
+  console.log('Event');
 });
diff --git a/package.json b/package.json
index c00b37d1..449e1c3a 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,8 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
+    "examples:build" : "babel ./examples --out-dir ./dist/examples",
+    "examples:run:eventprovider" : "node ./dist/examples/eventProvider.js",
     "lint": "eslint --fix --max-warnings 0 -- .",
     "migrate-files-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-files-to-nedb": "babel-node ./src/scripts/migrateFilesToDatabase nedb",

From 8ae485d0020d9d66c661dfc4d387e698e6710577 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Wed, 21 Jun 2017 10:35:49 +0200
Subject: [PATCH 446/504] Changed sample

---
 examples/eventProvider.js | 20 ++++++++++++++++++--
 package.json              |  2 +-
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/examples/eventProvider.js b/examples/eventProvider.js
index 27db83bc..4aa7a63d 100644
--- a/examples/eventProvider.js
+++ b/examples/eventProvider.js
@@ -6,12 +6,28 @@
 * EVENT_PROVIDER is smart enough to filter broadcasted events.
 */
 
+/*
+  to build this sample run
+
+    npm run examples:build
+
+  to run
+
+    npm run examples:run:eventprovider
+
+  if this fails, ensure you have to latest version of spark-protocol, run
+
+    npm update spark-protocol
+
+*/
+
 /* eslint-disable */
-import type { Event } from '../types';
 
+import type { Event } from '../types';
 import { Container } from 'constitute';
 import defaultBindings from '../defaultBindings';
 import settings from '../settings.js';
+import logger from '../lib/logger';
 
 const container = new Container();
 defaultBindings(container, settings);
@@ -21,5 +37,5 @@ deviceServer.start();
 
 const evProvider = container.constitute('EVENT_PROVIDER');
 evProvider.onNewEvent((event: Event) => {
-  console.log('Event');
+  logger.info('Event',event);
 });
diff --git a/package.json b/package.json
index 449e1c3a..dad78ad8 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
-    "examples:build" : "babel ./examples --out-dir ./dist/examples",
+    "examples:build" : "babel ./examples --out-dir ./dist/examples --ignore node_modules/**/*.js",
     "examples:run:eventprovider" : "node ./dist/examples/eventProvider.js",
     "lint": "eslint --fix --max-warnings 0 -- .",
     "migrate-files-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",

From c21fcf4c5694768d5b16f6d1b3daa75c84de4899 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Wed, 21 Jun 2017 10:45:19 +0200
Subject: [PATCH 447/504] changed ignore on npm build:examples

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index dad78ad8..d70262df 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
-    "examples:build" : "babel ./examples --out-dir ./dist/examples --ignore node_modules/**/*.js",
+    "examples:build" : "babel ./examples --out-dir ./dist/examples --ignore node_modules",
     "examples:run:eventprovider" : "node ./dist/examples/eventProvider.js",
     "lint": "eslint --fix --max-warnings 0 -- .",
     "migrate-files-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",

From 2a013c8d75bb457f4180e3889e927f38bf4ef788 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20H=C3=A4ferer?= 
Date: Wed, 21 Jun 2017 15:05:49 +0200
Subject: [PATCH 448/504] patched sample to working code

---
 examples/eventProvider.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/examples/eventProvider.js b/examples/eventProvider.js
index 4aa7a63d..497368a2 100644
--- a/examples/eventProvider.js
+++ b/examples/eventProvider.js
@@ -35,7 +35,15 @@ defaultBindings(container, settings);
 const deviceServer = container.constitute('DeviceServer');
 deviceServer.start();
 
+/*
 const evProvider = container.constitute('EVENT_PROVIDER');
 evProvider.onNewEvent((event: Event) => {
-  logger.info('Event',event);
+  logger.info('Event onNewEvent',event);
+});
+*/
+
+
+
+deviceServer._eventPublisher.subscribe('*', (ev: any) => {
+  logger.info({ data: ev.data, event: ev.name, name: ev.deviceID, event: ev }, 'Incomming Event');
 });

From f98fc8aa468a35a10211071c34e9121027444c36 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 22 Jun 2017 21:47:11 +0200
Subject: [PATCH 449/504] add prettier, add gitAttributes to hide dist in
 commit diffs

---
 .eslintrc                                     |    6 +-
 .gitattributes                                |    2 +
 dist/controllers/OauthClientsController.js    |  132 +-
 dist/controllers/ProductsController.js        |  220 +---
 dist/main.js                                  |    5 +-
 dist/managers/DeviceManager.js                |    8 +-
 dist/managers/EventManager.js                 |    4 +-
 dist/managers/PermissionManager.js            |    2 +-
 dist/managers/WebhookManager.js               |    5 +-
 .../repository/DeviceKeyDatabaseRepository.js |    4 +-
 dist/repository/NeDb.js                       |    5 +-
 dist/repository/UserDatabaseRepository.js     |   14 +-
 dist/repository/UserFileRepository.js         |    8 +-
 dist/scripts/migrateFilesToDatabase.js        |    8 +-
 examples/azure-mongodb/main.js                |   39 +-
 package-lock.json                             | 1113 +++++++++--------
 package.json                                  |   27 +-
 src/OAuthModel.js                             |   12 +-
 src/RouteConfig.js                            |  140 ++-
 src/app.js                                    |   12 +-
 src/controllers/DeviceClaimsController.js     |    8 +-
 src/controllers/DevicesController.js          |   21 +-
 src/controllers/OauthClientsController.js     |    3 -
 src/controllers/ProductsController.js         |   36 +-
 src/controllers/UsersController.js            |   19 +-
 src/controllers/types.js                      |   22 +-
 src/decorators/allowUpload.js                 |   27 +-
 src/decorators/anonymous.js                   |   13 +-
 src/decorators/httpVerb.js                    |   13 +-
 src/decorators/route.js                       |   13 +-
 src/decorators/serverSentEvents.js            |   13 +-
 src/decorators/types.js                       |   60 +-
 src/defaultBindings.js                        |  165 +--
 src/exports.js                                |    8 +-
 src/lib/DefaultLogger.js                      |   18 +-
 src/lib/HttpError.js                          |    5 +-
 src/lib/PasswordHasher.js                     |    5 +-
 src/lib/promisify.js                          |   27 +-
 src/main.js                                   |   27 +-
 src/managers/DeviceManager.js                 |  119 +-
 src/managers/EventManager.js                  |    8 +-
 src/managers/FirmwareCompilationManager.js    |   44 +-
 src/managers/PermissionManager.js             |   31 +-
 src/managers/WebhookManager.js                |  199 ++-
 .../DeviceAttributeDatabaseRepository.js      |    5 +-
 src/repository/DeviceKeyDatabaseRepository.js |   14 +-
 src/repository/MongoDb.js                     |  131 +-
 src/repository/NeDb.js                        |  110 +-
 src/repository/UserDatabaseRepository.js      |   56 +-
 src/repository/UserFileRepository.js          |   35 +-
 src/repository/WebhookDatabaseRepository.js   |   16 +-
 src/repository/WebhookFileRepository.js       |    7 +-
 src/repository/collectionNames.js             |    8 +-
 src/scripts/migrateFilesToDatabase.js         |   87 +-
 src/types.js                                  |   60 +-
 test/DeviceClaimsController.test.js           |   36 +-
 test/DevicesController.test.js                |  285 ++---
 test/EventsController.test.js                 |    2 +-
 test/ProvisioningController.test.js           |    9 +-
 test/UserFileRepository.test.js               |   77 +-
 test/UsersController.test.js                  |   23 +-
 test/WebhookManager.test.js                   |  670 +++++-----
 test/WebhooksController.test.js               |   17 +-
 test/setup/TestData.js                        |   47 +-
 test/setup/getDefaultContainer.js             |    1 -
 test/setup/settings.js                        |    5 +-
 66 files changed, 2011 insertions(+), 2360 deletions(-)
 create mode 100644 .gitattributes

diff --git a/.eslintrc b/.eslintrc
index 431dd96c..99c8a580 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,11 +1,15 @@
 {
-  "extends": "airbnb-base",
+  "extends": [
+    "airbnb-base",
+    "prettier"
+  ],
   "parser": "babel-eslint",
   "plugins": [
     "flowtype",
     "sorting"
   ],
   "rules": {
+    "class-methods-use-this": 0,
     "eol-last": 2,
     "flowtype/define-flow-type": 1,
     "flowtype/require-parameter-type": 1,
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..ef758db9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# hide dist files in commit diffs.
+dist/* linguist-generated
diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js
index 2aaeeaad..febf6743 100644
--- a/dist/controllers/OauthClientsController.js
+++ b/dist/controllers/OauthClientsController.js
@@ -4,18 +4,6 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor');
-
-var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
-
-var _regenerator = require('babel-runtime/regenerator');
-
-var _regenerator2 = _interopRequireDefault(_regenerator);
-
-var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
-
-var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
-
 var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
 
 var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
@@ -36,8 +24,6 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _desc, _value, _class;
-
 var _Controller2 = require('./Controller');
 
 var _Controller3 = _interopRequireDefault(_Controller2);
@@ -56,42 +42,7 @@ var _route2 = _interopRequireDefault(_route);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
-  var desc = {};
-  Object['ke' + 'ys'](descriptor).forEach(function (key) {
-    desc[key] = descriptor[key];
-  });
-  desc.enumerable = !!desc.enumerable;
-  desc.configurable = !!desc.configurable;
-
-  if ('value' in desc || desc.initializer) {
-    desc.writable = true;
-  }
-
-  desc = decorators.slice().reverse().reduce(function (desc, decorator) {
-    return decorator(target, property, desc) || desc;
-  }, desc);
-
-  if (context && desc.initializer !== void 0) {
-    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
-    desc.initializer = undefined;
-  }
-
-  if (desc.initializer === void 0) {
-    Object['define' + 'Property'](target, property, desc);
-    desc = null;
-  }
-
-  return desc;
-}
-
-var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'
-// eslint-disable-next-line class-methods-use-this
-), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'
-// eslint-disable-next-line class-methods-use-this
-), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'
-// eslint-disable-next-line class-methods-use-this
-), (_class = function (_Controller) {
+var OauthClientsController = function (_Controller) {
   (0, _inherits3.default)(OauthClientsController, _Controller);
 
   function OauthClientsController() {
@@ -101,77 +52,24 @@ var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
 
   (0, _createClass3.default)(OauthClientsController, [{
     key: 'createClient',
-    value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
-        return _regenerator2.default.wrap(function _callee$(_context) {
-          while (1) {
-            switch (_context.prev = _context.next) {
-              case 0:
-                throw new _HttpError2.default('not supported in the current server version');
-
-              case 1:
-              case 'end':
-                return _context.stop();
-            }
-          }
-        }, _callee, this);
-      }));
-
-      function createClient() {
-        return _ref.apply(this, arguments);
-      }
-
-      return createClient;
-    }()
+    // eslint-disable-next-line class-methods-use-this
+    value: function createClient() {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
   }, {
     key: 'editClient',
-    value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
-        return _regenerator2.default.wrap(function _callee2$(_context2) {
-          while (1) {
-            switch (_context2.prev = _context2.next) {
-              case 0:
-                throw new _HttpError2.default('not supported in the current server version');
-
-              case 1:
-              case 'end':
-                return _context2.stop();
-            }
-          }
-        }, _callee2, this);
-      }));
-
-      function editClient() {
-        return _ref2.apply(this, arguments);
-      }
-
-      return editClient;
-    }()
+    // eslint-disable-next-line class-methods-use-this
+    value: function editClient() {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
   }, {
     key: 'deleteClient',
-    value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-        return _regenerator2.default.wrap(function _callee3$(_context3) {
-          while (1) {
-            switch (_context3.prev = _context3.next) {
-              case 0:
-                throw new _HttpError2.default('not supported in the current server version');
-
-              case 1:
-              case 'end':
-                return _context3.stop();
-            }
-          }
-        }, _callee3, this);
-      }));
-
-      function deleteClient() {
-        return _ref3.apply(this, arguments);
-      }
-
-      return deleteClient;
-    }()
+    // eslint-disable-next-line class-methods-use-this
+    value: function deleteClient() {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
   }]);
   return OauthClientsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class));
+}(_Controller3.default);
+
 exports.default = OauthClientsController;
\ No newline at end of file
diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 1ec39bb1..a372e304 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -36,7 +36,7 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _desc, _value, _class;
+var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _desc, _value, _class;
 /* eslint-disable */
 
 var _Controller2 = require('./Controller');
@@ -86,17 +86,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'
-// eslint-disable-next-line class-methods-use-this
-), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'
-// eslint-disable-next-line class-methods-use-this
-), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec7 = (0, _httpVerb2.default)('post'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/device_claims'
-// eslint-disable-next-line class-methods-use-this
-), _dec9 = (0, _httpVerb2.default)('get'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec11 = (0, _httpVerb2.default)('post'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec15 = (0, _httpVerb2.default)('put'), _dec16 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec17 = (0, _httpVerb2.default)('delete'), _dec18 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'
-// eslint-disable-next-line class-methods-use-this
-), _dec19 = (0, _httpVerb2.default)('get'), _dec20 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec21 = (0, _httpVerb2.default)('get'), _dec22 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec23 = (0, _httpVerb2.default)('delete'), _dec24 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'
-// eslint-disable-next-line class-methods-use-this
-), (_class = function (_Controller) {
+var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec9 = (0, _httpVerb2.default)('put'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec11 = (0, _httpVerb2.default)('get'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), (_class = function (_Controller) {
   (0, _inherits3.default)(ProductsController, _Controller);
 
   function ProductsController(deviceManager) {
@@ -110,13 +100,25 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
   (0, _createClass3.default)(ProductsController, [{
     key: 'getProducts',
+    // eslint-disable-next-line class-methods-use-this
+    value: function getProducts() {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
+  }, {
+    key: 'createProduct',
+    // eslint-disable-next-line class-methods-use-this
+    value: function createProduct() {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
+  }, {
+    key: 'getProduct',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                throw new _HttpError2.default('Not implemented');
 
               case 1:
               case 'end':
@@ -126,21 +128,27 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee, this);
       }));
 
-      function getProducts() {
+      function getProduct(_x) {
         return _ref.apply(this, arguments);
       }
 
-      return getProducts;
+      return getProduct;
     }()
   }, {
-    key: 'createProduct',
+    key: 'generateClaimCode',
+    // eslint-disable-next-line class-methods-use-this
+    value: function generateClaimCode(productIdOrSlug) {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
+  }, {
+    key: 'getFirmware',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                throw new _HttpError2.default('Not implemented');
 
               case 1:
               case 'end':
@@ -150,14 +158,17 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee2, this);
       }));
 
-      function createProduct() {
+      function getFirmware(_x2) {
         return _ref2.apply(this, arguments);
       }
 
-      return createProduct;
+      return getFirmware;
     }()
+
+    // {version: number, name: 'current', binary: File, title: string, description: string}
+
   }, {
-    key: 'getProduct',
+    key: 'getFirmware',
     value: function () {
       var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -174,21 +185,21 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee3, this);
       }));
 
-      function getProduct(_x) {
+      function getFirmware(_x3) {
         return _ref3.apply(this, arguments);
       }
 
-      return getProduct;
+      return getFirmware;
     }()
   }, {
-    key: 'generateClaimCode',
+    key: 'getDevices',
     value: function () {
       var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                throw new _HttpError2.default('Not implemented');
 
               case 1:
               case 'end':
@@ -198,16 +209,16 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee4, this);
       }));
 
-      function generateClaimCode(_x2) {
+      function getDevices(_x4) {
         return _ref4.apply(this, arguments);
       }
 
-      return generateClaimCode;
+      return getDevices;
     }()
   }, {
-    key: 'getFirmware',
+    key: 'setFirmwareVersion',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIdOrSlug) {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIdOrSlug, deviceID, body) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
@@ -222,17 +233,20 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee5, this);
       }));
 
-      function getFirmware(_x3) {
+      function setFirmwareVersion(_x5, _x6, _x7) {
         return _ref5.apply(this, arguments);
       }
 
-      return getFirmware;
+      return setFirmwareVersion;
     }()
-
-    // {version: number, name: 'current', binary: File, title: string, description: string}
-
   }, {
-    key: 'getFirmware',
+    key: 'removeDeviceFromProduct',
+    // eslint-disable-next-line class-methods-use-this
+    value: function removeDeviceFromProduct(productIdOrSlug, deviceID) {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
+  }, {
+    key: 'getConfig',
     value: function () {
       var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
@@ -249,16 +263,16 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee6, this);
       }));
 
-      function getFirmware(_x4) {
+      function getConfig(_x8) {
         return _ref6.apply(this, arguments);
       }
 
-      return getFirmware;
+      return getConfig;
     }()
   }, {
-    key: 'getDevices',
+    key: 'getEvents',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIdOrSlug) {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIdOrSlug, eventName) {
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
@@ -273,134 +287,20 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee7, this);
       }));
 
-      function getDevices(_x5) {
+      function getEvents(_x9, _x10) {
         return _ref7.apply(this, arguments);
       }
 
-      return getDevices;
-    }()
-  }, {
-    key: 'setFirmwareVersion',
-    value: function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productIdOrSlug, deviceID, body) {
-        return _regenerator2.default.wrap(function _callee8$(_context8) {
-          while (1) {
-            switch (_context8.prev = _context8.next) {
-              case 0:
-                throw new _HttpError2.default('Not implemented');
-
-              case 1:
-              case 'end':
-                return _context8.stop();
-            }
-          }
-        }, _callee8, this);
-      }));
-
-      function setFirmwareVersion(_x6, _x7, _x8) {
-        return _ref8.apply(this, arguments);
-      }
-
-      return setFirmwareVersion;
-    }()
-  }, {
-    key: 'removeDeviceFromProduct',
-    value: function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIdOrSlug, deviceID) {
-        return _regenerator2.default.wrap(function _callee9$(_context9) {
-          while (1) {
-            switch (_context9.prev = _context9.next) {
-              case 0:
-                throw new _HttpError2.default('not supported in the current server version');
-
-              case 1:
-              case 'end':
-                return _context9.stop();
-            }
-          }
-        }, _callee9, this);
-      }));
-
-      function removeDeviceFromProduct(_x9, _x10) {
-        return _ref9.apply(this, arguments);
-      }
-
-      return removeDeviceFromProduct;
-    }()
-  }, {
-    key: 'getConfig',
-    value: function () {
-      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(productIdOrSlug) {
-        return _regenerator2.default.wrap(function _callee10$(_context10) {
-          while (1) {
-            switch (_context10.prev = _context10.next) {
-              case 0:
-                throw new _HttpError2.default('Not implemented');
-
-              case 1:
-              case 'end':
-                return _context10.stop();
-            }
-          }
-        }, _callee10, this);
-      }));
-
-      function getConfig(_x11) {
-        return _ref10.apply(this, arguments);
-      }
-
-      return getConfig;
-    }()
-  }, {
-    key: 'getEvents',
-    value: function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(productIdOrSlug, eventName) {
-        return _regenerator2.default.wrap(function _callee11$(_context11) {
-          while (1) {
-            switch (_context11.prev = _context11.next) {
-              case 0:
-                throw new _HttpError2.default('Not implemented');
-
-              case 1:
-              case 'end':
-                return _context11.stop();
-            }
-          }
-        }, _callee11, this);
-      }));
-
-      function getEvents(_x12, _x13) {
-        return _ref11.apply(this, arguments);
-      }
-
       return getEvents;
     }()
   }, {
     key: 'removeTeamMember',
-    value: function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug, username) {
-        return _regenerator2.default.wrap(function _callee12$(_context12) {
-          while (1) {
-            switch (_context12.prev = _context12.next) {
-              case 0:
-                throw new _HttpError2.default('not supported in the current server version');
-
-              case 1:
-              case 'end':
-                return _context12.stop();
-            }
-          }
-        }, _callee12, this);
-      }));
-
-      function removeTeamMember(_x14, _x15) {
-        return _ref12.apply(this, arguments);
-      }
-
-      return removeTeamMember;
-    }()
+    // eslint-disable-next-line class-methods-use-this
+    value: function removeTeamMember(productIdOrSlug, username) {
+      throw new _HttpError2.default('not supported in the current server version');
+    }
   }]);
   return ProductsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'generateClaimCode', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'generateClaimCode'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec23, _dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype)), _class));
 exports.default = ProductsController;
 /* eslint-enable */
\ No newline at end of file
diff --git a/dist/main.js b/dist/main.js
index 4b13a7ac..e18e8cb8 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -63,7 +63,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var NODE_PORT = process.env.NODE_PORT || _settings2.default.EXPRESS_SERVER_CONFIG.PORT;
 
 process.on('uncaughtException', function (exception) {
-  _logger2.default.error('uncaughtException', { message: exception.message, stack: exception.stack }); // logging with MetaData
+  _logger2.default.error('uncaughtException', {
+    message: exception.message,
+    stack: exception.stack
+  }); // logging with MetaData
   process.exit(1); // exit with failure
 });
 
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index f9cc3dcb..4ebfd859 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -94,7 +94,9 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 11:
               _context.next = 13;
-              return _this._deviceAttributeRepository.updateByID(deviceID, { ownerID: userID });
+              return _this._deviceAttributeRepository.updateByID(deviceID, {
+                ownerID: userID
+              });
 
             case 13:
               return _context.abrupt('return', _context.sent);
@@ -130,7 +132,9 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
 
             case 4:
               _context2.next = 6;
-              return _this._deviceAttributeRepository.updateByID(deviceID, { ownerID: null });
+              return _this._deviceAttributeRepository.updateByID(deviceID, {
+                ownerID: null
+              });
 
             case 6:
               return _context2.abrupt('return', _context2.sent);
diff --git a/dist/managers/EventManager.js b/dist/managers/EventManager.js
index 3d95ecf1..a78708ba 100644
--- a/dist/managers/EventManager.js
+++ b/dist/managers/EventManager.js
@@ -16,7 +16,9 @@ var EventManager = function EventManager(eventPublisher) {
   (0, _classCallCheck3.default)(this, EventManager);
 
   this.subscribe = function (eventNamePrefix, eventHandler, filterOptions) {
-    return _this._eventPublisher.subscribe(eventNamePrefix, eventHandler, { filterOptions: filterOptions });
+    return _this._eventPublisher.subscribe(eventNamePrefix, eventHandler, {
+      filterOptions: filterOptions
+    });
   };
 
   this.unsubscribe = function (subscriptionID) {
diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index 5e5e7f7a..ecc4bc8d 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -124,7 +124,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
                 break;
               }
 
-              throw new _HttpError2.default('User doesn\'t have access', 403);
+              throw new _HttpError2.default("User doesn't have access", 403);
 
             case 7:
               return _context3.abrupt('return', entity);
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 4e632814..b3ed0f03 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -387,7 +387,10 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
     };
   }();
 
-  this.runWebhookThrottled = (0, _throttle2.default)(this.runWebhook, WEBHOOK_THROTTLE_TIME, { leading: false, trailing: true });
+  this.runWebhookThrottled = (0, _throttle2.default)(this.runWebhook, WEBHOOK_THROTTLE_TIME, {
+    leading: false,
+    trailing: true
+  });
 
   this._callWebhook = function (webhook, event, requestOptions) {
     return new _promise2.default(function (resolve, reject) {
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index a0e7cf2a..01751dfc 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -40,7 +40,9 @@ var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database)
           switch (_context.prev = _context.next) {
             case 0:
               _context.next = 2;
-              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({ _id: model.deviceID }, model));
+              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({
+                _id: model.deviceID
+              }, model));
 
             case 2:
               return _context.abrupt('return', _context.sent);
diff --git a/dist/repository/NeDb.js b/dist/repository/NeDb.js
index 477dd3ff..48473f00 100644
--- a/dist/repository/NeDb.js
+++ b/dist/repository/NeDb.js
@@ -186,7 +186,10 @@ var NeDb = function (_BaseMongoDb) {
                         switch (_context5.prev = _context5.next) {
                           case 0:
                             _context5.next = 2;
-                            return (0, _promisify.promisify)(collection, 'update', query, updateQuery, { returnUpdatedDocs: true, upsert: true });
+                            return (0, _promisify.promisify)(collection, 'update', query, updateQuery, {
+                              returnUpdatedDocs: true,
+                              upsert: true
+                            });
 
                           case 2:
                             _ref7 = _context5.sent;
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 0eab830a..754f178d 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -184,7 +184,9 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
           switch (_context6.prev = _context6.next) {
             case 0:
               _context6.next = 2;
-              return _this._database.findOne(_this._collectionName, { accessTokens: { $elemMatch: { accessToken: accessToken } } });
+              return _this._database.findOne(_this._collectionName, {
+                accessTokens: { $elemMatch: { accessToken: accessToken } }
+              });
 
             case 2:
               user = _context6.sent;
@@ -195,7 +197,9 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
               }
 
               _context6.next = 6;
-              return _this._database.findOne(_this._collectionName, { 'accessTokens.accessToken': accessToken });
+              return _this._database.findOne(_this._collectionName, {
+                'accessTokens.accessToken': accessToken
+              });
 
             case 6:
               user = _context6.sent;
@@ -354,7 +358,9 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
             case 0:
               _context12.prev = 0;
               _context12.next = 3;
-              return _this._database.findOne(_this._collectionName, { username: username });
+              return _this._database.findOne(_this._collectionName, {
+                username: username
+              });
 
             case 3:
               user = _context12.sent;
@@ -364,7 +370,7 @@ var UserDatabaseRepository = function UserDatabaseRepository(database) {
                 break;
               }
 
-              throw new _HttpError2.default('User doesn\'t exist', 404);
+              throw new _HttpError2.default("User doesn't exist", 404);
 
             case 6:
               _context12.next = 8;
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index ac42ec06..ae9eed18 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -148,7 +148,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                   break;
                 }
 
-                throw new Error('User doesn\'t exist');
+                throw new Error("User doesn't exist");
 
               case 5:
                 _context2.next = 7;
@@ -231,7 +231,9 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
               case 5:
                 _context4.next = 7;
-                return _this.updateByID(userID, { accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject]) });
+                return _this.updateByID(userID, {
+                  accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject])
+                });
 
               case 7:
                 return _context4.abrupt('return', _context4.sent);
@@ -272,7 +274,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                   break;
                 }
 
-                throw new Error('User doesn\'t exist');
+                throw new Error("User doesn't exist");
 
               case 6:
                 _context5.next = 8;
diff --git a/dist/scripts/migrateFilesToDatabase.js b/dist/scripts/migrateFilesToDatabase.js
index 98fce9f9..afc01388 100644
--- a/dist/scripts/migrateFilesToDatabase.js
+++ b/dist/scripts/migrateFilesToDatabase.js
@@ -120,13 +120,17 @@ var parseFile = function parseFile(file) {
 
 var mapOwnerID = function mapOwnerID(userIDsMap) {
   return function (item) {
-    return (0, _extends3.default)({}, item, { ownerID: userIDsMap.get(item.ownerID) || null
+    return (0, _extends3.default)({}, item, {
+      ownerID: userIDsMap.get(item.ownerID) || null
     });
   };
 };
 
 var translateDeviceID = function translateDeviceID(item) {
-  return (0, _extends3.default)({}, item, { _id: new _mongodb.ObjectId(item.deviceID), id: item.deviceID });
+  return (0, _extends3.default)({}, item, {
+    _id: new _mongodb.ObjectId(item.deviceID),
+    id: item.deviceID
+  });
 };
 
 // eslint-disable-next-line no-unused-vars
diff --git a/examples/azure-mongodb/main.js b/examples/azure-mongodb/main.js
index 10e38be6..59b70342 100644
--- a/examples/azure-mongodb/main.js
+++ b/examples/azure-mongodb/main.js
@@ -24,10 +24,10 @@ const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
 const useHttp = NODE_PORT !== 443;
 
 process.on('uncaughtException', (exception: Error) => {
-  logger.error(
-    'uncaughtException',
-    { message: exception.message, stack: exception.stack },
-  ); // logging with MetaData
+  logger.error('uncaughtException', {
+    message: exception.message,
+    stack: exception.stack,
+  }); // logging with MetaData
   process.exit(1); // exit with failure
 });
 
@@ -37,11 +37,7 @@ defaultBindings(container, settings);
 // you have to override database bindings to use MongoDB instead of neDB:
 container.bindValue('DATABASE_URL', settings.DB_CONFIG.URL);
 container.bindValue('DATABASE_OPTIONS', settings.DB_CONFIG.OPTIONS);
-container.bindClass(
-  'Database',
-  MongoDb,
-  ['DATABASE_URL', 'DATABASE_OPTIONS'],
-);
+container.bindClass('Database', MongoDb, ['DATABASE_URL', 'DATABASE_OPTIONS']);
 
 function promisify(
   call: (serviceCallback: (error: StorageError, result: T) => void) => void,
@@ -67,11 +63,11 @@ const blobService = azure.createBlobService(
 
 (async () => {
   try {
-    const sslCertificate = await promisify(
-      cb => blobService.getBlobToText('keys', settings.SSL_CERTIFICATE, cb),
+    const sslCertificate = await promisify(cb =>
+      blobService.getBlobToText('keys', settings.SSL_CERTIFICATE, cb),
     );
-    const privateKey = await promisify(
-      cb => blobService.getBlobToText('keys', settings.SSL_PRIVATE_KEY, cb),
+    const privateKey = await promisify(cb =>
+      blobService.getBlobToText('keys', settings.SSL_PRIVATE_KEY, cb),
     );
 
     const options = {
@@ -89,21 +85,18 @@ const blobService = azure.createBlobService(
 
     (useHttp
       ? http.createServer(app)
-      : https.createServer(options, app))
-      .listen(
-        NODE_PORT,
-        (): void =>
-          console.log(`express server started on port ${NODE_PORT}`),
-      );
+      : https.createServer(options, app)).listen(NODE_PORT, (): void =>
+      console.log(`express server started on port ${NODE_PORT}`),
+    );
 
     const addresses = arrayFlatten(
       Object.entries(os.networkInterfaces()).map(
         // eslint-disable-next-line no-unused-vars
         ([name, nic]: [string, mixed]): Array =>
           (nic: any)
-            .filter((address: Object): boolean =>
-              address.family === 'IPv4' &&
-              address.address !== '127.0.0.1',
+            .filter(
+              (address: Object): boolean =>
+                address.family === 'IPv4' && address.address !== '127.0.0.1',
             )
             .map((address: Object): boolean => address.address),
       ),
@@ -115,5 +108,3 @@ const blobService = azure.createBlobService(
     console.log('SSL ERROR', error);
   }
 })();
-
-
diff --git a/package-lock.json b/package-lock.json
index 2aa0c9a9..02d3f319 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,10 +14,9 @@
       "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo="
     },
     "acorn": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
-      "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
-      "dev": true
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
+      "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
     },
     "acorn-jsx": {
       "version": "3.0.1",
@@ -88,6 +87,12 @@
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
       "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc="
     },
+    "app-root-path": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz",
+      "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=",
+      "dev": true
+    },
     "append-field": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz",
@@ -180,10 +185,9 @@
       "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI="
     },
     "async": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
-      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
-      "dev": true
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
+      "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
     },
     "async-each": {
       "version": "1.0.1",
@@ -248,9 +252,9 @@
       "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ="
     },
     "babel-core": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.24.1.tgz",
-      "integrity": "sha1-jEKFZNzh4fQfszfsNPTDsCK1rYM="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz",
+      "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk="
     },
     "babel-eslint": {
       "version": "7.2.3",
@@ -259,9 +263,9 @@
       "dev": true
     },
     "babel-generator": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz",
-      "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz",
+      "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw="
     },
     "babel-helper-bindify-decorators": {
       "version": "6.24.1",
@@ -741,29 +745,29 @@
       "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs="
     },
     "babel-template": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz",
-      "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
+      "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE="
     },
     "babel-traverse": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz",
-      "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz",
+      "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE="
     },
     "babel-types": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz",
-      "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU="
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
+      "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4="
     },
     "babylon": {
-      "version": "6.17.2",
-      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.2.tgz",
-      "integrity": "sha1-IB0l71+JLEG65JSIsI2w3Udun1w="
+      "version": "6.17.4",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz",
+      "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw=="
     },
     "balanced-match": {
-      "version": "0.4.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
-      "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
     },
     "base62": {
       "version": "0.1.1",
@@ -831,9 +835,9 @@
       "dev": true
     },
     "brace-expansion": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
-      "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k="
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI="
     },
     "braces": {
       "version": "1.8.5",
@@ -904,7 +908,15 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz",
       "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "deep-equal": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+          "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+          "dev": true
+        }
+      }
     },
     "call-signature": {
       "version": "0.0.2",
@@ -1183,6 +1195,20 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
+    "cosmiconfig": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz",
+      "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        }
+      }
+    },
     "create-error-class": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
@@ -1193,7 +1219,15 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
       "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+          "dev": true
+        }
+      }
     },
     "cryptiles": {
       "version": "2.0.5",
@@ -1223,6 +1257,12 @@
         }
       }
     },
+    "date-fns": {
+      "version": "1.28.5",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.28.5.tgz",
+      "integrity": "sha1-JXz8RdMi30XvVlhmWWfuhBzXP68=",
+      "dev": true
+    },
     "date-time": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/date-time/-/date-time-0.1.1.tgz",
@@ -1253,10 +1293,9 @@
       }
     },
     "deep-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
-      "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
-      "dev": true
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz",
+      "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0="
     },
     "deep-extend": {
       "version": "0.4.2",
@@ -1383,6 +1422,12 @@
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
       "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
     },
+    "elegant-spinner": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
+      "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=",
+      "dev": true
+    },
     "empower-core": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz",
@@ -1417,14 +1462,7 @@
     "es3ify": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz",
-      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=",
-      "dependencies": {
-        "esprima-fb": {
-          "version": "3001.1.0-dev-harmony-fb",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
-          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
-        }
-      }
+      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E="
     },
     "es5-ext": {
       "version": "0.10.23",
@@ -1505,6 +1543,20 @@
       "integrity": "sha1-8X1OUpksHUXRt3E++81ezQ5+BQY=",
       "dev": true
     },
+    "eslint-config-prettier": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.2.0.tgz",
+      "integrity": "sha1-ykdmOFJ4mnXBD+umc+gCzB7/CF8=",
+      "dev": true,
+      "dependencies": {
+        "get-stdin": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
+          "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=",
+          "dev": true
+        }
+      }
+    },
     "eslint-import-resolver-node": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz",
@@ -1512,24 +1564,10 @@
       "dev": true
     },
     "eslint-module-utils": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.0.0.tgz",
-      "integrity": "sha1-pvjCHZATWHWc3DXbrBmCrh7li84=",
-      "dev": true,
-      "dependencies": {
-        "debug": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
-          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
-          "dev": true
-        },
-        "ms": {
-          "version": "0.7.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
-          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
-          "dev": true
-        }
-      }
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz",
+      "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==",
+      "dev": true
     },
     "eslint-plugin-flowtype": {
       "version": "2.34.0",
@@ -1538,9 +1576,9 @@
       "dev": true
     },
     "eslint-plugin-import": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.3.0.tgz",
-      "integrity": "sha1-N8gB4K2g4pbL3yDD85OstbUq82s=",
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.5.0.tgz",
+      "integrity": "sha512-JoGslQQHFveBnS3oZGKtinynThbTAo2QEpY5PQaD2VUGxhjB6us1wGD9PbwYLnevBdaEbUUfjtEAWmsEu3CtrQ==",
       "dev": true,
       "dependencies": {
         "doctrine": {
@@ -1608,13 +1646,20 @@
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
       "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "acorn": {
+          "version": "5.0.3",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
+          "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
+          "dev": true
+        }
+      }
     },
-    "esprima": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
-      "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
-      "dev": true
+    "esprima-fb": {
+      "version": "3001.1.0-dev-harmony-fb",
+      "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
+      "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
     },
     "espurify": {
       "version": "1.7.0",
@@ -1629,18 +1674,10 @@
       "dev": true
     },
     "esrecurse": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz",
-      "integrity": "sha1-RxO2U2rffyrE8yfVWed1a/9kgiA=",
-      "dev": true,
-      "dependencies": {
-        "estraverse": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz",
-          "integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=",
-          "dev": true
-        }
-      }
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
+      "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
+      "dev": true
     },
     "estraverse": {
       "version": "4.2.0",
@@ -1669,6 +1706,26 @@
       "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
       "dev": true
     },
+    "execa": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+      "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "dev": true
+        },
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+          "dev": true
+        }
+      }
+    },
     "exit-hook": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
@@ -1727,11 +1784,6 @@
       "resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
       "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=",
       "dependencies": {
-        "acorn": {
-          "version": "1.2.2",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
-          "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
-        },
         "isarray": {
           "version": "0.0.1",
           "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@@ -1884,696 +1936,545 @@
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
     "fsevents": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz",
-      "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=",
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
+      "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
       "optional": true,
       "dependencies": {
         "abbrev": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
-          "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
+          "bundled": true,
+          "optional": true
+        },
+        "ajv": {
+          "version": "4.11.8",
+          "bundled": true,
           "optional": true
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
-        },
-        "ansi-styles": {
-          "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
-          "optional": true
+          "bundled": true
         },
         "aproba": {
           "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
-          "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=",
+          "bundled": true,
           "optional": true
         },
         "are-we-there-yet": {
-          "version": "1.1.2",
-          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
-          "integrity": "sha1-gORw6VoIR5T+GJkmLFZnxuiN4bM=",
+          "version": "1.1.4",
+          "bundled": true,
           "optional": true
         },
         "asn1": {
           "version": "0.2.3",
-          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
-          "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+          "bundled": true,
           "optional": true
         },
         "assert-plus": {
           "version": "0.2.0",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
-          "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+          "bundled": true,
           "optional": true
         },
         "asynckit": {
           "version": "0.4.0",
-          "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-          "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+          "bundled": true,
           "optional": true
         },
         "aws-sign2": {
           "version": "0.6.0",
-          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
-          "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+          "bundled": true,
           "optional": true
         },
         "aws4": {
           "version": "1.6.0",
-          "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
-          "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
+          "bundled": true,
           "optional": true
         },
         "balanced-match": {
           "version": "0.4.2",
-          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
-          "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+          "bundled": true
         },
         "bcrypt-pbkdf": {
           "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
-          "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+          "bundled": true,
           "optional": true
         },
         "block-stream": {
           "version": "0.0.9",
-          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
-          "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo="
+          "bundled": true
         },
         "boom": {
           "version": "2.10.1",
-          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
-          "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
+          "bundled": true
         },
         "brace-expansion": {
-          "version": "1.1.6",
-          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
-          "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk="
+          "version": "1.1.7",
+          "bundled": true
         },
         "buffer-shims": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
-          "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
+          "bundled": true
         },
         "caseless": {
-          "version": "0.11.0",
-          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
-          "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=",
+          "version": "0.12.0",
+          "bundled": true,
           "optional": true
         },
-        "chalk": {
-          "version": "1.1.3",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+        "co": {
+          "version": "4.6.0",
+          "bundled": true,
           "optional": true
         },
         "code-point-at": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+          "bundled": true
         },
         "combined-stream": {
           "version": "1.0.5",
-          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-          "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
-        },
-        "commander": {
-          "version": "2.9.0",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
-          "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
-          "optional": true
+          "bundled": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+          "bundled": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-          "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+          "bundled": true
         },
         "core-util-is": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+          "bundled": true
         },
         "cryptiles": {
           "version": "2.0.5",
-          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
-          "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+          "bundled": true,
           "optional": true
         },
         "dashdash": {
           "version": "1.14.1",
-          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
-          "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "debug": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
-          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+          "version": "2.6.8",
+          "bundled": true,
           "optional": true
         },
         "deep-extend": {
-          "version": "0.4.1",
-          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
-          "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=",
+          "version": "0.4.2",
+          "bundled": true,
           "optional": true
         },
         "delayed-stream": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-          "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+          "bundled": true
         },
         "delegates": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+          "bundled": true,
           "optional": true
         },
         "ecc-jsbn": {
           "version": "0.1.1",
-          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
-          "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
-          "optional": true
-        },
-        "escape-string-regexp": {
-          "version": "1.0.5",
-          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-          "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+          "bundled": true,
           "optional": true
         },
         "extend": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
-          "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=",
+          "version": "3.0.1",
+          "bundled": true,
           "optional": true
         },
         "extsprintf": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
-          "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+          "bundled": true
         },
         "forever-agent": {
           "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-          "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+          "bundled": true,
           "optional": true
         },
         "form-data": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz",
-          "integrity": "sha1-icNTQAi5fq2ky7FX1Y9vXfAl6uQ=",
+          "version": "2.1.4",
+          "bundled": true,
           "optional": true
         },
         "fs.realpath": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+          "bundled": true
         },
         "fstream": {
-          "version": "1.0.10",
-          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz",
-          "integrity": "sha1-YE6Kkv4m/9n2+uMDmdSYThqyKCI="
+          "version": "1.0.11",
+          "bundled": true
         },
         "fstream-ignore": {
           "version": "1.0.5",
-          "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
-          "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
+          "bundled": true,
           "optional": true
         },
         "gauge": {
-          "version": "2.7.3",
-          "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz",
-          "integrity": "sha1-HCOFX5YvF7OtPQ3HRD8wRULt/gk=",
-          "optional": true
-        },
-        "generate-function": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
-          "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
-          "optional": true
-        },
-        "generate-object-property": {
-          "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
-          "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+          "version": "2.7.4",
+          "bundled": true,
           "optional": true
         },
         "getpass": {
-          "version": "0.1.6",
-          "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
-          "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=",
+          "version": "0.1.7",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "glob": {
-          "version": "7.1.1",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
-          "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg="
+          "version": "7.1.2",
+          "bundled": true
         },
         "graceful-fs": {
           "version": "4.1.11",
-          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
-          "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+          "bundled": true
         },
-        "graceful-readlink": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
-          "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
+        "har-schema": {
+          "version": "1.0.5",
+          "bundled": true,
           "optional": true
         },
         "har-validator": {
-          "version": "2.0.6",
-          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
-          "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
-          "optional": true
-        },
-        "has-ansi": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-          "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+          "version": "4.2.1",
+          "bundled": true,
           "optional": true
         },
         "has-unicode": {
           "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+          "bundled": true,
           "optional": true
         },
         "hawk": {
           "version": "3.1.3",
-          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
-          "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+          "bundled": true,
           "optional": true
         },
         "hoek": {
           "version": "2.16.3",
-          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
-          "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+          "bundled": true
         },
         "http-signature": {
           "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
-          "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+          "bundled": true,
           "optional": true
         },
         "inflight": {
           "version": "1.0.6",
-          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
+          "bundled": true
         },
         "inherits": {
           "version": "2.0.3",
-          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+          "bundled": true
         },
         "ini": {
           "version": "1.3.4",
-          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
-          "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+          "bundled": true,
           "optional": true
         },
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
-          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs="
-        },
-        "is-my-json-valid": {
-          "version": "2.15.0",
-          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz",
-          "integrity": "sha1-k27do8o8IR/ZjzstPgjaQ/eykVs=",
-          "optional": true
-        },
-        "is-property": {
-          "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
-          "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
-          "optional": true
+          "bundled": true
         },
         "is-typedarray": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-          "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+          "bundled": true,
           "optional": true
         },
         "isarray": {
           "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+          "bundled": true
         },
         "isstream": {
           "version": "0.1.2",
-          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-          "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+          "bundled": true,
           "optional": true
         },
         "jodid25519": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
-          "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+          "bundled": true,
           "optional": true
         },
         "jsbn": {
           "version": "0.1.1",
-          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
-          "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+          "bundled": true,
           "optional": true
         },
         "json-schema": {
           "version": "0.2.3",
-          "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
-          "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+          "bundled": true,
+          "optional": true
+        },
+        "json-stable-stringify": {
+          "version": "1.0.1",
+          "bundled": true,
           "optional": true
         },
         "json-stringify-safe": {
           "version": "5.0.1",
-          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-          "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+          "bundled": true,
           "optional": true
         },
-        "jsonpointer": {
-          "version": "4.0.1",
-          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
-          "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+        "jsonify": {
+          "version": "0.0.0",
+          "bundled": true,
           "optional": true
         },
         "jsprim": {
-          "version": "1.3.1",
-          "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
-          "integrity": "sha1-KnJW9wQSop7jZwqspiWZTE3P8lI=",
-          "optional": true
+          "version": "1.4.0",
+          "bundled": true,
+          "optional": true,
+          "dependencies": {
+            "assert-plus": {
+              "version": "1.0.0",
+              "bundled": true,
+              "optional": true
+            }
+          }
         },
         "mime-db": {
-          "version": "1.26.0",
-          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz",
-          "integrity": "sha1-6v/NDk/Gk1z4E02iRuLmw1MFrf8="
+          "version": "1.27.0",
+          "bundled": true
         },
         "mime-types": {
-          "version": "2.1.14",
-          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz",
-          "integrity": "sha1-9+99l1g/yvO30oK2+LVnnaselO4="
+          "version": "2.1.15",
+          "bundled": true
         },
         "minimatch": {
-          "version": "3.0.3",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
-          "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q="
+          "version": "3.0.4",
+          "bundled": true
         },
         "minimist": {
           "version": "0.0.8",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+          "bundled": true
         },
         "mkdirp": {
           "version": "0.5.1",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
+          "bundled": true
         },
         "ms": {
-          "version": "0.7.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
-          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+          "version": "2.0.0",
+          "bundled": true,
           "optional": true
         },
         "node-pre-gyp": {
-          "version": "0.6.33",
-          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz",
-          "integrity": "sha1-ZArFUZj2qSWXLgwWxKwmoDTV7Mk=",
+          "version": "0.6.36",
+          "bundled": true,
           "optional": true
         },
         "nopt": {
-          "version": "3.0.6",
-          "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
-          "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+          "version": "4.0.1",
+          "bundled": true,
           "optional": true
         },
         "npmlog": {
-          "version": "4.0.2",
-          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz",
-          "integrity": "sha1-0DlQ4OeM4VJ7om0qdZLpNIrD518=",
+          "version": "4.1.0",
+          "bundled": true,
           "optional": true
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+          "bundled": true
         },
         "oauth-sign": {
           "version": "0.8.2",
-          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
-          "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+          "bundled": true,
           "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "bundled": true,
           "optional": true
         },
         "once": {
           "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
+          "bundled": true
         },
-        "path-is-absolute": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-          "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+        "os-homedir": {
+          "version": "1.0.2",
+          "bundled": true,
+          "optional": true
         },
-        "pinkie": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-          "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+        "os-tmpdir": {
+          "version": "1.0.2",
+          "bundled": true,
           "optional": true
         },
-        "pinkie-promise": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-          "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+        "osenv": {
+          "version": "0.1.4",
+          "bundled": true,
+          "optional": true
+        },
+        "path-is-absolute": {
+          "version": "1.0.1",
+          "bundled": true
+        },
+        "performance-now": {
+          "version": "0.2.0",
+          "bundled": true,
           "optional": true
         },
         "process-nextick-args": {
           "version": "1.0.7",
-          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
-          "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+          "bundled": true
         },
         "punycode": {
           "version": "1.4.1",
-          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
-          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "bundled": true,
           "optional": true
         },
         "qs": {
-          "version": "6.3.1",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz",
-          "integrity": "sha1-kYwLO802Z5dyuvE1say0wWUe150=",
+          "version": "6.4.0",
+          "bundled": true,
           "optional": true
         },
         "rc": {
-          "version": "1.1.7",
-          "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
-          "integrity": "sha1-xepWS7B6/5/TpbMukGwdOmWUD+o=",
+          "version": "1.2.1",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "minimist": {
               "version": "1.2.0",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "readable-stream": {
-          "version": "2.2.2",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz",
-          "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=",
-          "optional": true
+          "version": "2.2.9",
+          "bundled": true
         },
         "request": {
-          "version": "2.79.0",
-          "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
-          "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=",
+          "version": "2.81.0",
+          "bundled": true,
           "optional": true
         },
         "rimraf": {
-          "version": "2.5.4",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz",
-          "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ="
+          "version": "2.6.1",
+          "bundled": true
+        },
+        "safe-buffer": {
+          "version": "5.0.1",
+          "bundled": true
         },
         "semver": {
           "version": "5.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
-          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+          "bundled": true,
           "optional": true
         },
         "set-blocking": {
           "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-          "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+          "bundled": true,
           "optional": true
         },
         "signal-exit": {
           "version": "3.0.2",
-          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
-          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+          "bundled": true,
           "optional": true
         },
         "sntp": {
           "version": "1.0.9",
-          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
-          "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+          "bundled": true,
           "optional": true
         },
         "sshpk": {
-          "version": "1.10.2",
-          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz",
-          "integrity": "sha1-1agEziJpVRVjjnmNviMnPeBwpfo=",
+          "version": "1.13.0",
+          "bundled": true,
           "optional": true,
           "dependencies": {
             "assert-plus": {
               "version": "1.0.0",
-              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-              "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+              "bundled": true,
               "optional": true
             }
           }
         },
         "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+          "version": "1.0.1",
+          "bundled": true
         },
         "string-width": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
-          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M="
+          "bundled": true
         },
         "stringstream": {
           "version": "0.0.5",
-          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
-          "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
+          "bundled": true,
           "optional": true
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8="
+          "bundled": true
         },
         "strip-json-comments": {
           "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
-          "optional": true
-        },
-        "supports-color": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "bundled": true,
           "optional": true
         },
         "tar": {
           "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
-          "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE="
+          "bundled": true
         },
         "tar-pack": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz",
-          "integrity": "sha1-MJMYFkGPVa/E0hd1r91nIM7kXa4=",
-          "optional": true,
-          "dependencies": {
-            "once": {
-              "version": "1.3.3",
-              "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
-              "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
-              "optional": true
-            },
-            "readable-stream": {
-              "version": "2.1.5",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz",
-              "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=",
-              "optional": true
-            }
-          }
+          "version": "3.4.0",
+          "bundled": true,
+          "optional": true
         },
         "tough-cookie": {
           "version": "2.3.2",
-          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
-          "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+          "bundled": true,
           "optional": true
         },
         "tunnel-agent": {
-          "version": "0.4.3",
-          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
-          "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
+          "version": "0.6.0",
+          "bundled": true,
           "optional": true
         },
         "tweetnacl": {
           "version": "0.14.5",
-          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
-          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+          "bundled": true,
           "optional": true
         },
         "uid-number": {
           "version": "0.0.6",
-          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
-          "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
+          "bundled": true,
           "optional": true
         },
         "util-deprecate": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+          "bundled": true
         },
         "uuid": {
           "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
-          "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=",
+          "bundled": true,
           "optional": true
         },
         "verror": {
           "version": "1.3.6",
-          "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
-          "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+          "bundled": true,
           "optional": true
         },
         "wide-align": {
-          "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz",
-          "integrity": "sha1-QO3egCpx/qHwcNo+YtzaLnrdlq0=",
+          "version": "1.1.2",
+          "bundled": true,
           "optional": true
         },
         "wrappy": {
           "version": "1.0.2",
-          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
-        },
-        "xtend": {
-          "version": "4.0.1",
-          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
-          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
-          "optional": true
+          "bundled": true
         }
       }
     },
@@ -2607,6 +2508,12 @@
       "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
       "dev": true
     },
+    "get-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+      "dev": true
+    },
     "getpass": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -2811,29 +2718,7 @@
     "inline-process-browser": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz",
-      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=",
-      "dependencies": {
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-        },
-        "readable-stream": {
-          "version": "1.0.34",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
-          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
-        },
-        "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
-        },
-        "through2": {
-          "version": "0.6.5",
-          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
-        }
-      }
+      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI="
     },
     "inquirer": {
       "version": "0.12.0",
@@ -3085,12 +2970,6 @@
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
     },
-    "jodid25519": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
-      "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
-      "optional": true
-    },
     "js-tokens": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
@@ -3100,7 +2979,15 @@
       "version": "3.8.4",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
       "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "esprima": {
+          "version": "3.1.3",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+          "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+          "dev": true
+        }
+      }
     },
     "jsbn": {
       "version": "0.1.1",
@@ -3161,11 +3048,6 @@
       "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz",
       "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=",
       "dependencies": {
-        "esprima-fb": {
-          "version": "3001.1.0-dev-harmony-fb",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
-          "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
-        },
         "source-map": {
           "version": "0.1.31",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz",
@@ -3182,7 +3064,15 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz",
       "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=",
-      "dev": true
+      "dev": true,
+      "dependencies": {
+        "through2": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+          "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+          "dev": true
+        }
+      }
     },
     "latest-version": {
       "version": "2.0.0",
@@ -3207,6 +3097,44 @@
       "resolved": "https://registry.npmjs.org/lie/-/lie-3.0.2.tgz",
       "integrity": "sha1-/9oh17uibzd8rYZdNkmy/Izjn+o="
     },
+    "lint-staged": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.0.0.tgz",
+      "integrity": "sha1-wVZp9ZhhSm5oCQMD4XWnmdSODYU=",
+      "dev": true
+    },
+    "listr": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz",
+      "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=",
+      "dev": true
+    },
+    "listr-silent-renderer": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
+      "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=",
+      "dev": true
+    },
+    "listr-update-renderer": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz",
+      "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=",
+      "dev": true,
+      "dependencies": {
+        "indent-string": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.1.0.tgz",
+          "integrity": "sha1-CP9DNGAziDmbMp5rlTjcejz13n0=",
+          "dev": true
+        }
+      }
+    },
+    "listr-verbose-renderer": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.0.tgz",
+      "integrity": "sha1-RNwBuww0oDxXIVTU0Izemx3FYg8=",
+      "dev": true
+    },
     "load-json-file": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@@ -3279,6 +3207,12 @@
       "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=",
       "dev": true
     },
+    "lodash.chunk": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
+      "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=",
+      "dev": true
+    },
     "lodash.cond": {
       "version": "4.5.2",
       "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
@@ -3339,6 +3273,18 @@
       "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
       "dev": true
     },
+    "log-symbols": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
+      "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
+      "dev": true
+    },
+    "log-update": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz",
+      "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=",
+      "dev": true
+    },
     "lolex": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz",
@@ -3363,10 +3309,9 @@
       "dev": true
     },
     "lru-cache": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz",
-      "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==",
-      "dev": true
+      "version": "2.7.3",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
+      "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
     },
     "lru-queue": {
       "version": "0.1.0",
@@ -3489,9 +3434,9 @@
       "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
     },
     "mongodb": {
-      "version": "2.2.28",
-      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.28.tgz",
-      "integrity": "sha1-2P9FdUNm4Dlz+iWb9PEUR4WNplc=",
+      "version": "2.2.29",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.29.tgz",
+      "integrity": "sha512-MrQvIsN6zN80I4hdFo8w46w51cIqD2FJBGsUfApX9GmjXA1aCclEAJbOHaQWjCtabeWq57S3ECzqEKg/9bdBhA==",
       "dependencies": {
         "readable-stream": {
           "version": "2.2.7",
@@ -3501,9 +3446,9 @@
       }
     },
     "mongodb-core": {
-      "version": "2.1.12",
-      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.12.tgz",
-      "integrity": "sha1-FTEZJRG8Fu8WCsauDMRndv/YRR0="
+      "version": "2.1.13",
+      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.13.tgz",
+      "integrity": "sha512-mbcvqLLZwVcpTrsfBDY3hRNk2SDNJWOvKKxFJSc0pnUBhYojymBc/L0THfQsWwKJrkb2nIXSjfFll1mG/I5OqQ=="
     },
     "moniker": {
       "version": "0.1.2",
@@ -3561,11 +3506,6 @@
       "resolved": "https://registry.npmjs.org/nedb-core/-/nedb-core-3.0.6.tgz",
       "integrity": "sha1-4BrQ8iciF/UQkY2YY5MwPotMjTI=",
       "dependencies": {
-        "async": {
-          "version": "2.4.1",
-          "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
-          "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
-        },
         "mkdirp": {
           "version": "0.5.1",
           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
@@ -3685,6 +3625,24 @@
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
       "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk="
     },
+    "npm-path": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz",
+      "integrity": "sha1-Fc/04ciaONp39W9gVbJPl137K74=",
+      "dev": true
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true
+    },
+    "npm-which": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
+      "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=",
+      "dev": true
+    },
     "nullthrows": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.0.0.tgz",
@@ -3764,6 +3722,12 @@
       "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
       "dev": true
     },
+    "ora": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
+      "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=",
+      "dev": true
+    },
     "os-homedir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
@@ -3791,6 +3755,12 @@
       "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz",
       "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY="
     },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
+    },
     "p-limit": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz",
@@ -3803,6 +3773,12 @@
       "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
       "dev": true
     },
+    "p-map": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.1.1.tgz",
+      "integrity": "sha1-BfXkrpegaDcbwqXMhr+9vBnErno=",
+      "dev": true
+    },
     "package-hash": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-1.2.0.tgz",
@@ -3854,6 +3830,12 @@
       "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
       "dev": true
     },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
     "path-parse": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
@@ -3977,6 +3959,12 @@
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
           "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
           "dev": true
+        },
+        "lru-cache": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
+          "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
+          "dev": true
         }
       }
     },
@@ -3997,6 +3985,12 @@
       "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
       "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
     },
+    "prettier": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.4.4.tgz",
+      "integrity": "sha512-GuuPazIvjW1DG26yLQgO+nagmRF/h9M4RaCtZWqu/eFW7csdZkQEwPJUeXX10d+LzmCnR9DuIZndqIOn3p2YoA==",
+      "dev": true
+    },
     "pretty-ms": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz",
@@ -4067,9 +4061,28 @@
       "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM="
     },
     "randomatic": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz",
-      "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs="
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
+      "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
+      "dependencies": {
+        "is-number": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+          "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+          "dependencies": {
+            "kind-of": {
+              "version": "3.2.2",
+              "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="
+            }
+          }
+        },
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc="
+        }
+      }
     },
     "range-parser": {
       "version": "1.2.0",
@@ -4114,9 +4127,9 @@
       "dev": true
     },
     "readable-stream": {
-      "version": "2.2.11",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz",
-      "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q=="
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.2.tgz",
+      "integrity": "sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00="
     },
     "readdirp": {
       "version": "2.1.0",
@@ -4239,9 +4252,15 @@
       "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA="
     },
     "require_optional": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.0.tgz",
-      "integrity": "sha1-UqhhN6hJco62ClVTNhf4+RT1mr8="
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g=="
+    },
+    "require-from-string": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz",
+      "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=",
+      "dev": true
     },
     "require-precompiled": {
       "version": "0.1.0",
@@ -4313,10 +4332,24 @@
       "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
       "dev": true
     },
+    "rxjs": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.1.tgz",
+      "integrity": "sha1-ti91fyeURdJloYpY+wpw3JDpFiY=",
+      "dev": true,
+      "dependencies": {
+        "symbol-observable": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz",
+          "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=",
+          "dev": true
+        }
+      }
+    },
     "safe-buffer": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
-      "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+      "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
     },
     "samsam": {
       "version": "1.1.2",
@@ -4436,7 +4469,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#32411bde64ff3bbeaad281798fe6921c00185ea4"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#b3f7fc088417384cf16f93c1025523b6d8008f7d"
     },
     "spawn-sync": {
       "version": "1.0.15",
@@ -4475,9 +4508,9 @@
       "dev": true
     },
     "sshpk": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz",
-      "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=",
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
+      "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
       "dependencies": {
         "assert-plus": {
           "version": "1.0.0",
@@ -4492,6 +4525,12 @@
       "integrity": "sha1-lAy4L8z6hOj/Lz/fKT/ngBa+zNE=",
       "dev": true
     },
+    "staged-git-files": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-0.0.4.tgz",
+      "integrity": "sha1-15fhtVHKemOd7AI33G60u5vhfTU=",
+      "dev": true
+    },
     "statuses": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
@@ -4514,15 +4553,21 @@
       "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
       "dev": true
     },
+    "stream-to-observable": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz",
+      "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=",
+      "dev": true
+    },
     "streamsearch": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
       "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
     },
     "string_decoder": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz",
-      "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk="
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ=="
     },
     "string-length": {
       "version": "1.0.1",
@@ -4558,6 +4603,12 @@
       "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
       "dev": true
     },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
+    },
     "strip-indent": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
@@ -4576,6 +4627,12 @@
       "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
       "dev": true,
       "dependencies": {
+        "async": {
+          "version": "1.5.2",
+          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+          "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+          "dev": true
+        },
         "form-data": {
           "version": "1.0.0-rc4",
           "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
@@ -4632,21 +4689,11 @@
       "resolved": "https://registry.npmjs.org/tape/-/tape-3.6.1.tgz",
       "integrity": "sha1-SJPdU+KApfWMDOswwsDrs7zVHh8=",
       "dependencies": {
-        "deep-equal": {
-          "version": "0.2.2",
-          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz",
-          "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0="
-        },
         "glob": {
           "version": "3.2.11",
           "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
           "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0="
         },
-        "lru-cache": {
-          "version": "2.7.3",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
-          "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
-        },
         "minimatch": {
           "version": "0.3.0",
           "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
@@ -4672,10 +4719,26 @@
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
     },
     "through2": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
-      "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
-      "dev": true
+      "version": "0.6.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+      "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        }
+      }
     },
     "time-require": {
       "version": "0.1.2",
@@ -4840,29 +4903,7 @@
     "unreachable-branch-transform": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz",
-      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=",
-      "dependencies": {
-        "isarray": {
-          "version": "0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-        },
-        "readable-stream": {
-          "version": "1.0.34",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
-          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
-        },
-        "string_decoder": {
-          "version": "0.10.31",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
-        },
-        "through2": {
-          "version": "0.6.5",
-          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-          "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg="
-        }
-      }
+      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo="
     },
     "unzip-response": {
       "version": "1.0.2",
@@ -4912,9 +4953,9 @@
       "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg="
     },
     "uuid": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
-      "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE="
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
+      "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
     },
     "v8flags": {
       "version": "2.1.1",
diff --git a/package.json b/package.json
index c00b37d1..5841a412 100644
--- a/package.json
+++ b/package.json
@@ -30,21 +30,41 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
+    "git-add": "git add",
     "lint": "eslint --fix --max-warnings 0 -- .",
+    "lint-staged": "lint-staged",
     "migrate-files-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-files-to-nedb": "babel-node ./src/scripts/migrateFilesToDatabase nedb",
     "prebuild": "npm run build:clean",
-    "start:warn": "babel-node ./src/main.js --trace-warnings",
+    "prettify": "prettier --single-quote --trailing-comma all --write",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
     "start:debug": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
     "start:prod": "node ./dist/main.js",
+    "start:warn": "babel-node ./src/main.js --trace-warnings",
     "test": "ava --serial --no-cache",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",
     "watch": "babel ./src --out-dir ./dist --watch"
   },
+  "lint-staged": {
+    "examples/**/*.js": [
+      "lint",
+      "prettify",
+      "git add"
+    ],
+    "src/**/*.js": [
+      "lint",
+      "prettify",
+      "git add"
+    ],
+    "test/**/*.js": [
+      "lint",
+      "prettify",
+      "git add"
+    ]
+  },
   "pre-commit": [
-    "lint",
+    "lint-staged",
     "test"
   ],
   "ava": {
@@ -102,12 +122,15 @@
     "babel-register": "^6.18.0",
     "eslint": "^3.11.0",
     "eslint-config-airbnb-base": "^10.0.1",
+    "eslint-config-prettier": "^2.2.0",
     "eslint-plugin-flowtype": "^2.28.2",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-sorting": "^0.3.0",
     "flow-bin": "^0.37.4",
+    "lint-staged": "^4.0.0",
     "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
+    "prettier": "^1.4.4",
     "rimraf": "^2.5.4",
     "sinon": "^1.17.7",
     "supertest": "^2.0.1",
diff --git a/src/OAuthModel.js b/src/OAuthModel.js
index dbcbb9ca..a96ef4aa 100644
--- a/src/OAuthModel.js
+++ b/src/OAuthModel.js
@@ -2,12 +2,7 @@
 
 import oauthClients from './oauthClients.json';
 
-import type {
-  Client,
-  IUserRepository,
-  TokenObject,
-  User,
-} from './types';
+import type { Client, IUserRepository, TokenObject, User } from './types';
 
 const OAUTH_CLIENTS = oauthClients;
 
@@ -40,8 +35,9 @@ class OauthModel {
   };
 
   getClient = (clientId: string, clientSecret: string): ?Client =>
-    OAUTH_CLIENTS.find((client: Client): boolean =>
-      client.clientId === clientId && client.clientSecret === clientSecret,
+    OAUTH_CLIENTS.find(
+      (client: Client): boolean =>
+        client.clientId === clientId && client.clientSecret === clientSecret,
     );
 
   getUser = async (username: string, password: string): Promise =>
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 97875188..7c846549 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -14,46 +14,52 @@ import nullthrows from 'nullthrows';
 import multer from 'multer';
 import HttpError from './lib/HttpError';
 
-const maybe = (middleware: Middleware, condition: boolean): Middleware =>
-  (request: $Request, response: $Response, next: NextFunction) => {
-    if (condition) {
-      middleware(request, response, next);
-    } else {
-      next();
-    }
-  };
-
-const injectUserMiddleware = (container: Container): Middleware =>
-  (request: $Request, response: $Response, next: NextFunction) => {
-    const oauthInfo = response.locals.oauth;
-    if (oauthInfo) {
-      const token = (oauthInfo: any).token;
-      const user = token && token.user;
-      // eslint-disable-next-line no-param-reassign
-      (request: any).user = user;
-      container.constitute('UserRepository').setCurrentUser(user);
-    }
+const maybe = (middleware: Middleware, condition: boolean): Middleware => (
+  request: $Request,
+  response: $Response,
+  next: NextFunction,
+) => {
+  if (condition) {
+    middleware(request, response, next);
+  } else {
     next();
-  };
+  }
+};
+
+const injectUserMiddleware = (container: Container): Middleware => (
+  request: $Request,
+  response: $Response,
+  next: NextFunction,
+) => {
+  const oauthInfo = response.locals.oauth;
+  if (oauthInfo) {
+    const token = (oauthInfo: any).token;
+    const user = token && token.user;
+    // eslint-disable-next-line no-param-reassign
+    (request: any).user = user;
+    container.constitute('UserRepository').setCurrentUser(user);
+  }
+  next();
+};
 
 // in old codebase there was _keepAlive() function in controllers , which
 // prevents of closing server-sent-events stream if there aren't events for
 // a long time, but according to the docs sse keep connection alive automatically.
 // if there will be related issues in the future, we can return _keepAlive() back.
-const serverSentEventsMiddleware = (): Middleware =>
-  (request: $Request, response: $Response, next: NextFunction) => {
-    request.socket.setNoDelay();
-    response.writeHead(
-      200,
-      {
-        'Cache-Control': 'no-cache',
-        Connection: 'keep-alive',
-        'Content-Type': 'text/event-stream',
-      },
-    );
+const serverSentEventsMiddleware = (): Middleware => (
+  request: $Request,
+  response: $Response,
+  next: NextFunction,
+) => {
+  request.socket.setNoDelay();
+  response.writeHead(200, {
+    'Cache-Control': 'no-cache',
+    Connection: 'keep-alive',
+    'Content-Type': 'text/event-stream',
+  });
 
-    next();
-  };
+  next();
+};
 
 export default (
   app: $Application,
@@ -61,12 +67,15 @@ export default (
   controllers: Array,
   settings: Settings,
 ) => {
-  const filesMiddleware = (allowedUploads: ?Array<{
-    maxCount: number,
-    name: string,
-  }> = []): Middleware => nullthrows(allowedUploads).length
-    ? multer().fields(allowedUploads)
-    : multer().any();
+  const filesMiddleware = (
+    allowedUploads: ?Array<{
+      maxCount: number,
+      name: string,
+    }> = [],
+  ): Middleware =>
+    nullthrows(allowedUploads).length
+      ? multer().fields(allowedUploads)
+      : multer().any();
 
   const oauth = container.constitute('OAuthServer');
 
@@ -96,11 +105,13 @@ export default (
         injectUserMiddleware(container),
         maybe(filesMiddleware(allowedUploads), allowedUploads),
         async (request: $Request, response: $Response): Promise => {
-          const argumentNames = (route.match(/:[\w]*/g) || []).map(
-            (argumentName: string): string => argumentName.replace(':', ''),
+          const argumentNames = (route.match(/:[\w]*/g) || [])
+            .map((argumentName: string): string =>
+              argumentName.replace(':', ''),
+            );
+          const values = argumentNames.map(
+            (argument: string): string => request.params[argument],
           );
-          const values = argumentNames
-            .map((argument: string): string => request.params[argument]);
 
           let controllerInstance = container.constitute(controllerName);
 
@@ -133,15 +144,15 @@ export default (
             if (functionResult.then) {
               const result = !serverSentEvents
                 ? await Promise.race([
-                  functionResult,
-                  new Promise(
-                    (resolve: () => void, reject: () => void): number =>
-                      setTimeout(
-                        (): void => reject(new Error('timeout')),
-                        settings.API_TIMEOUT,
-                      ),
-                  ),
-                ])
+                    functionResult,
+                    new Promise(
+                      (resolve: () => void, reject: () => void): number =>
+                        setTimeout(
+                          (): void => reject(new Error('timeout')),
+                          settings.API_TIMEOUT,
+                        ),
+                    ),
+                  ])
                 : await functionResult;
 
               response
@@ -157,7 +168,8 @@ export default (
               ok: false,
             });
           }
-        });
+        },
+      );
     });
   });
 
@@ -165,18 +177,18 @@ export default (
     response.sendStatus(404);
   });
 
-  (app: any).use((
-    error: Error,
-    request: $Request,
-    response: $Response,
-    // eslint-disable-next-line no-unused-vars
-    next: NextFunction,
-  ) => {
-    response
-      .status(400)
-      .json({
+  (app: any).use(
+    (
+      error: Error,
+      request: $Request,
+      response: $Response,
+      // eslint-disable-next-line no-unused-vars
+      next: NextFunction,
+    ) => {
+      response.status(400).json({
         error: error.code ? error.code : error,
         ok: false,
       });
-  });
+    },
+  );
 };
diff --git a/src/app.js b/src/app.js
index 25cf4462..2104a100 100644
--- a/src/app.js
+++ b/src/app.js
@@ -42,11 +42,13 @@ export default (
   };
 
   if (settings.LOG_REQUESTS) {
-    app.use(morgan(
-      '[:date[iso]] :remote-addr - :remote-user ":method :url ' +
-      'HTTP/:http-version" :status :res[content-length] ":referrer" ' +
-      '":user-agent"',
-    ));
+    app.use(
+      morgan(
+        '[:date[iso]] :remote-addr - :remote-user ":method :url ' +
+          'HTTP/:http-version" :status :res[content-length] ":referrer" ' +
+          '":user-agent"',
+      ),
+    );
   }
 
   app.use(bodyParser.json());
diff --git a/src/controllers/DeviceClaimsController.js b/src/controllers/DeviceClaimsController.js
index 96b94037..dd40720b 100644
--- a/src/controllers/DeviceClaimsController.js
+++ b/src/controllers/DeviceClaimsController.js
@@ -25,14 +25,10 @@ class DeviceClaimsController extends Controller {
   @httpVerb('post')
   @route('/v1/device_claims')
   async createClaimCode(): Promise<*> {
-    const claimCode = this._claimCodeManager.createClaimCode(
-      this.user.id,
-    );
+    const claimCode = this._claimCodeManager.createClaimCode(this.user.id);
 
     const devices = await this._deviceManager.getAll();
-    const deviceIDs = devices.map(
-      (device: Device): string => device.deviceID,
-    );
+    const deviceIDs = devices.map((device: Device): string => device.deviceID);
     return this.ok({ claim_code: claimCode, device_ids: deviceIDs });
   }
 }
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index 8f972cdd..638684bf 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -74,8 +74,8 @@ class DevicesController extends Controller {
   async getDevices(): Promise<*> {
     try {
       const devices = await this._deviceManager.getAll();
-      return this.ok(devices.map((device: Device): DeviceAPIType =>
-        deviceToAPI(device)),
+      return this.ok(
+        devices.map((device: Device): DeviceAPIType => deviceToAPI(device)),
       );
     } catch (error) {
       // I wish we could return no devices found but meh :/
@@ -92,10 +92,7 @@ class DevicesController extends Controller {
 
   @httpVerb('get')
   @route('/v1/devices/:deviceID/:varName/')
-  async getVariableValue(
-    deviceID: string,
-    varName: string,
-  ): Promise<*> {
+  async getVariableValue(deviceID: string, varName: string): Promise<*> {
     try {
       const varValue = await this._deviceManager.getVariableValue(
         deviceID,
@@ -164,17 +161,13 @@ class DevicesController extends Controller {
       throw new Error('Firmware file not provided');
     }
 
-    const file =
-      this.request.files &&
-      (this.request.files: any).file[0];
+    const file = this.request.files && (this.request.files: any).file[0];
 
     if (
-      file && (
-        file.originalname === 'binary' || file.originalname.endsWith('.bin')
-      )
+      file &&
+      (file.originalname === 'binary' || file.originalname.endsWith('.bin'))
     ) {
-      const flashResult = await this._deviceManager
-        .flashBinary(deviceID, file);
+      const flashResult = await this._deviceManager.flashBinary(deviceID, file);
 
       return this.ok({ id: deviceID, status: flashResult.status });
     }
diff --git a/src/controllers/OauthClientsController.js b/src/controllers/OauthClientsController.js
index 793a6db7..efcfe1c8 100644
--- a/src/controllers/OauthClientsController.js
+++ b/src/controllers/OauthClientsController.js
@@ -8,21 +8,18 @@ import route from '../decorators/route';
 class OauthClientsController extends Controller {
   @httpVerb('post')
   @route('/v1/products/:productIDorSlug/clients/')
-  // eslint-disable-next-line class-methods-use-this
   async createClient(): Promise<*> {
     throw new HttpError('not supported in the current server version');
   }
 
   @httpVerb('put')
   @route('/v1/products/:productIDorSlug/clients/:clientID')
-  // eslint-disable-next-line class-methods-use-this
   async editClient(): Promise<*> {
     throw new HttpError('not supported in the current server version');
   }
 
   @httpVerb('delete')
   @route('/v1/products/:productIDorSlug/clients/:clientID')
-  // eslint-disable-next-line class-methods-use-this
   async deleteClient(): Promise<*> {
     throw new HttpError('not supported in the current server version');
   }
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 2ea2632a..5bd0d0eb 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -19,49 +19,38 @@ class ProductsController extends Controller {
 
   @httpVerb('get')
   @route('/v1/products')
-  // eslint-disable-next-line class-methods-use-this
   async getProducts(): Promise<*> {
     throw new HttpError('not supported in the current server version');
   }
 
   @httpVerb('post')
   @route('/v1/products')
-  // eslint-disable-next-line class-methods-use-this
   async createProduct(): Promise<*> {
     throw new HttpError('not supported in the current server version');
   }
 
   @httpVerb('get')
   @route('/v1/products/:productIdOrSlug')
-  async getProduct(
-    productIdOrSlug: string,
-  ): Promise<*> {
+  async getProduct(productIdOrSlug: string): Promise<*> {
     throw new HttpError('Not implemented');
   }
 
   @httpVerb('post')
   @route('/v1/products/:productIdOrSlug/device_claims')
-  // eslint-disable-next-line class-methods-use-this
-  async generateClaimCode(
-    productIdOrSlug: string,
-  ): Promise<*> {
+  async generateClaimCode(productIdOrSlug: string): Promise<*> {
     throw new HttpError('not supported in the current server version');
   }
 
   @httpVerb('get')
   @route('/v1/products/:productIdOrSlug/firmware')
-  async getFirmware(
-    productIdOrSlug: string,
-  ): Promise<*> {
+  async getFirmware(productIdOrSlug: string): Promise<*> {
     throw new HttpError('Not implemented');
   }
 
   // {version: number, name: 'current', binary: File, title: string, description: string}
   @httpVerb('post')
   @route('/v1/products/:productIdOrSlug/firmware')
-  async getFirmware(
-    productIdOrSlug: string,
-  ): Promise<*> {
+  async getFirmware(productIdOrSlug: string): Promise<*> {
     /*
     {
     "updated_at": "2017-01-23T05:55:11.592Z",
@@ -99,9 +88,7 @@ class ProductsController extends Controller {
 
   @httpVerb('get')
   @route('/v1/products/:productIdOrSlug/devices')
-  async getDevices(
-    productIdOrSlug: string,
-  ): Promise<*> {
+  async getDevices(productIdOrSlug: string): Promise<*> {
     throw new HttpError('Not implemented');
   }
 
@@ -110,7 +97,7 @@ class ProductsController extends Controller {
   async setFirmwareVersion(
     productIdOrSlug: string,
     deviceID: string,
-    body: {desired_firmware_version: number},
+    body: { desired_firmware_version: number },
   ): Promise<*> {
     /*
     {
@@ -124,7 +111,6 @@ class ProductsController extends Controller {
 
   @httpVerb('delete')
   @route('/v1/products/:productIdOrSlug/devices/:deviceID')
-  // eslint-disable-next-line class-methods-use-this
   async removeDeviceFromProduct(
     productIdOrSlug: string,
     deviceID: string,
@@ -134,9 +120,7 @@ class ProductsController extends Controller {
 
   @httpVerb('get')
   @route('/v1/products/:productIdOrSlug/config')
-  async getConfig(
-    productIdOrSlug: string,
-  ): Promise<*> {
+  async getConfig(productIdOrSlug: string): Promise<*> {
     /*
     {
       "product_configuration": [
@@ -154,16 +138,12 @@ class ProductsController extends Controller {
 
   @httpVerb('get')
   @route('/v1/products/:productIdOrSlug/events/:eventPrefix?*')
-  async getEvents(
-    productIdOrSlug: string,
-    eventName: string,
-  ): Promise<*> {
+  async getEvents(productIdOrSlug: string, eventName: string): Promise<*> {
     throw new HttpError('Not implemented');
   }
 
   @httpVerb('delete')
   @route('/v1/products/:productIdOrSlug/team/:username')
-  // eslint-disable-next-line class-methods-use-this
   async removeTeamMember(
     productIdOrSlug: string,
     username: string,
diff --git a/src/controllers/UsersController.js b/src/controllers/UsersController.js
index ed634efd..d3dd3535 100644
--- a/src/controllers/UsersController.js
+++ b/src/controllers/UsersController.js
@@ -1,9 +1,6 @@
 // @flow
 
-import type {
-  IUserRepository,
-  UserCredentials,
-} from '../types';
+import type { IUserRepository, UserCredentials } from '../types';
 
 import basicAuthParser from 'basic-auth-parser';
 import Controller from './Controller';
@@ -25,16 +22,15 @@ class UsersController extends Controller {
   @anonymous()
   async createUser(userCredentials: UserCredentials): Promise<*> {
     try {
-      const isUserNameInUse =
-        await this._userRepository.isUserNameInUse(userCredentials.username);
+      const isUserNameInUse = await this._userRepository.isUserNameInUse(
+        userCredentials.username,
+      );
 
       if (isUserNameInUse) {
         throw new HttpError('user with the username already exists');
       }
 
-      await this._userRepository.createWithCredentials(
-        userCredentials,
-      );
+      await this._userRepository.createWithCredentials(userCredentials);
 
       return this.ok({ ok: true });
     } catch (error) {
@@ -49,10 +45,7 @@ class UsersController extends Controller {
     const { username, password } = basicAuthParser(
       this.request.get('authorization'),
     );
-    const user = await this._userRepository.validateLogin(
-      username,
-      password,
-    );
+    const user = await this._userRepository.validateLogin(username, password);
 
     this._userRepository.deleteAccessToken(user.id, token);
 
diff --git a/src/controllers/types.js b/src/controllers/types.js
index c32a21e3..c3144506 100644
--- a/src/controllers/types.js
+++ b/src/controllers/types.js
@@ -1,12 +1,14 @@
 // @flow
 
-export type HttpResult = {
-  data: ?TType,
-  status: number,
-} | {
-  data: {
-    error: string,
-    ok: false,
-  },
-  status: number,
-};
+export type HttpResult =
+  | {
+      data: ?TType,
+      status: number,
+    }
+  | {
+      data: {
+        error: string,
+        ok: false,
+      },
+      status: number,
+    };
diff --git a/src/decorators/allowUpload.js b/src/decorators/allowUpload.js
index 7543cc0e..04774b08 100644
--- a/src/decorators/allowUpload.js
+++ b/src/decorators/allowUpload.js
@@ -7,16 +7,19 @@ import type Controller from '../controllers/Controller';
 export default (
   fileName: ?string = undefined,
   maxCount: number = 0,
-): Decorator =>
-  (target: Controller, name: $Keys, descriptor: Descriptor): Descriptor => {
-    const allowedUploads = (target: any)[name].allowedUploads || [];
-    if (fileName) {
-      allowedUploads.push({
-        maxCount,
-        name: fileName,
-      });
-    }
+): Decorator => (
+  target: Controller,
+  name: $Keys,
+  descriptor: Descriptor,
+): Descriptor => {
+  const allowedUploads = (target: any)[name].allowedUploads || [];
+  if (fileName) {
+    allowedUploads.push({
+      maxCount,
+      name: fileName,
+    });
+  }
 
-    (target: any)[name].allowedUploads = allowedUploads;
-    return descriptor;
-  };
+  (target: any)[name].allowedUploads = allowedUploads;
+  return descriptor;
+};
diff --git a/src/decorators/anonymous.js b/src/decorators/anonymous.js
index a66c468a..eb09c694 100644
--- a/src/decorators/anonymous.js
+++ b/src/decorators/anonymous.js
@@ -4,8 +4,11 @@ import type { Decorator, Descriptor } from './types';
 import type Controller from '../controllers/Controller';
 
 /* eslint-disable no-param-reassign */
-export default (): Decorator =>
-  (target: Controller, name: string, descriptor: Descriptor): Descriptor => {
-    (target: any)[name].anonymous = true;
-    return descriptor;
-  };
+export default (): Decorator => (
+  target: Controller,
+  name: string,
+  descriptor: Descriptor,
+): Descriptor => {
+  (target: any)[name].anonymous = true;
+  return descriptor;
+};
diff --git a/src/decorators/httpVerb.js b/src/decorators/httpVerb.js
index bc1df0bb..9676fc5a 100644
--- a/src/decorators/httpVerb.js
+++ b/src/decorators/httpVerb.js
@@ -4,8 +4,11 @@ import type { Decorator, Descriptor, HttpVerb } from './types';
 import type Controller from '../controllers/Controller';
 
 /* eslint-disable no-param-reassign */
-export default (httpVerb: HttpVerb): Decorator =>
-  (target: Controller, name: $Keys, descriptor: Descriptor): Descriptor => {
-    (target: any)[name].httpVerb = httpVerb;
-    return descriptor;
-  };
+export default (httpVerb: HttpVerb): Decorator => (
+  target: Controller,
+  name: $Keys,
+  descriptor: Descriptor,
+): Descriptor => {
+  (target: any)[name].httpVerb = httpVerb;
+  return descriptor;
+};
diff --git a/src/decorators/route.js b/src/decorators/route.js
index 2cb850ce..03a889f6 100644
--- a/src/decorators/route.js
+++ b/src/decorators/route.js
@@ -4,8 +4,11 @@ import type { Decorator, Descriptor } from './types';
 import type Controller from '../controllers/Controller';
 
 /* eslint-disable no-param-reassign */
-export default (route: string): Decorator =>
-  (target: Controller, name: string, descriptor: Descriptor): Descriptor => {
-    (target: any)[name].route = route;
-    return descriptor;
-  };
+export default (route: string): Decorator => (
+  target: Controller,
+  name: string,
+  descriptor: Descriptor,
+): Descriptor => {
+  (target: any)[name].route = route;
+  return descriptor;
+};
diff --git a/src/decorators/serverSentEvents.js b/src/decorators/serverSentEvents.js
index 22a9ae92..3b2da200 100644
--- a/src/decorators/serverSentEvents.js
+++ b/src/decorators/serverSentEvents.js
@@ -4,8 +4,11 @@ import type { Decorator, Descriptor } from './types';
 import type Controller from '../controllers/Controller';
 
 /* eslint-disable no-param-reassign */
-export default (): Decorator =>
-  (target: Controller, name: string, descriptor: Descriptor): Descriptor => {
-    (target: any)[name].serverSentEvents = true;
-    return descriptor;
-  };
+export default (): Decorator => (
+  target: Controller,
+  name: string,
+  descriptor: Descriptor,
+): Descriptor => {
+  (target: any)[name].serverSentEvents = true;
+  return descriptor;
+};
diff --git a/src/decorators/types.js b/src/decorators/types.js
index 0fd09f2e..34449e88 100644
--- a/src/decorators/types.js
+++ b/src/decorators/types.js
@@ -7,36 +7,36 @@ export type Decorator = (
 ) => Descriptor;
 
 export type Descriptor = {
-    configurable: boolean,
-    enumerable: boolean,
-    value: Function,
-    writeable: boolean,
+  configurable: boolean,
+  enumerable: boolean,
+  value: Function,
+  writeable: boolean,
 };
 
 export type HttpVerb =
-  'checkout' |
-  'connect' |
-  'copy' |
-  'deleteById' |
-  'head' |
-  'get' |
-  'lock' |
-  'm-search' |
-  'merge' |
-  'mkactivity' |
-  'mkcol' |
-  'move' |
-  'notify' |
-  'options' |
-  'patch' |
-  'post' |
-  'propfind' |
-  'proppatch' |
-  'purge' |
-  'put' |
-  'report' |
-  'search' |
-  'subscribe' |
-  'trace' |
-  'unlock' |
-  'unsubscribe';
+  | 'checkout'
+  | 'connect'
+  | 'copy'
+  | 'deleteById'
+  | 'head'
+  | 'get'
+  | 'lock'
+  | 'm-search'
+  | 'merge'
+  | 'mkactivity'
+  | 'mkcol'
+  | 'move'
+  | 'notify'
+  | 'options'
+  | 'patch'
+  | 'post'
+  | 'propfind'
+  | 'proppatch'
+  | 'purge'
+  | 'put'
+  | 'report'
+  | 'search'
+  | 'subscribe'
+  | 'trace'
+  | 'unlock'
+  | 'unsubscribe';
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index ac9a73e0..b210d322 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -21,8 +21,7 @@ import EventManager from './managers/EventManager';
 import PermissionManager from './managers/PermissionManager';
 import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileRepository';
 import NeDb from './repository/NeDb';
-import DeviceAttributeDatabaseRepository from
-  './repository/DeviceAttributeDatabaseRepository';
+import DeviceAttributeDatabaseRepository from './repository/DeviceAttributeDatabaseRepository';
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
@@ -61,113 +60,55 @@ export default (container: Container, newSettings: Settings) => {
     ['OAuthModel'],
   );
 
-  container.bindClass(
-    'OAuthModel',
-    OAuthModel,
-    ['UserRepository'],
-  );
+  container.bindClass('OAuthModel', OAuthModel, ['UserRepository']);
 
-  container.bindClass(
-    'OAuthServer',
-    OAuthServer,
-    ['OAUTH_SETTINGS'],
-  );
+  container.bindClass('OAuthServer', OAuthServer, ['OAUTH_SETTINGS']);
 
-  container.bindClass(
-    'Database',
-    NeDb,
-    ['DATABASE_PATH'],
-  );
+  container.bindClass('Database', NeDb, ['DATABASE_PATH']);
 
   // lib
-  container.bindClass(
-    'WebhookLogger',
-    WebhookLogger,
-    [],
-  );
+  container.bindClass('WebhookLogger', WebhookLogger, []);
 
   // controllers
-  container.bindClass(
-    'DeviceClaimsController',
-    DeviceClaimsController,
-    [
-      'DeviceManager',
-      'ClaimCodeManager',
-    ],
-  );
-  container.bindClass(
-    'DevicesController',
-    DevicesController,
-    ['DeviceManager'],
-  );
-  container.bindClass(
-    'EventsController',
-    EventsController,
-    ['EventManager'],
-  );
-  container.bindClass(
-    'PermissionManager',
-    PermissionManager,
-    [
-      'DeviceAttributeRepository',
-      'UserRepository',
-      'WebhookRepository',
-      'OAuthServer',
-    ],
-  );
-  container.bindClass(
-    'OauthClientsController',
-    OauthClientsController,
-    [],
-  );
-  container.bindClass(
-    'ProductsController',
-    ProductsController,
-    [],
-  );
-  container.bindClass(
-    'ProvisioningController',
-    ProvisioningController,
-    ['DeviceManager'],
-  );
-  container.bindClass(
-    'UsersController',
-    UsersController,
-    ['UserRepository'],
-  );
-  container.bindClass(
-    'WebhooksController',
-    WebhooksController,
-    ['WebhookManager'],
-  );
-
-  // managers
-  container.bindClass(
+  container.bindClass('DeviceClaimsController', DeviceClaimsController, [
     'DeviceManager',
-    DeviceManager,
-    [
-      'DeviceAttributeRepository',
-      'DeviceFirmwareRepository',
-      'DeviceKeyRepository',
-      'PermissionManager',
-      'EventPublisher',
-    ],
-  );
-  container.bindClass(
-    'EventManager',
-    EventManager,
-    ['EventPublisher'],
-  );
-  container.bindClass(
+    'ClaimCodeManager',
+  ]);
+  container.bindClass('DevicesController', DevicesController, [
+    'DeviceManager',
+  ]);
+  container.bindClass('EventsController', EventsController, ['EventManager']);
+  container.bindClass('PermissionManager', PermissionManager, [
+    'DeviceAttributeRepository',
+    'UserRepository',
+    'WebhookRepository',
+    'OAuthServer',
+  ]);
+  container.bindClass('OauthClientsController', OauthClientsController, []);
+  container.bindClass('ProductsController', ProductsController, []);
+  container.bindClass('ProvisioningController', ProvisioningController, [
+    'DeviceManager',
+  ]);
+  container.bindClass('UsersController', UsersController, ['UserRepository']);
+  container.bindClass('WebhooksController', WebhooksController, [
     'WebhookManager',
-    WebhookManager,
-    [
-      'EventPublisher',
-      'PermissionManager',
-      'WebhookLogger',
-      'WebhookRepository',
-    ],
-  );
+  ]);
+
+  // managers
+  container.bindClass('DeviceManager', DeviceManager, [
+    'DeviceAttributeRepository',
+    'DeviceFirmwareRepository',
+    'DeviceKeyRepository',
+    'PermissionManager',
+    'EventPublisher',
+  ]);
+  container.bindClass('EventManager', EventManager, ['EventPublisher']);
+  container.bindClass('WebhookManager', WebhookManager, [
+    'EventPublisher',
+    'PermissionManager',
+    'WebhookLogger',
+    'WebhookRepository',
+  ]);
 
   // Repositories
   container.bindClass(
@@ -180,19 +121,11 @@ export default (container: Container, newSettings: Settings) => {
     DeviceFirmwareFileRepository,
     ['FIRMWARE_DIRECTORY'],
   );
-  container.bindClass(
-    'DeviceKeyRepository',
-    DeviceKeyDatabaseRepository,
-    ['Database'],
-  );
-  container.bindClass(
-    'UserRepository',
-    UserDatabaseRepository,
-    ['Database'],
-  );
-  container.bindClass(
-    'WebhookRepository',
-    WebhookDatabaseRepository,
-    ['Database'],
-  );
+  container.bindClass('DeviceKeyRepository', DeviceKeyDatabaseRepository, [
+    'Database',
+  ]);
+  container.bindClass('UserRepository', UserDatabaseRepository, ['Database']);
+  container.bindClass('WebhookRepository', WebhookDatabaseRepository, [
+    'Database',
+  ]);
 };
diff --git a/src/exports.js b/src/exports.js
index 0b515802..fb04d2d4 100644
--- a/src/exports.js
+++ b/src/exports.js
@@ -6,10 +6,4 @@ import defaultBindings from './defaultBindings';
 import settings from './settings';
 import MongoDb from './repository/MongoDb';
 
-export {
-  MongoDb,
-  createApp,
-  defaultBindings,
-  logger,
-  settings,
-};
+export { MongoDb, createApp, defaultBindings, logger, settings };
diff --git a/src/lib/DefaultLogger.js b/src/lib/DefaultLogger.js
index f287a67a..5224f8c7 100644
--- a/src/lib/DefaultLogger.js
+++ b/src/lib/DefaultLogger.js
@@ -38,11 +38,10 @@ function _transform(...params: Array): Array {
 }
 
 function getDate(): string {
-  return (new Date()).toISOString();
+  return new Date().toISOString();
 }
 
 export class DefaultLogger implements ILogger {
-
   static log(...params: Array) {
     if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
       DefaultLogger._log(`[${getDate()}]`, _transform(...params));
@@ -50,24 +49,15 @@ export class DefaultLogger implements ILogger {
   }
 
   static info(...params: Array) {
-    DefaultLogger._log(
-      `[${getDate()}]`,
-      chalk.cyan(_transform(...params)),
-    );
+    DefaultLogger._log(`[${getDate()}]`, chalk.cyan(_transform(...params)));
   }
 
   static warn(...params: Array) {
-    DefaultLogger._log(
-      `[${getDate()}]`,
-      chalk.yellow(_transform(...params)),
-    );
+    DefaultLogger._log(`[${getDate()}]`, chalk.yellow(_transform(...params)));
   }
 
   static error(...params: Array) {
-    DefaultLogger._log(
-      `[${getDate()}]`,
-      chalk.red(_transform(...params)),
-    );
+    DefaultLogger._log(`[${getDate()}]`, chalk.red(_transform(...params)));
   }
   static _log(...params: Array) {
     console.log(...params);
diff --git a/src/lib/HttpError.js b/src/lib/HttpError.js
index bb4cb74b..fe188476 100644
--- a/src/lib/HttpError.js
+++ b/src/lib/HttpError.js
@@ -3,10 +3,7 @@
 class HttpError extends Error {
   status: number;
 
-  constructor(
-    error: string | Error | HttpError,
-    status?: number = 400,
-  ) {
+  constructor(error: string | Error | HttpError, status?: number = 400) {
     super(error.message || error);
     if (typeof error.status === 'number') {
       this.status = error.status;
diff --git a/src/lib/PasswordHasher.js b/src/lib/PasswordHasher.js
index a34043c8..4964190b 100644
--- a/src/lib/PasswordHasher.js
+++ b/src/lib/PasswordHasher.js
@@ -38,10 +38,7 @@ class PasswordHasher {
     });
   }
 
-  static hash(
-    password: string,
-    salt: string,
-  ): Promise<*> {
+  static hash(password: string, salt: string): Promise<*> {
     return new Promise((resolve: Function, reject: Function) => {
       crypto.pbkdf2(
         password,
diff --git a/src/lib/promisify.js b/src/lib/promisify.js
index b6fe1c45..24c15dac 100644
--- a/src/lib/promisify.js
+++ b/src/lib/promisify.js
@@ -5,17 +5,18 @@ export const promisify = (
   fnName: string,
   ...args: Array
 ): Promise<*> =>
-  new Promise((
-    resolve: (result: any) => void,
-    reject: (error: Error) => void,
-  ): void => object[fnName](...args, (error: Error, ...callbackArgs: any): ?Function => {
-    if (error) {
-      reject(error);
-      return null;
-    }
+  new Promise(
+    (resolve: (result: any) => void, reject: (error: Error) => void): void =>
+      object[
+        fnName
+      ](...args, (error: Error, ...callbackArgs: any): ?Function => {
+        if (error) {
+          reject(error);
+          return null;
+        }
 
-    return callbackArgs.length <= 1
-      ? resolve(...callbackArgs)
-      : resolve(callbackArgs);
-  }),
-);
+        return callbackArgs.length <= 1
+          ? resolve(...callbackArgs)
+          : resolve(callbackArgs);
+      }),
+  );
diff --git a/src/main.js b/src/main.js
index f42c8e77..20ab6622 100644
--- a/src/main.js
+++ b/src/main.js
@@ -15,10 +15,10 @@ import { Container } from 'constitute';
 const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
 
 process.on('uncaughtException', (exception: Error) => {
-  logger.error(
-    'uncaughtException',
-    { message: exception.message, stack: exception.stack },
-  ); // logging with MetaData
+  logger.error('uncaughtException', {
+    message: exception.message,
+    stack: exception.stack,
+  }); // logging with MetaData
   process.exit(1); // exit with failure
 });
 
@@ -53,21 +53,16 @@ const {
 
 if (useSSL) {
   const options = {
-    cert: certificateFilePath && fs.readFileSync(
-      nulltrhows(certificateFilePath),
-    ),
-    key: privateKeyFilePath && fs.readFileSync(
-      nulltrhows(privateKeyFilePath),
-    ),
+    cert:
+      certificateFilePath && fs.readFileSync(nulltrhows(certificateFilePath)),
+    key: privateKeyFilePath && fs.readFileSync(nulltrhows(privateKeyFilePath)),
     ...expressConfig,
   };
   https
     .createServer(options, (app: any))
     .listen(NODE_PORT, onServerStartListen);
 } else {
-  http
-    .createServer((app: any))
-    .listen(NODE_PORT, onServerStartListen);
+  http.createServer((app: any)).listen(NODE_PORT, onServerStartListen);
 }
 
 const addresses = arrayFlatten(
@@ -75,9 +70,9 @@ const addresses = arrayFlatten(
     // eslint-disable-next-line no-unused-vars
     ([name, nic]: [string, mixed]): Array =>
       (nic: any)
-        .filter((address: Object): boolean =>
-          address.family === 'IPv4' &&
-          address.address !== '127.0.0.1',
+        .filter(
+          (address: Object): boolean =>
+            address.family === 'IPv4' && address.address !== '127.0.0.1',
         )
         .map((address: Object): boolean => address.address),
   ),
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index c08ae4c1..0e5f5751 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -42,8 +42,7 @@ class DeviceManager {
     userID: string,
   ): Promise => {
     // todo check: we may not need to get attributes from db here.
-    const attributes =
-      await this._deviceAttributeRepository.getByID(deviceID);
+    const attributes = await this._deviceAttributeRepository.getByID(deviceID);
 
     if (!attributes) {
       throw new HttpError('No device found', 404);
@@ -63,10 +62,9 @@ class DeviceManager {
     });
 
     // todo check: we may not need to update attributes in db here.
-    return await this._deviceAttributeRepository.updateByID(
-      deviceID,
-      { ownerID: userID },
-    );
+    return await this._deviceAttributeRepository.updateByID(deviceID, {
+      ownerID: userID,
+    });
   };
 
   unclaimDevice = async (deviceID: string): Promise => {
@@ -78,10 +76,9 @@ class DeviceManager {
       name: SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES,
     });
 
-    return await this._deviceAttributeRepository.updateByID(
-      deviceID,
-      { ownerID: null },
-    );
+    return await this._deviceAttributeRepository.updateByID(deviceID, {
+      ownerID: null,
+    });
   };
 
   getAttributesByID = async (deviceID: string): Promise => {
@@ -91,16 +88,17 @@ class DeviceManager {
   };
 
   getByID = async (deviceID: string): Promise => {
-    const connectedDeviceAttributes = await this._eventPublisher
-      .publishAndListenForResponse({
+    const connectedDeviceAttributes = await this._eventPublisher.publishAndListenForResponse(
+      {
         context: { deviceID },
         name: SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES,
-      });
+      },
+    );
 
     const attributes = !connectedDeviceAttributes.error &&
       this._permissionManager.doesUserHaveAccess(connectedDeviceAttributes)
-        ? connectedDeviceAttributes
-        : await this._permissionManager.getEntityByID(
+      ? connectedDeviceAttributes
+      : await this._permissionManager.getEntityByID(
           'deviceAttributes',
           deviceID,
         );
@@ -117,15 +115,18 @@ class DeviceManager {
   };
 
   getAll = async (): Promise> => {
-    const devicesAttributes =
-      await this._permissionManager.getAllEntitiesForCurrentUser('deviceAttributes');
+    const devicesAttributes = await this._permissionManager.getAllEntitiesForCurrentUser(
+      'deviceAttributes',
+    );
 
     const devicePromises = devicesAttributes.map(
       async (attributes: DeviceAttributes): Promise => {
-        const pingResponse = await this._eventPublisher.publishAndListenForResponse({
-          context: { deviceID: attributes.deviceID },
-          name: SPARK_SERVER_EVENTS.PING_DEVICE,
-        });
+        const pingResponse = await this._eventPublisher.publishAndListenForResponse(
+          {
+            context: { deviceID: attributes.deviceID },
+            name: SPARK_SERVER_EVENTS.PING_DEVICE,
+          },
+        );
         return {
           ...attributes,
           connected: pingResponse.connected || false,
@@ -141,17 +142,18 @@ class DeviceManager {
   callFunction = async (
     deviceID: string,
     functionName: string,
-    functionArguments: {[key: string]: string},
+    functionArguments: { [key: string]: string },
   ): Promise<*> => {
     await this._permissionManager.checkPermissionsForEntityByID(
       'deviceAttributes',
       deviceID,
     );
-    const callFunctionResponse =
-      await this._eventPublisher.publishAndListenForResponse({
+    const callFunctionResponse = await this._eventPublisher.publishAndListenForResponse(
+      {
         context: { deviceID, functionArguments, functionName },
         name: SPARK_SERVER_EVENTS.CALL_DEVICE_FUNCTION,
-      });
+      },
+    );
 
     const { error } = callFunctionResponse;
     if (error) {
@@ -170,11 +172,12 @@ class DeviceManager {
       deviceID,
     );
 
-    const getVariableResponse =
-      await this._eventPublisher.publishAndListenForResponse({
+    const getVariableResponse = await this._eventPublisher.publishAndListenForResponse(
+      {
         context: { deviceID, variableName },
         name: SPARK_SERVER_EVENTS.GET_DEVICE_VARIABLE_VALUE,
-      });
+      },
+    );
 
     const { error, result } = getVariableResponse;
     if (error) {
@@ -184,20 +187,18 @@ class DeviceManager {
     return result;
   };
 
-  flashBinary = async (
-    deviceID: string,
-    file: File,
-  ): Promise<*> => {
+  flashBinary = async (deviceID: string, file: File): Promise<*> => {
     await this._permissionManager.checkPermissionsForEntityByID(
       'deviceAttributes',
       deviceID,
     );
 
-    const flashResponse =
-      await this._eventPublisher.publishAndListenForResponse({
+    const flashResponse = await this._eventPublisher.publishAndListenForResponse(
+      {
         context: { deviceID, fileBuffer: file.buffer },
         name: SPARK_SERVER_EVENTS.FLASH_DEVICE,
-      });
+      },
+    );
 
     const { error } = flashResponse;
     if (error) {
@@ -207,10 +208,7 @@ class DeviceManager {
     return flashResponse;
   };
 
-  flashKnownApp = async (
-    deviceID: string,
-    appName: string,
-  ): Promise<*> => {
+  flashKnownApp = async (deviceID: string, appName: string): Promise<*> => {
     await this._permissionManager.checkPermissionsForEntityByID(
       'deviceAttributes',
       deviceID,
@@ -222,10 +220,12 @@ class DeviceManager {
       throw new HttpError(`No firmware ${appName} found`, 404);
     }
 
-    const flashResponse = await this._eventPublisher.publishAndListenForResponse({
-      context: { deviceID, fileBuffer: knownFirmware },
-      name: SPARK_SERVER_EVENTS.FLASH_DEVICE,
-    });
+    const flashResponse = await this._eventPublisher.publishAndListenForResponse(
+      {
+        context: { deviceID, fileBuffer: knownFirmware },
+        name: SPARK_SERVER_EVENTS.FLASH_DEVICE,
+      },
+    );
 
     const { error } = flashResponse;
     if (error) {
@@ -262,23 +262,17 @@ class DeviceManager {
       }
     }
 
-    await this._deviceKeyRepository.updateByID(
+    await this._deviceKeyRepository.updateByID(deviceID, {
+      algorithm,
       deviceID,
-      {
-        algorithm,
-        deviceID,
-        key: publicKey,
-      },
-    );
+      key: publicKey,
+    });
 
-    await this._deviceAttributeRepository.updateByID(
-      deviceID,
-      {
-        ownerID: userID,
-        registrar: userID,
-        timestamp: new Date(),
-      },
-    );
+    await this._deviceAttributeRepository.updateByID(deviceID, {
+      ownerID: userID,
+      registrar: userID,
+      timestamp: new Date(),
+    });
     return await this.getByID(deviceID);
   };
 
@@ -291,11 +285,12 @@ class DeviceManager {
       deviceID,
     );
 
-    const raiseYourHandResponse =
-      await this._eventPublisher.publishAndListenForResponse({
+    const raiseYourHandResponse = await this._eventPublisher.publishAndListenForResponse(
+      {
         context: { deviceID, shouldShowSignal },
         name: SPARK_SERVER_EVENTS.RAISE_YOUR_HAND,
-      });
+      },
+    );
 
     const { error } = raiseYourHandResponse;
     if (error) {
@@ -319,7 +314,7 @@ class DeviceManager {
     });
 
     return await this._deviceAttributeRepository.updateByID(deviceID, { name });
-  }
+  };
 }
 
 export default DeviceManager;
diff --git a/src/managers/EventManager.js b/src/managers/EventManager.js
index d0b6e1d6..71b928e5 100644
--- a/src/managers/EventManager.js
+++ b/src/managers/EventManager.js
@@ -21,11 +21,9 @@ class EventManager {
     eventHandler: (event: Event) => void,
     filterOptions: FilterOptions,
   ): string =>
-    this._eventPublisher.subscribe(
-      eventNamePrefix,
-      eventHandler,
-      { filterOptions },
-    );
+    this._eventPublisher.subscribe(eventNamePrefix, eventHandler, {
+      filterOptions,
+    });
 
   unsubscribe = (subscriptionID: string): void =>
     this._eventPublisher.unsubscribe(subscriptionID);
diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js
index 5566baec..87b98ffa 100644
--- a/src/managers/FirmwareCompilationManager.js
+++ b/src/managers/FirmwareCompilationManager.js
@@ -12,21 +12,16 @@ import { spawn } from 'child_process';
 import { knownPlatforms } from 'spark-protocol';
 import settings from '../settings';
 
-const IS_COMPILATION_ENABLED =
-  fs.existsSync(settings.FIRMWARE_REPOSITORY_DIRECTORY);
+const IS_COMPILATION_ENABLED = fs.existsSync(
+  settings.FIRMWARE_REPOSITORY_DIRECTORY,
+);
 
 const USER_APP_PATH = path.join(
   settings.FIRMWARE_REPOSITORY_DIRECTORY,
   'user/applications',
 );
-const BIN_PATH = path.join(
-  settings.BUILD_DIRECTORY,
-  'bin',
-);
-const MAKE_PATH = path.join(
-  settings.FIRMWARE_REPOSITORY_DIRECTORY,
-  'main',
-);
+const BIN_PATH = path.join(settings.BUILD_DIRECTORY, 'bin');
+const MAKE_PATH = path.join(settings.FIRMWARE_REPOSITORY_DIRECTORY, 'main');
 
 type CompilationResponse = {
   binary_id: string,
@@ -37,10 +32,8 @@ type CompilationResponse = {
 
 const FILE_NAME_BY_KEY = new Map();
 
-const getKey = (): string => crypto
-  .randomBytes(24)
-  .toString('hex')
-  .substring(0, 24);
+const getKey = (): string =>
+  crypto.randomBytes(24).toString('hex').substring(0, 24);
 
 const getUniqueKey = (): string => {
   let key = getKey();
@@ -64,7 +57,8 @@ class FirmwareCompilationManager {
       return null;
     }
 
-    const binFileName = fs.readdirSync(binaryPath)
+    const binFileName = fs
+      .readdirSync(binaryPath)
       .find((file: string): boolean => file.endsWith('.bin'));
 
     if (!binFileName) {
@@ -88,8 +82,7 @@ class FirmwareCompilationManager {
     }
 
     platformName = platformName.toLowerCase();
-    const appFolder =
-      `${platformName}_firmware_${(new Date()).getTime()}`.toLowerCase();
+    const appFolder = `${platformName}_firmware_${new Date().getTime()}`.toLowerCase();
     const appPath = path.join(USER_APP_PATH, appFolder);
     mkdirp.sync(appPath);
 
@@ -154,10 +147,7 @@ class FirmwareCompilationManager {
       sizeInfo,
     };
 
-    FirmwareCompilationManager.addFirmwareCleanupTask(
-      appPath,
-      config,
-    );
+    FirmwareCompilationManager.addFirmwareCleanupTask(appPath, config);
 
     return config;
   };
@@ -173,11 +163,8 @@ class FirmwareCompilationManager {
     const currentDate = new Date();
     const difference =
       new Date(config.expires_at).getTime() - currentDate.getTime();
-    setTimeout(
-      (): void => rmfr(appFolderPath),
-      difference,
-    );
-  }
+    setTimeout((): void => rmfr(appFolderPath), difference);
+  };
 }
 
 if (IS_COMPILATION_ENABLED) {
@@ -208,10 +195,7 @@ if (IS_COMPILATION_ENABLED) {
       rmfr(configPath);
       rmfr(path.join(BIN_PATH, config.binary_id));
     } else {
-      FirmwareCompilationManager.addFirmwareCleanupTask(
-        appFolder,
-        config,
-      );
+      FirmwareCompilationManager.addFirmwareCleanupTask(appFolder, config);
     }
   });
 }
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index fbe794c5..036ca3d3 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -25,7 +25,10 @@ class PermissionManager {
     oauthServer: Object,
   ) {
     this._userRepository = userRepository;
-    this._repositoriesByEntityName.set('deviceAttributes', deviceAttributeRepository);
+    this._repositoriesByEntityName.set(
+      'deviceAttributes',
+      deviceAttributeRepository,
+    );
     this._repositoriesByEntityName.set('webhook', webhookRepository);
     this._oauthServer = oauthServer;
 
@@ -35,25 +38,30 @@ class PermissionManager {
   checkPermissionsForEntityByID = async (
     entityName: ProtectedEntityName,
     id: string,
-  ): Promise => !!(await this.getEntityByID(entityName, id));
+  ): Promise => !!await this.getEntityByID(entityName, id);
 
-  getAllEntitiesForCurrentUser = async (entityName: ProtectedEntityName): Promise<*> => {
+  getAllEntitiesForCurrentUser = async (
+    entityName: ProtectedEntityName,
+  ): Promise<*> => {
     const currentUser = this._userRepository.getCurrentUser();
-    return await nullthrows(this._repositoriesByEntityName.get(entityName))
-      .getAll(currentUser.id);
+    return await nullthrows(
+      this._repositoriesByEntityName.get(entityName),
+    ).getAll(currentUser.id);
   };
 
   getEntityByID = async (
     entityName: ProtectedEntityName,
     id: string,
   ): Promise<*> => {
-    const entity = await nullthrows(this._repositoriesByEntityName.get(entityName)).getByID(id);
+    const entity = await nullthrows(
+      this._repositoriesByEntityName.get(entityName),
+    ).getByID(id);
     if (!entity) {
       return null;
     }
 
     if (!this.doesUserHaveAccess(entity)) {
-      throw new HttpError('User doesn\'t have access', 403);
+      throw new HttpError("User doesn't have access", 403);
     }
 
     return entity;
@@ -71,9 +79,7 @@ class PermissionManager {
 
       const token = await this._generateAdminToken();
 
-      logger.info(
-        `New default admin user created with token: ${token}`,
-      );
+      logger.info(`New default admin user created with token: ${token}`);
     } catch (error) {
       logger.error(`Error during default admin user creating: ${error}`);
     }
@@ -115,8 +121,9 @@ class PermissionManager {
   };
 
   _init = async (): Promise => {
-    const defaultAdminUser =
-      await this._userRepository.getByUsername(settings.DEFAULT_ADMIN_USERNAME);
+    const defaultAdminUser = await this._userRepository.getByUsername(
+      settings.DEFAULT_ADMIN_USERNAME,
+    );
     if (defaultAdminUser) {
       logger.info(
         'Default admin accessToken: ' +
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index a651b313..7393b9f8 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -38,7 +38,7 @@ const splitBufferIntoChunks = (
   const chunks = [];
   let ii = 0;
   while (ii < buffer.length) {
-    chunks.push(buffer.slice(ii, ii += chunkSize));
+    chunks.push(buffer.slice(ii, (ii += chunkSize)));
   }
 
   return chunks;
@@ -53,9 +53,7 @@ const validateRequestType = (requestType: string): RequestType => {
   return upperRequestType;
 };
 
-const REQUEST_TYPES: Array = [
-  'DELETE', 'GET', 'POST', 'PUT',
-];
+const REQUEST_TYPES: Array = ['DELETE', 'GET', 'POST', 'PUT'];
 const MAX_WEBHOOK_ERRORS_COUNT = 10;
 const WEBHOOK_THROTTLE_TIME = 1000 * 60; // 1min;
 const MAX_RESPONSE_MESSAGE_CHUNK_SIZE = 512;
@@ -97,7 +95,10 @@ class WebhookManager {
   };
 
   deleteByID = async (webhookID: string): Promise => {
-    const webhook = await this._permissonManager.getEntityByID('webhook', webhookID);
+    const webhook = await this._permissonManager.getEntityByID(
+      'webhook',
+      webhookID,
+    );
     if (!webhook) {
       throw new HttpError('no webhook found', 404);
     }
@@ -110,7 +111,10 @@ class WebhookManager {
     await this._permissonManager.getAllEntitiesForCurrentUser('webhook');
 
   getByID = async (webhookID: string): Promise => {
-    const webhook = await this._permissonManager.getEntityByID('webhook', webhookID);
+    const webhook = await this._permissonManager.getEntityByID(
+      'webhook',
+      webhookID,
+    );
     if (!webhook) {
       throw new HttpError('no webhook found', 404);
     }
@@ -120,8 +124,8 @@ class WebhookManager {
 
   _init = async (): Promise => {
     const allWebhooks = await this._webhookRepository.getAll();
-    allWebhooks.forEach(
-      (webhook: Webhook): void => this._subscribeWebhook(webhook),
+    allWebhooks.forEach((webhook: Webhook): void =>
+      this._subscribeWebhook(webhook),
     );
   };
 
@@ -151,37 +155,34 @@ class WebhookManager {
     this._subscriptionIDsByWebhookID.delete(webhookID);
   };
 
-  _onNewWebhookEvent = (webhook: Webhook): (event: Event) => void =>
-    (event: Event) => {
-      try {
-        const webhookErrorCount =
-          this._errorsCountByWebhookID.get(webhook.id) || 0;
+  _onNewWebhookEvent = (webhook: Webhook): ((event: Event) => void) => (
+    event: Event,
+  ) => {
+    try {
+      const webhookErrorCount =
+        this._errorsCountByWebhookID.get(webhook.id) || 0;
 
-        if (webhookErrorCount < MAX_WEBHOOK_ERRORS_COUNT) {
-          this.runWebhook(webhook, event);
-          return;
-        }
+      if (webhookErrorCount < MAX_WEBHOOK_ERRORS_COUNT) {
+        this.runWebhook(webhook, event);
+        return;
+      }
 
-        this._eventPublisher.publish({
-          data: 'Too many errors, webhook disabled',
-          isPublic: false,
-          name: this._compileErrorResponseTopic(
-            webhook,
-            event,
-          ),
-          userID: event.userID,
-        });
+      this._eventPublisher.publish({
+        data: 'Too many errors, webhook disabled',
+        isPublic: false,
+        name: this._compileErrorResponseTopic(webhook, event),
+        userID: event.userID,
+      });
 
-        this.runWebhookThrottled(webhook, event);
-      } catch (error) {
-        logger.error(`webhookError: ${error}`);
-      }
-    };
+      this.runWebhookThrottled(webhook, event);
+    } catch (error) {
+      logger.error(`webhookError: ${error}`);
+    }
+  };
 
   runWebhook = async (webhook: Webhook, event: Event): Promise => {
     try {
-      const webhookVariablesObject =
-        this._getEventVariables(event);
+      const webhookVariablesObject = this._getEventVariables(event);
 
       const requestAuth = this._compileJsonTemplate(
         webhook.auth,
@@ -253,24 +254,22 @@ class WebhookManager {
       const isResponseBodyAnObject = responseBody === Object(responseBody);
 
       const responseTemplate =
-        webhook.responseTemplate && isResponseBodyAnObject && hogan
-          .compile(webhook.responseTemplate)
-          .render(responseBody);
+        webhook.responseTemplate &&
+        isResponseBodyAnObject &&
+        hogan.compile(webhook.responseTemplate).render(responseBody);
 
-      const responseEventData = responseTemplate || (isResponseBodyAnObject
-        ? JSON.stringify(responseBody)
-        : responseBody);
+      const responseEventData =
+        responseTemplate ||
+        (isResponseBodyAnObject ? JSON.stringify(responseBody) : responseBody);
 
       const chunks = splitBufferIntoChunks(
-        Buffer
-          .from(responseEventData)
-          .slice(0, MAX_RESPONSE_MESSAGE_SIZE),
+        Buffer.from(responseEventData).slice(0, MAX_RESPONSE_MESSAGE_SIZE),
         MAX_RESPONSE_MESSAGE_CHUNK_SIZE,
       );
 
       chunks.forEach((chunk: Buffer, index: number) => {
         const responseEventName =
-          responseTopic && `${responseTopic}/${index}` ||
+          (responseTopic && `${responseTopic}/${index}`) ||
           `hook-response/${event.name}/${index}`;
 
         this._eventPublisher.publish({
@@ -293,64 +292,62 @@ class WebhookManager {
     }
   };
 
-  runWebhookThrottled = throttle(
-    this.runWebhook,
-    WEBHOOK_THROTTLE_TIME,
-    { leading: false, trailing: true },
-  );
+  runWebhookThrottled = throttle(this.runWebhook, WEBHOOK_THROTTLE_TIME, {
+    leading: false,
+    trailing: true,
+  });
 
   _callWebhook = (
     webhook: Webhook,
     event: Event,
     requestOptions: RequestOptions,
-  ): Promise<*> => new Promise(
-    (
-      resolve: (responseBody: string | Buffer | Object) => void,
-      reject: (error: Error) => void,
-    ): void => request(
-      requestOptions,
+  ): Promise<*> =>
+    new Promise(
       (
-        error: ?Error,
-        response: http$IncomingMessage,
-        responseBody: string | Buffer | Object,
-      ) => {
-        const onResponseError = (errorMessage: ?string) => {
-          this._incrementWebhookErrorCounter(webhook.id);
-
-          this._eventPublisher.publish({
-            data: errorMessage,
-            isPublic: false,
-            name: this._compileErrorResponseTopic(
-              webhook,
-              event,
-            ),
-            userID: event.userID,
-          });
-
-          reject(new Error(errorMessage));
-        };
-
-        if (error) {
-          onResponseError(error.message);
-          return;
-        }
-        if (response.statusCode >= 400) {
-          onResponseError((response: any).statusMessage);
-          return;
-        }
-
-        this._resetWebhookErrorCounter(webhook.id);
-
-        this._eventPublisher.publish({
-          isPublic: false,
-          name: `hook-sent/${event.name}`,
-          userID: event.userID,
-        });
-
-        resolve(responseBody);
-      },
-    ),
-  );
+        resolve: (responseBody: string | Buffer | Object) => void,
+        reject: (error: Error) => void,
+      ): void =>
+        request(
+          requestOptions,
+          (
+            error: ?Error,
+            response: http$IncomingMessage,
+            responseBody: string | Buffer | Object,
+          ) => {
+            const onResponseError = (errorMessage: ?string) => {
+              this._incrementWebhookErrorCounter(webhook.id);
+
+              this._eventPublisher.publish({
+                data: errorMessage,
+                isPublic: false,
+                name: this._compileErrorResponseTopic(webhook, event),
+                userID: event.userID,
+              });
+
+              reject(new Error(errorMessage));
+            };
+
+            if (error) {
+              onResponseError(error.message);
+              return;
+            }
+            if (response.statusCode >= 400) {
+              onResponseError((response: any).statusMessage);
+              return;
+            }
+
+            this._resetWebhookErrorCounter(webhook.id);
+
+            this._eventPublisher.publish({
+              isPublic: false,
+              name: `hook-sent/${event.name}`,
+              userID: event.userID,
+            });
+
+            resolve(responseBody);
+          },
+        ),
+    );
 
   _getEventVariables = (event: Event): Object => {
     const defaultWebhookVariables = {
@@ -392,9 +389,7 @@ class WebhookManager {
   };
 
   _compileTemplate = (template?: ?string, variables: Object): ?string =>
-    template && hogan
-      .compile(template)
-      .render(variables);
+    template && hogan.compile(template).render(variables);
 
   _compileJsonTemplate = (template?: ?Object, variables: Object): ?Object => {
     if (!template) {
@@ -414,10 +409,10 @@ class WebhookManager {
 
   _compileErrorResponseTopic = (webhook: Webhook, event: Event): string => {
     const variables = this._getEventVariables(event);
-    return this._compileTemplate(
-      webhook.errorResponseTopic,
-      variables,
-    ) || `hook-error/${event.name}`;
+    return (
+      this._compileTemplate(webhook.errorResponseTopic, variables) ||
+      `hook-error/${event.name}`
+    );
   };
 
   _incrementWebhookErrorCounter = (webhookID: string) => {
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 0db7a621..8b4a0544 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -29,10 +29,7 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-    return await this._database.find(
-      this._collectionName,
-      query,
-    );
+    return await this._database.find(this._collectionName, query);
   };
 
   getByID = async (deviceID: string): Promise =>
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index e044321c..378f38f9 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -9,7 +9,6 @@ import type {
 
 import COLLECTION_NAMES from './collectionNames';
 
-
 // getByID, deleteByID and update uses model.deviceID as ID for querying
 class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
   _database: IBaseDatabase;
@@ -20,10 +19,10 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
   }
 
   create = async (model: DeviceKeyObject): Promise =>
-    await this._database.insertOne(
-      this._collectionName,
-      { _id: model.deviceID, ...model },
-    );
+    await this._database.insertOne(this._collectionName, {
+      _id: model.deviceID,
+      ...model,
+    });
 
   deleteByID = async (deviceID: string): Promise =>
     await this._database.remove(this._collectionName, { deviceID });
@@ -33,10 +32,7 @@ class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
   };
 
   getByID = async (deviceID: string): Promise =>
-    await this._database.findOne(
-      this._collectionName,
-      { deviceID },
-    );
+    await this._database.findOne(this._collectionName, { deviceID });
 
   updateByID = async (
     deviceID: string,
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index 7c1bc3c1..4c3cee17 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -19,70 +19,62 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
     (async (): Promise => await this._init(url, options))();
   }
 
-  insertOne = async (
-    collectionName: string,
-    entity: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const insertResult = await collection.insertOne(entity);
-      return this.__translateResultItem(insertResult.ops[0]);
-    },
-  );
-
-  find = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItems = await collection.find(
-        this.__translateQuery(query),
-        { timeout: false },
-      ).toArray();
-
-      return resultItems.map(this.__translateResultItem);
-    },
-  );
+  insertOne = async (collectionName: string, entity: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const insertResult = await collection.insertOne(entity);
+        return this.__translateResultItem(insertResult.ops[0]);
+      },
+    );
+
+  find = async (collectionName: string, query: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const resultItems = await collection
+          .find(this.__translateQuery(query), { timeout: false })
+          .toArray();
+
+        return resultItems.map(this.__translateResultItem);
+      },
+    );
 
   findAndModify = async (
     collectionName: string,
     query: Object,
     updateQuery: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const modifyResult = await collection.findAndModify(
-        this.__translateQuery(query),
-        null,
-        this.__translateQuery(updateQuery),
-        { new: true, upsert: true },
-      );
-      return this.__translateResultItem(modifyResult.value);
-    },
-  );
-
-  findOne = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItem = await collection.findOne(
-        this.__translateQuery(query),
-      );
-      return this.__translateResultItem(resultItem);
-    },
-  );
-
-  remove = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> =>
-      await collection.remove(this.__translateQuery(query)),
-  );
+  ): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const modifyResult = await collection.findAndModify(
+          this.__translateQuery(query),
+          null,
+          this.__translateQuery(updateQuery),
+          { new: true, upsert: true },
+        );
+        return this.__translateResultItem(modifyResult.value);
+      },
+    );
+
+  findOne = async (collectionName: string, query: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const resultItem = await collection.findOne(
+          this.__translateQuery(query),
+        );
+        return this.__translateResultItem(resultItem);
+      },
+    );
+
+  remove = async (collectionName: string, query: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> =>
+        await collection.remove(this.__translateQuery(query)),
+    );
 
   __runForCollection = async (
     collectionName: string,
@@ -101,20 +93,14 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
   _init = async (url: string, options: Object): Promise => {
     const database = await MongoClient.connect(url, options);
 
-    database.on(
-      'error',
-      (error: Error): void =>
-        Logger.error('DB connection Error: ', error),
+    database.on('error', (error: Error): void =>
+      Logger.error('DB connection Error: ', error),
     );
 
-    database.on(
-      'open',
-      (): void => Logger.log('DB connected'),
-    );
+    database.on('open', (): void => Logger.log('DB connected'));
 
-    database.on(
-      'close',
-      (str: string): void => Logger.log('DB disconnected: ', str),
+    database.on('close', (str: string): void =>
+      Logger.log('DB disconnected: ', str),
     );
 
     this._database = database;
@@ -127,10 +113,7 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
     }
 
     return new Promise((resolve: () => void) => {
-      this._statusEventEmitter.once(
-        DB_READY_EVENT,
-        (): void => resolve(),
-      );
+      this._statusEventEmitter.once(DB_READY_EVENT, (): void => resolve());
     });
   };
 }
diff --git a/src/repository/NeDb.js b/src/repository/NeDb.js
index d0129ba6..4ceedf34 100644
--- a/src/repository/NeDb.js
+++ b/src/repository/NeDb.js
@@ -31,72 +31,60 @@ class NeDb extends BaseMongoDb implements IBaseDatabase {
     });
   }
 
-  insertOne = async (
-    collectionName: string,
-    entity: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const insertResult = await promisify(
-        collection,
-        'insert',
-        entity,
-      );
-
-      return this.__translateResultItem(insertResult);
-    },
-  );
-
-  find = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItems = await promisify(collection, 'find', query);
-      return resultItems.map(this.__translateResultItem);
-    },
-  );
+  insertOne = async (collectionName: string, entity: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const insertResult = await promisify(collection, 'insert', entity);
+
+        return this.__translateResultItem(insertResult);
+      },
+    );
+
+  find = async (collectionName: string, query: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const resultItems = await promisify(collection, 'find', query);
+        return resultItems.map(this.__translateResultItem);
+      },
+    );
 
   findAndModify = async (
     collectionName: string,
     query: Object,
     updateQuery: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      // eslint-disable-next-line no-unused-vars
-      const [count, resultItem] = await promisify(
-        collection,
-        'update',
-        query,
-        updateQuery,
-        { returnUpdatedDocs: true, upsert: true },
-      );
-
-      return this.__translateResultItem(resultItem);
-    },
-  );
-
-  findOne = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> => {
-      const resultItem = await promisify(collection, 'findOne', query);
-      return this.__translateResultItem(resultItem);
-    },
-  );
-
-  remove = async (
-    collectionName: string,
-    query: Object,
-  ): Promise<*> => await this.__runForCollection(
-    collectionName,
-    async (collection: Object): Promise<*> =>
-      await promisify(collection, 'remove', query),
-  );
+  ): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const [
+          count, // eslint-disable-line no-unused-vars
+          resultItem,
+        ] = await promisify(collection, 'update', query, updateQuery, {
+          returnUpdatedDocs: true,
+          upsert: true,
+        });
+
+        return this.__translateResultItem(resultItem);
+      },
+    );
+
+  findOne = async (collectionName: string, query: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> => {
+        const resultItem = await promisify(collection, 'findOne', query);
+        return this.__translateResultItem(resultItem);
+      },
+    );
+
+  remove = async (collectionName: string, query: Object): Promise<*> =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> =>
+        await promisify(collection, 'remove', query),
+    );
 
   __runForCollection = async (
     collectionName: string,
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index 4bbd3df2..37abb303 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -25,10 +25,7 @@ class UserDatabaseRepository implements IUserRepository {
 
   // eslint-disable-next-line no-unused-vars
   create = async (user: $Shape): Promise =>
-    await this._database.insertOne(
-      this._collectionName,
-      user,
-    );
+    await this._database.insertOne(this._collectionName, user);
 
   createWithCredentials = async (
     userCredentials: UserCredentials,
@@ -47,13 +44,13 @@ class UserDatabaseRepository implements IUserRepository {
       username,
     };
 
-    return await this._database.insertOne(
-      this._collectionName,
-      modelToSave,
-    );
+    return await this._database.insertOne(this._collectionName, modelToSave);
   };
 
-  deleteAccessToken = async (userID: string, accessToken: string): Promise =>
+  deleteAccessToken = async (
+    userID: string,
+    accessToken: string,
+  ): Promise =>
     await this._database.findAndModify(
       this._collectionName,
       { _id: userID },
@@ -68,17 +65,15 @@ class UserDatabaseRepository implements IUserRepository {
   };
 
   getByAccessToken = async (accessToken: string): Promise => {
-    let user = await this._database.findOne(
-      this._collectionName,
-      { accessTokens: { $elemMatch: { accessToken } } },
-    );
+    let user = await this._database.findOne(this._collectionName, {
+      accessTokens: { $elemMatch: { accessToken } },
+    });
 
     if (!user) {
       // The newer query only works on mongo so we run this for tingo.
-      user = await this._database.findOne(
-        this._collectionName,
-        { 'accessTokens.accessToken': accessToken },
-      );
+      user = await this._database.findOne(this._collectionName, {
+        'accessTokens.accessToken': accessToken,
+      });
     }
 
     return user;
@@ -90,24 +85,22 @@ class UserDatabaseRepository implements IUserRepository {
   };
 
   getByUsername = async (username: string): Promise =>
-    await this._database.findOne(
-      this._collectionName,
-      { username },
-    );
+    await this._database.findOne(this._collectionName, { username });
 
   getCurrentUser = (): User => this._currentUser;
 
   isUserNameInUse = async (username: string): Promise =>
-    !!(await this.getByUsername(username));
+    !!await this.getByUsername(username);
 
   saveAccessToken = async (
     userID: string,
     tokenObject: TokenObject,
-  ): Promise<*> => await this._database.findAndModify(
-    this._collectionName,
-    { _id: userID },
-    { $push: { accessTokens: tokenObject } },
-  );
+  ): Promise<*> =>
+    await this._database.findAndModify(
+      this._collectionName,
+      { _id: userID },
+      { $push: { accessTokens: tokenObject } },
+    );
 
   setCurrentUser = (user: User) => {
     this._currentUser = user;
@@ -122,13 +115,12 @@ class UserDatabaseRepository implements IUserRepository {
 
   validateLogin = async (username: string, password: string): Promise => {
     try {
-      const user = await this._database.findOne(
-        this._collectionName,
-        { username },
-      );
+      const user = await this._database.findOne(this._collectionName, {
+        username,
+      });
 
       if (!user) {
-        throw new HttpError('User doesn\'t exist', 404);
+        throw new HttpError("User doesn't exist", 404);
       }
 
       const hash = await PasswordHasher.hash(password, user.salt);
diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js
index 27238af0..143e05f8 100644
--- a/src/repository/UserFileRepository.js
+++ b/src/repository/UserFileRepository.js
@@ -61,18 +61,15 @@ class UserFileRepository implements IUserRepository {
   deleteAccessToken = async (userID: string, token: string): Promise => {
     const user = await this.getByID(userID);
     if (!user) {
-      throw new Error('User doesn\'t exist');
+      throw new Error("User doesn't exist");
     }
 
-    return await this.updateByID(
-      userID,
-      {
-        accessTokens: user.accessTokens.filter(
-          (tokenObject: TokenObject): boolean =>
-            tokenObject.accessToken !== token,
-        ),
-      },
-    );
+    return await this.updateByID(userID, {
+      accessTokens: user.accessTokens.filter(
+        (tokenObject: TokenObject): boolean =>
+          tokenObject.accessToken !== token,
+      ),
+    });
   };
 
   @memoizeSet(['id'])
@@ -89,8 +86,9 @@ class UserFileRepository implements IUserRepository {
   // isn't a good way to clear the cache.
   getByAccessToken = async (accessToken: string): Promise =>
     (await this.getAll()).find((user: User): boolean =>
-      user.accessTokens.some((tokenObject: TokenObject): boolean =>
-        tokenObject.accessToken === accessToken,
+      user.accessTokens.some(
+        (tokenObject: TokenObject): boolean =>
+          tokenObject.accessToken === accessToken,
       ),
     );
 
@@ -110,8 +108,8 @@ class UserFileRepository implements IUserRepository {
 
   @memoizeGet(['username'])
   async isUserNameInUse(username: string): Promise {
-    return (await this.getAll()).some((user: User): boolean =>
-      user.username === username,
+    return (await this.getAll()).some(
+      (user: User): boolean => user.username === username,
     );
   }
 
@@ -125,10 +123,9 @@ class UserFileRepository implements IUserRepository {
       throw new HttpError('Could not find user for user ID');
     }
 
-    return await this.updateByID(
-      userID,
-      { accessTokens: [...user.accessTokens, tokenObject] },
-    );
+    return await this.updateByID(userID, {
+      accessTokens: [...user.accessTokens, tokenObject],
+    });
   };
 
   setCurrentUser = (user: User) => {
@@ -149,7 +146,7 @@ class UserFileRepository implements IUserRepository {
       const user = await this.getByUsername(username);
 
       if (!user) {
-        throw new Error('User doesn\'t exist');
+        throw new Error("User doesn't exist");
       }
 
       const hash = await PasswordHasher.hash(password, user.salt);
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index 789108cb..f9ea7145 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -14,23 +14,17 @@ class WebhookDatabaseRepository implements IWebhookRepository {
   }
 
   create = async (model: $Shape): Promise =>
-    await this._database.insertOne(
-      this._collectionName,
-      {
-        ...model,
-        created_at: new Date(),
-      },
-    );
+    await this._database.insertOne(this._collectionName, {
+      ...model,
+      created_at: new Date(),
+    });
 
   deleteByID = async (id: string): Promise =>
     await this._database.remove(this._collectionName, { _id: id });
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-    return await this._database.find(
-      this._collectionName,
-      query,
-    );
+    return await this._database.find(this._collectionName, query);
   };
 
   getByID = async (id: string): Promise =>
diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js
index 2778c942..0b28c4a1 100644
--- a/src/repository/WebhookFileRepository.js
+++ b/src/repository/WebhookFileRepository.js
@@ -40,17 +40,14 @@ class WebhookFileRepository implements IWebhookRepository {
 
     if (userID) {
       return allData.filter(
-        (webhook: Webhook): boolean =>
-          webhook.ownerID === userID,
+        (webhook: Webhook): boolean => webhook.ownerID === userID,
       );
     }
     return allData;
   };
 
   @memoizeGet(['id'])
-  async getByID(
-    id: string,
-  ): Promise {
+  async getByID(id: string): Promise {
     return this._fileManager.getFile(`${id}.json`);
   }
 
diff --git a/src/repository/collectionNames.js b/src/repository/collectionNames.js
index 5cb11faa..48567561 100644
--- a/src/repository/collectionNames.js
+++ b/src/repository/collectionNames.js
@@ -1,10 +1,10 @@
 // @flow
 
 export type CollectionName =
-  'deviceAttributes'|
-  'deviceKeys'|
-  'users'|
-  'webhooks';
+  | 'deviceAttributes'
+  | 'deviceKeys'
+  | 'users'
+  | 'webhooks';
 
 const COLLECTION_NAMES: { [key: string]: CollectionName } = {
   DEVICE_ATTRIBUTES: 'deviceAttributes',
diff --git a/src/scripts/migrateFilesToDatabase.js b/src/scripts/migrateFilesToDatabase.js
index 5a612cec..1e3ca5b7 100644
--- a/src/scripts/migrateFilesToDatabase.js
+++ b/src/scripts/migrateFilesToDatabase.js
@@ -17,7 +17,7 @@ type FileObject = {
   fileBuffer: Buffer,
 };
 
-const DATABASE_TYPE: DatabaseType = ((process.argv[2]): any);
+const DATABASE_TYPE: DatabaseType = (process.argv[2]: any);
 
 const setupDatabase = async (): Promise => {
   if (DATABASE_TYPE === 'mongo') {
@@ -46,7 +46,8 @@ const getFiles = (
   directoryPath: string,
   fileExtension?: string = '.json',
 ): Array => {
-  const fileNames = fs.readdirSync(directoryPath)
+  const fileNames = fs
+    .readdirSync(directoryPath)
     .filter((fileName: string): boolean => fileName.endsWith(fileExtension));
 
   return fileNames.map((fileName: string): FileObject => ({
@@ -57,13 +58,18 @@ const getFiles = (
 
 const parseFile = (file: Buffer): Object => JSON.parse(file.toString());
 
-const mapOwnerID = (userIDsMap: Map): (item: Object) => Object =>
-  (item: Object): Object => ({
-    ...item, ownerID: userIDsMap.get(item.ownerID) || null,
-  });
+const mapOwnerID = (
+  userIDsMap: Map,
+): ((item: Object) => Object) => (item: Object): Object => ({
+  ...item,
+  ownerID: userIDsMap.get(item.ownerID) || null,
+});
 
-const translateDeviceID = (item: Object): Object =>
-  ({ ...item, _id: new ObjectId(item.deviceID), id: item.deviceID });
+const translateDeviceID = (item: Object): Object => ({
+  ...item,
+  _id: new ObjectId(item.deviceID),
+  id: item.deviceID,
+});
 
 // eslint-disable-next-line no-unused-vars
 const filterID = ({ id, ...otherProps }: Object): Object => ({ ...otherProps });
@@ -71,9 +77,8 @@ const filterID = ({ id, ...otherProps }: Object): Object => ({ ...otherProps });
 const insertItem = (
   database: Object,
   collectionName: string,
-): (item: Object) => Promise =>
-  async (item: Object): Promise =>
-    await database.insertOne(collectionName, item);
+): ((item: Object) => Promise) => async (item: Object): Promise =>
+  await database.insertOne(collectionName, item);
 
 const insertUsers = async (
   database: Object,
@@ -81,10 +86,12 @@ const insertUsers = async (
 ): Promise> => {
   const userIDsMap = new Map();
 
-  await Promise.all(users.map(async (user: Object): Promise => {
-    const insertedUser = await database.insertOne('users', filterID(user));
-    userIDsMap.set(user.id, insertedUser.id);
-  }));
+  await Promise.all(
+    users.map(async (user: Object): Promise => {
+      const insertedUser = await database.insertOne('users', filterID(user));
+      userIDsMap.set(user.id, insertedUser.id);
+    }),
+  );
 
   return userIDsMap;
 };
@@ -95,39 +102,37 @@ const insertUsers = async (
     const database = await setupDatabase();
     console.log(`Start migration to ${DATABASE_TYPE}`);
 
-    const users = getFiles(settings.USERS_DIRECTORY)
-      .map(
-        ({ fileBuffer }: FileObject): Object => parseFile(fileBuffer),
-      );
+    const users = getFiles(
+      settings.USERS_DIRECTORY,
+    ).map(({ fileBuffer }: FileObject): Object => parseFile(fileBuffer));
 
     const userIDsMap = await insertUsers(database, users);
 
-    await Promise.all(getFiles(settings.WEBHOOKS_DIRECTORY)
-      .map(
-        ({ fileBuffer }: FileObject): Object => parseFile(fileBuffer),
-      )
-      .map(mapOwnerID(userIDsMap))
-      .map(filterID)
-      .map(insertItem(database, 'webhooks')),
+    await Promise.all(
+      getFiles(settings.WEBHOOKS_DIRECTORY)
+        .map(({ fileBuffer }: FileObject): Object => parseFile(fileBuffer))
+        .map(mapOwnerID(userIDsMap))
+        .map(filterID)
+        .map(insertItem(database, 'webhooks')),
     );
 
-    await Promise.all(getFiles(settings.DEVICE_DIRECTORY)
-      .map(
-        ({ fileBuffer }: FileObject): Object => parseFile(fileBuffer),
-      )
-      .map(mapOwnerID(userIDsMap))
-      .map(translateDeviceID)
-      .map(filterID)
-      .map(insertItem(database, 'deviceAttributes')),
+    await Promise.all(
+      getFiles(settings.DEVICE_DIRECTORY)
+        .map(({ fileBuffer }: FileObject): Object => parseFile(fileBuffer))
+        .map(mapOwnerID(userIDsMap))
+        .map(translateDeviceID)
+        .map(filterID)
+        .map(insertItem(database, 'deviceAttributes')),
     );
 
-    await Promise.all(getFiles(settings.DEVICE_DIRECTORY, '.pub.pem')
-      .map(({ fileName, fileBuffer }: FileObject): DeviceKeyObject => ({
-        algorithm: 'rsa',
-        deviceID: fileName.substring(0, fileName.indexOf('.pub.pem')),
-        key: fileBuffer.toString(),
-      }))
-      .map(insertItem(database, 'deviceKeys')),
+    await Promise.all(
+      getFiles(settings.DEVICE_DIRECTORY, '.pub.pem')
+        .map(({ fileName, fileBuffer }: FileObject): DeviceKeyObject => ({
+          algorithm: 'rsa',
+          deviceID: fileName.substring(0, fileName.indexOf('.pub.pem')),
+          key: fileBuffer.toString(),
+        }))
+        .map(insertItem(database, 'deviceKeys')),
     );
 
     console.log('All files migrated to the database successfully!');
diff --git a/src/types.js b/src/types.js
index d0fb69d6..5102b06a 100644
--- a/src/types.js
+++ b/src/types.js
@@ -57,7 +57,6 @@ export type Client = {
   grants: Array,
 };
 
-
 export type Device = DeviceAttributes & {
   connected: boolean,
 };
@@ -104,10 +103,7 @@ export type EventData = {
   userID: string,
 };
 
-export type GrantType =
-  'bearer_token'|
-  'password'|
-  'refresh_token';
+export type GrantType = 'bearer_token' | 'password' | 'refresh_token';
 
 export type TokenObject = {
   accessToken: string,
@@ -167,7 +163,7 @@ export type Settings = {
     PORT: number,
   },
   USERS_DIRECTORY: string,
-  WEBHOOK_TEMPLATE_PARAMETERS: {[key: string]: string},
+  WEBHOOK_TEMPLATE_PARAMETERS: { [key: string]: string },
   WEBHOOKS_DIRECTORY: string,
 };
 
@@ -197,29 +193,31 @@ export type Product = {
 };
 
 export interface IBaseRepository {
-  create(model: TModel | $Shape): Promise;
-  deleteByID(id: string): Promise;
-  getAll(): Promise>;
-  getByID(id: string): Promise;
-  updateByID(id: string, props: $Shape): Promise;
+  create(model: TModel | $Shape): Promise,
+  deleteByID(id: string): Promise,
+  getAll(): Promise>,
+  getByID(id: string): Promise,
+  updateByID(id: string, props: $Shape): Promise,
 }
 
 export interface IWebhookRepository extends IBaseRepository {}
 
-export interface IDeviceAttributeRepository extends IBaseRepository {}
+export interface IDeviceAttributeRepository
+  extends IBaseRepository {}
 
-export interface IDeviceKeyRepository extends IBaseRepository {}
+export interface IDeviceKeyRepository
+  extends IBaseRepository {}
 
 export interface IUserRepository extends IBaseRepository {
-  createWithCredentials(credentials: UserCredentials): Promise;
-  deleteAccessToken(userID: string, accessToken: string): Promise;
-  getByAccessToken(accessToken: string): Promise;
-  getByUsername(username: string): Promise;
-  getCurrentUser(): User;
-  isUserNameInUse(username: string): Promise;
-  saveAccessToken(userID: string, tokenObject: TokenObject): Promise;
-  setCurrentUser(user: User): void;
-  validateLogin(username: string, password: string): Promise;
+  createWithCredentials(credentials: UserCredentials): Promise,
+  deleteAccessToken(userID: string, accessToken: string): Promise,
+  getByAccessToken(accessToken: string): Promise,
+  getByUsername(username: string): Promise,
+  getCurrentUser(): User,
+  isUserNameInUse(username: string): Promise,
+  saveAccessToken(userID: string, tokenObject: TokenObject): Promise,
+  setCurrentUser(user: User): void,
+  validateLogin(username: string, password: string): Promise,
 }
 
 export interface IDeviceFirmwareRepository {
@@ -227,16 +225,16 @@ export interface IDeviceFirmwareRepository {
 }
 
 export interface IBaseDatabase {
-  find(collectionName: string, ...args: Array): Promise<*>;
-  findAndModify(collectionName: string, ...args: Array): Promise<*>;
-  findOne(collectionName: string, ...args: Array): Promise<*>;
-  insertOne(collectionName: string, ...args: Array): Promise<*>;
-  remove(collectionName: string, query: Object): Promise<*>;
+  find(collectionName: string, ...args: Array): Promise<*>,
+  findAndModify(collectionName: string, ...args: Array): Promise<*>,
+  findOne(collectionName: string, ...args: Array): Promise<*>,
+  insertOne(collectionName: string, ...args: Array): Promise<*>,
+  remove(collectionName: string, query: Object): Promise<*>,
 }
 
 export interface ILogger {
-  static error(params: Array): void;
-  static info(params: Array): void;
-  static log(params: Array): void;
-  static warn(params: Array): void;
+  static error(params: Array): void,
+  static info(params: Array): void,
+  static log(params: Array): void,
+  static warn(params: Array): void,
 }
diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js
index 01c989f0..1bcbc213 100644
--- a/test/DeviceClaimsController.test.js
+++ b/test/DeviceClaimsController.test.js
@@ -21,23 +21,24 @@ test.before(async () => {
     container.constitute('EventPublisher'),
     'publishAndListenForResponse',
     ({ name }) => {
-      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
+      if (name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
         return { error: new Error('Could not get device for ID') };
       }
-      if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
+      if (name === SPARK_SERVER_EVENTS.PING_DEVICE) {
         return {
           connected: true,
           lastHeard: new Date(),
         };
       }
-    }
+    },
   );
 
   const userResponse = await request(app)
     .post('/v1/users')
     .send(USER_CREDENTIALS);
 
-  testUser = await container.constitute('UserRepository')
+  testUser = await container
+    .constitute('UserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
@@ -69,23 +70,18 @@ test.before(async () => {
   }
 });
 
+test("should return claimCode, and user's devices ids", async t => {
+  const response = await request(app)
+    .post(`/v1/device_claims`)
+    .set('Content-Type', 'application/x-www-form-urlencoded')
+    .send({ access_token: userToken });
 
-test(
-  'should return claimCode, and user\'s devices ids',
-  async t => {
-    const response = await request(app)
-      .post(`/v1/device_claims`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .send({ access_token: userToken });
-
-    t.is(response.status, 200);
-    t.truthy(response.body.claim_code);
-    t.truthy(
-      response.body.device_ids &&
-      response.body.device_ids[0] === DEVICE_ID
-    );
-  },
-);
+  t.is(response.status, 200);
+  t.truthy(response.body.claim_code);
+  t.truthy(
+    response.body.device_ids && response.body.device_ids[0] === DEVICE_ID,
+  );
+});
 
 test.after.always(async (): Promise => {
   await container.constitute('UserRepository').deleteByID(testUser.id);
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index a8a3f33e..fbca0154 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -33,16 +33,16 @@ test.before(async () => {
     container.constitute('EventPublisher'),
     'publishAndListenForResponse',
     ({
-       name,
-       context: {
-         deviceID,
-         functionArguments,
-         functionName,
-         shouldShowSignal,
-         variableName,
-       },
+      name,
+      context: {
+        deviceID,
+        functionArguments,
+        functionName,
+        shouldShowSignal,
+        variableName,
+      },
     }) => {
-      if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
+      if (name === SPARK_SERVER_EVENTS.PING_DEVICE) {
         return deviceID === CONNECTED_DEVICE_ID
           ? {
               connected: true,
@@ -54,19 +54,19 @@ test.before(async () => {
             };
       }
 
-      if(deviceID !== CONNECTED_DEVICE_ID) {
-        return { error: new Error('Could not get device for ID')};
+      if (deviceID !== CONNECTED_DEVICE_ID) {
+        return { error: new Error('Could not get device for ID') };
       }
 
-      if(name === SPARK_SERVER_EVENTS.CALL_DEVICE_FUNCTION) {
-        if(TEST_DEVICE_FUNTIONS.includes(functionName)) {
-          return functionArguments.argument
+      if (name === SPARK_SERVER_EVENTS.CALL_DEVICE_FUNCTION) {
+        if (TEST_DEVICE_FUNTIONS.includes(functionName)) {
+          return functionArguments.argument;
         } else {
           return { error: new Error(`Unknown Function ${functionName}`) };
         }
       }
 
-      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
+      if (name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
         return {
           deviceID: CONNECTED_DEVICE_ID,
           functions: TEST_DEVICE_FUNTIONS,
@@ -76,21 +76,21 @@ test.before(async () => {
         };
       }
 
-      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_VARIABLE_VALUE) {
-        if(!TEST_DEVICE_VARIABLES.includes(variableName)) {
-          throw new Error(`Variable not found`)
+      if (name === SPARK_SERVER_EVENTS.GET_DEVICE_VARIABLE_VALUE) {
+        if (!TEST_DEVICE_VARIABLES.includes(variableName)) {
+          throw new Error(`Variable not found`);
         }
         return { result: TEST_VARIABLE_RESULT };
       }
 
       if (name === SPARK_SERVER_EVENTS.FLASH_DEVICE) {
-        return { status: 'Update finished' }
+        return { status: 'Update finished' };
       }
 
-      if(name === SPARK_SERVER_EVENTS.RAISE_YOUR_HAND) {
+      if (name === SPARK_SERVER_EVENTS.RAISE_YOUR_HAND) {
         return shouldShowSignal ? { status: 1 } : { status: 0 };
       }
-    }
+    },
   );
   const { filePath, fileBuffer } = await TestData.createCustomFirmwareBinary();
   customFirmwareFilePath = filePath;
@@ -100,7 +100,8 @@ test.before(async () => {
     .post('/v1/users')
     .send(USER_CREDENTIALS);
 
-  testUser = await container.constitute('UserRepository')
+  testUser = await container
+    .constitute('UserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
@@ -150,47 +151,40 @@ test('should throw an error for compile source code endpoint', async t => {
   t.is(response.status, 400);
 });
 
+test.serial('should return device details for connected device', async t => {
+  const response = await request(app)
+    .get(`/v1/devices/${CONNECTED_DEVICE_ID}`)
+    .query({ access_token: userToken });
 
-test.serial(
-  'should return device details for connected device',
-  async t => {
-    const response = await request(app)
-      .get(`/v1/devices/${CONNECTED_DEVICE_ID}`)
-      .query({ access_token: userToken });
-
-    t.is(response.status, 200);
-    t.is(response.body.connected, true);
-    t.is(
-      JSON.stringify(response.body.functions),
-      JSON.stringify(TEST_DEVICE_FUNTIONS),
-    );
-    t.is(response.body.id, connectedDeviceToApiAttributes.id);
-    t.is(response.body.name, connectedDeviceToApiAttributes.name);
-    t.is(response.body.ownerID, connectedDeviceToApiAttributes.ownerID);
-    t.is(
-      JSON.stringify(response.body.variables),
-      JSON.stringify(TEST_DEVICE_VARIABLES),
-    );
-    t.is(response.body.last_heard, TEST_LAST_HEARD.toISOString());
-  },
-);
+  t.is(response.status, 200);
+  t.is(response.body.connected, true);
+  t.is(
+    JSON.stringify(response.body.functions),
+    JSON.stringify(TEST_DEVICE_FUNTIONS),
+  );
+  t.is(response.body.id, connectedDeviceToApiAttributes.id);
+  t.is(response.body.name, connectedDeviceToApiAttributes.name);
+  t.is(response.body.ownerID, connectedDeviceToApiAttributes.ownerID);
+  t.is(
+    JSON.stringify(response.body.variables),
+    JSON.stringify(TEST_DEVICE_VARIABLES),
+  );
+  t.is(response.body.last_heard, TEST_LAST_HEARD.toISOString());
+});
 
-test.serial(
-  'should return device details for disconnected device',
-  async t => {
-    const response = await request(app)
-      .get(`/v1/devices/${DISCONNECTED_DEVICE_ID}`)
-      .query({ access_token: userToken });
+test.serial('should return device details for disconnected device', async t => {
+  const response = await request(app)
+    .get(`/v1/devices/${DISCONNECTED_DEVICE_ID}`)
+    .query({ access_token: userToken });
 
-    t.is(response.status, 200);
-    t.is(response.body.connected, false);
-    t.is(response.body.functions, null);
-    t.is(response.body.id, disconnectedDeviceToApiAttributes.id);
-    t.is(response.body.name, disconnectedDeviceToApiAttributes.name);
-    t.is(response.body.ownerID, disconnectedDeviceToApiAttributes.ownerID);
-    t.is(response.body.variables, null);
-  },
-);
+  t.is(response.status, 200);
+  t.is(response.body.connected, false);
+  t.is(response.body.functions, null);
+  t.is(response.body.id, disconnectedDeviceToApiAttributes.id);
+  t.is(response.body.name, disconnectedDeviceToApiAttributes.name);
+  t.is(response.body.ownerID, disconnectedDeviceToApiAttributes.ownerID);
+  t.is(response.body.variables, null);
+});
 
 test.serial('should throw an error if device not found', async t => {
   const response = await request(app)
@@ -265,10 +259,9 @@ test.serial(
 test.serial(
   'should throw an error if device belongs to somebody else',
   async t => {
-    const deviceAttributesStub = sinon.stub(
-      container.constitute('DeviceAttributeRepository'),
-      'getByID',
-    ).returns({ ownerID: TestData.getID()});
+    const deviceAttributesStub = sinon
+      .stub(container.constitute('DeviceAttributeRepository'), 'getByID')
+      .returns({ ownerID: TestData.getID() });
 
     const claimDeviceResponse = await request(app)
       .post('/v1/devices')
@@ -303,95 +296,77 @@ test.serial(
   },
 );
 
-test.serial(
-  'should throw an error if function doesn\'t exist',
-  async t => {
-    const callFunctionResponse = await request(app)
-      .post(`/v1/devices/${CONNECTED_DEVICE_ID}/wrong${TEST_DEVICE_FUNTIONS[0]}`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .send({
-        access_token: userToken,
-      });
+test.serial("should throw an error if function doesn't exist", async t => {
+  const callFunctionResponse = await request(app)
+    .post(`/v1/devices/${CONNECTED_DEVICE_ID}/wrong${TEST_DEVICE_FUNTIONS[0]}`)
+    .set('Content-Type', 'application/x-www-form-urlencoded')
+    .send({
+      access_token: userToken,
+    });
 
-    t.is(callFunctionResponse.status, 404);
-    t.is(callFunctionResponse.body.error, 'Function not found');
-  },
-);
+  t.is(callFunctionResponse.status, 404);
+  t.is(callFunctionResponse.body.error, 'Function not found');
+});
 
-test.serial(
-  'should return variable value',
-  async t => {
-    const getVariableResponse = await request(app)
-      .get(`/v1/devices/${CONNECTED_DEVICE_ID}/${TEST_DEVICE_VARIABLES[0]}/`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .query({ access_token: userToken });
+test.serial('should return variable value', async t => {
+  const getVariableResponse = await request(app)
+    .get(`/v1/devices/${CONNECTED_DEVICE_ID}/${TEST_DEVICE_VARIABLES[0]}/`)
+    .set('Content-Type', 'application/x-www-form-urlencoded')
+    .query({ access_token: userToken });
 
-    t.is(getVariableResponse.status, 200);
-    t.is(getVariableResponse.body.result, TEST_VARIABLE_RESULT);
-  },
-);
+  t.is(getVariableResponse.status, 200);
+  t.is(getVariableResponse.body.result, TEST_VARIABLE_RESULT);
+});
 
-test.serial(
-  'should throw an error if variable not found',
-  async t => {
-    const getVariableResponse = await request(app)
-      .get(`/v1/devices/${CONNECTED_DEVICE_ID}/wrong${TEST_DEVICE_VARIABLES[0]}/`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .query({ access_token: userToken });
+test.serial('should throw an error if variable not found', async t => {
+  const getVariableResponse = await request(app)
+    .get(`/v1/devices/${CONNECTED_DEVICE_ID}/wrong${TEST_DEVICE_VARIABLES[0]}/`)
+    .set('Content-Type', 'application/x-www-form-urlencoded')
+    .query({ access_token: userToken });
 
-    t.is(getVariableResponse.status, 404);
-    t.is(getVariableResponse.body.error, 'Variable not found');
-  },
-);
+  t.is(getVariableResponse.status, 404);
+  t.is(getVariableResponse.body.error, 'Variable not found');
+});
 
-test.serial(
-  'should rename device',
-  async t => {
-    const newDeviceName = 'newDeviceName';
+test.serial('should rename device', async t => {
+  const newDeviceName = 'newDeviceName';
 
-    const renameDeviceResponse = await request(app)
-      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .send({
-        access_token: userToken,
-        name: newDeviceName,
-      });
+  const renameDeviceResponse = await request(app)
+    .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
+    .set('Content-Type', 'application/x-www-form-urlencoded')
+    .send({
+      access_token: userToken,
+      name: newDeviceName,
+    });
 
-    t.is(renameDeviceResponse.status, 200);
-    t.is(renameDeviceResponse.body.name, newDeviceName);
-  },
-);
+  t.is(renameDeviceResponse.status, 200);
+  t.is(renameDeviceResponse.body.name, newDeviceName);
+});
 
-test.serial(
-  'should invoke raise your hand on device',
-  async t => {
-    const raiseYourHandResponse = await request(app)
-      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .send({
-        access_token: userToken,
-        signal: '1',
-      });
+test.serial('should invoke raise your hand on device', async t => {
+  const raiseYourHandResponse = await request(app)
+    .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
+    .set('Content-Type', 'application/x-www-form-urlencoded')
+    .send({
+      access_token: userToken,
+      signal: '1',
+    });
 
-    t.is(raiseYourHandResponse.status, 200);
-  },
-);
+  t.is(raiseYourHandResponse.status, 200);
+});
 
-test.serial(
-  'should throw an error if signal is wrong value',
-  async t => {
-    const raiseYourHandResponse = await request(app)
-      .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
-      .set('Content-Type', 'application/x-www-form-urlencoded')
-      .send({
-        access_token: userToken,
-        signal: 'some wrong value',
-      });
+test.serial('should throw an error if signal is wrong value', async t => {
+  const raiseYourHandResponse = await request(app)
+    .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
+    .set('Content-Type', 'application/x-www-form-urlencoded')
+    .send({
+      access_token: userToken,
+      signal: 'some wrong value',
+    });
 
-    t.is(raiseYourHandResponse.status, 400);
-    t.truthy(raiseYourHandResponse.body.error, 'Wrong signal value');
-  },
-);
+  t.is(raiseYourHandResponse.status, 400);
+  t.truthy(raiseYourHandResponse.body.error, 'Wrong signal value');
+});
 
 test.serial(
   'should start device flashing process with known application',
@@ -399,10 +374,9 @@ test.serial(
     const knownAppName = 'knownAppName';
     const knownAppBuffer = new Buffer(knownAppName);
 
-    const deviceFirmwareStub = sinon.stub(
-      container.constitute('DeviceFirmwareRepository'),
-      'getByName',
-    ).returns(knownAppBuffer);
+    const deviceFirmwareStub = sinon
+      .stub(container.constitute('DeviceFirmwareRepository'), 'getByName')
+      .returns(knownAppBuffer);
 
     const flashKnownAppResponse = await request(app)
       .put(`/v1/devices/${CONNECTED_DEVICE_ID}`)
@@ -433,10 +407,7 @@ test.serial(
       });
 
     t.is(flashKnownAppResponse.status, 404);
-    t.is(
-      flashKnownAppResponse.body.error,
-      `No firmware ${knownAppName} found`,
-    );
+    t.is(flashKnownAppResponse.body.error, `No firmware ${knownAppName} found`);
   },
 );
 
@@ -487,8 +458,16 @@ test.serial(
 test.after.always(async (): Promise => {
   await TestData.deleteCustomFirmwareBinary(customFirmwareFilePath);
   await container.constitute('UserRepository').deleteByID(testUser.id);
-  await container.constitute('DeviceAttributeRepository').deleteByID(CONNECTED_DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteByID(CONNECTED_DEVICE_ID);
-  await container.constitute('DeviceAttributeRepository').deleteByID(DISCONNECTED_DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteByID(DISCONNECTED_DEVICE_ID);
+  await container
+    .constitute('DeviceAttributeRepository')
+    .deleteByID(CONNECTED_DEVICE_ID);
+  await container
+    .constitute('DeviceKeyRepository')
+    .deleteByID(CONNECTED_DEVICE_ID);
+  await container
+    .constitute('DeviceAttributeRepository')
+    .deleteByID(DISCONNECTED_DEVICE_ID);
+  await container
+    .constitute('DeviceKeyRepository')
+    .deleteByID(DISCONNECTED_DEVICE_ID);
 });
diff --git a/test/EventsController.test.js b/test/EventsController.test.js
index 3d930c59..7632d191 100644
--- a/test/EventsController.test.js
+++ b/test/EventsController.test.js
@@ -2,4 +2,4 @@
 // TODO write EventsController tests
 import test from 'ava';
 
-test('eventsController test', t => {});
\ No newline at end of file
+test('eventsController test', t => {});
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index 11f0c12f..124b6366 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -22,23 +22,24 @@ test.before(async () => {
     container.constitute('EventPublisher'),
     'publishAndListenForResponse',
     ({ name }) => {
-      if(name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
+      if (name === SPARK_SERVER_EVENTS.GET_DEVICE_ATTRIBUTES) {
         return { error: new Error('Could not get device for ID') };
       }
-      if(name === SPARK_SERVER_EVENTS.PING_DEVICE) {
+      if (name === SPARK_SERVER_EVENTS.PING_DEVICE) {
         return {
           connected: true,
           lastHeard: new Date(),
         };
       }
-    }
+    },
   );
 
   const userResponse = await request(app)
     .post('/v1/users')
     .send(USER_CREDENTIALS);
 
-  testUser = await container.constitute('UserRepository')
+  testUser = await container
+    .constitute('UserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
diff --git a/test/UserFileRepository.test.js b/test/UserFileRepository.test.js
index 486f34fc..38f79474 100644
--- a/test/UserFileRepository.test.js
+++ b/test/UserFileRepository.test.js
@@ -5,43 +5,40 @@ import TestData from './setup/TestData';
 import UserFileRepository from '../src/repository/UserFileRepository';
 
 // Testing memoize
-test(
-  'should memoize and cache bust when mutator functions are called',
-  async t => {
-    let testIterator = 0;
-    const repository = new UserFileRepository('path');
-    const user = await repository.createWithCredentials(TestData.getUser());
-    const fileManager = repository._fileManager;
-    const getAllSpy = sinon.spy(fileManager, 'getAllData');
-    const getByIDSpy = sinon.spy(fileManager, 'getFile');
-    const deleteByIDSpy = sinon.spy(fileManager, 'deleteFile');
-
-    async function testAllAccessors() {
-      testIterator++;
-
-      await repository.getAll();
-      t.truthy(getAllSpy.callCount === testIterator);
-      await repository.getAll();
-      t.truthy(getAllSpy.callCount === testIterator);
-
-      await repository.getByID(user.id);
-      t.truthy(getByIDSpy.callCount === testIterator);
-      await repository.getByID(user.id);
-      t.truthy(getByIDSpy.callCount === testIterator);
-
-      await repository.getByUsername(user.username);
-      t.truthy(getAllSpy.callCount === testIterator);
-      await repository.getByUsername(user.username);
-      t.truthy(getAllSpy.callCount === testIterator);
-    };
-
-    await testAllAccessors();
-
-    await repository.updateByID(user.id, user);
-    await testAllAccessors();
-
-    await repository.deleteByID(user.id);
-    await testAllAccessors();
-    t.truthy(deleteByIDSpy.callCount === 1);
-  },
-);
+test('should memoize and cache bust when mutator functions are called', async t => {
+  let testIterator = 0;
+  const repository = new UserFileRepository('path');
+  const user = await repository.createWithCredentials(TestData.getUser());
+  const fileManager = repository._fileManager;
+  const getAllSpy = sinon.spy(fileManager, 'getAllData');
+  const getByIDSpy = sinon.spy(fileManager, 'getFile');
+  const deleteByIDSpy = sinon.spy(fileManager, 'deleteFile');
+
+  async function testAllAccessors() {
+    testIterator++;
+
+    await repository.getAll();
+    t.truthy(getAllSpy.callCount === testIterator);
+    await repository.getAll();
+    t.truthy(getAllSpy.callCount === testIterator);
+
+    await repository.getByID(user.id);
+    t.truthy(getByIDSpy.callCount === testIterator);
+    await repository.getByID(user.id);
+    t.truthy(getByIDSpy.callCount === testIterator);
+
+    await repository.getByUsername(user.username);
+    t.truthy(getAllSpy.callCount === testIterator);
+    await repository.getByUsername(user.username);
+    t.truthy(getAllSpy.callCount === testIterator);
+  }
+
+  await testAllAccessors();
+
+  await repository.updateByID(user.id, user);
+  await testAllAccessors();
+
+  await repository.deleteByID(user.id);
+  await testAllAccessors();
+  t.truthy(deleteByIDSpy.callCount === 1);
+});
diff --git a/test/UsersController.test.js b/test/UsersController.test.js
index 9f1a4e33..c59361bf 100644
--- a/test/UsersController.test.js
+++ b/test/UsersController.test.js
@@ -1,5 +1,5 @@
 /* eslint-disable */
-import type {  TokenObject, UserCredentials } from '../src/types';
+import type { TokenObject, UserCredentials } from '../src/types';
 
 import test from 'ava';
 import request from 'supertest';
@@ -15,11 +15,10 @@ let userToken;
 test.serial('should create new user', async t => {
   USER_CREDENTIALS = TestData.getUser();
 
-  const response = await request(app)
-    .post('/v1/users')
-    .send(USER_CREDENTIALS);
+  const response = await request(app).post('/v1/users').send(USER_CREDENTIALS);
 
-  user = await container.constitute('UserRepository')
+  user = await container
+    .constitute('UserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   t.is(response.status, 200);
@@ -28,9 +27,7 @@ test.serial('should create new user', async t => {
 });
 
 test.serial('should throw an error if username already in use', async t => {
-  const response = await request(app)
-    .post('/v1/users')
-    .send(USER_CREDENTIALS);
+  const response = await request(app).post('/v1/users').send(USER_CREDENTIALS);
   t.is(response.status, 400);
   t.is(response.body.error, 'user with the username already exists');
 });
@@ -64,7 +61,6 @@ test.serial('should return all access tokens for the user', async t => {
   t.truthy(Array.isArray(tokens) && tokens.length > 0);
 });
 
-
 test.serial('should delete the access token for the user', async t => {
   const deleteResponse = await request(app)
     .delete(`/v1/access_tokens/${userToken}`)
@@ -77,9 +73,12 @@ test.serial('should delete the access token for the user', async t => {
   const allTokens = allTokensResponse.body;
 
   t.is(deleteResponse.status, 200);
-  t.falsy(allTokens.some((tokenObject: TokenObject): boolean =>
-    tokenObject.accessToken === userToken,
-  ));
+  t.falsy(
+    allTokens.some(
+      (tokenObject: TokenObject): boolean =>
+        tokenObject.accessToken === userToken,
+    ),
+  );
 });
 
 test.after.always(async (): Promise => {
diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js
index 2ce6c02f..26dfff28 100644
--- a/test/WebhookManager.test.js
+++ b/test/WebhookManager.test.js
@@ -41,20 +41,19 @@ test.beforeEach(t => {
   t.context.eventPublisher = eventPublisher;
 });
 
-test(
-  'should run basic request',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = 'testData';
-    const event = getEvent(data);
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+test('should run basic request', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = 'testData';
+  const event = getEvent(data);
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
       t.is(requestOptions.form, undefined);
@@ -62,29 +61,28 @@ test(
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(WEBHOOK_BASE, event);
-  },
-);
-
-test(
-  'should run basic request without default data',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      noDefaults: true,
-    };
-    const data = 'testData';
-    const event = getEvent(data);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(WEBHOOK_BASE, event);
+});
+
+test('should run basic request without default data', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const webhook = {
+    ...WEBHOOK_BASE,
+    noDefaults: true,
+  };
+  const data = 'testData';
+  const event = getEvent(data);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
       t.is(requestOptions.form, undefined);
@@ -92,32 +90,31 @@ test(
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile json body',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = '{"t":"123"}';
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      json: {
-        "testValue": "{{t}}"
-      },
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile json body', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = '{"t":"123"}';
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    json: {
+      testValue: '{{t}}',
+    },
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(
         JSON.stringify(requestOptions.body),
@@ -128,33 +125,32 @@ test(
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile form body',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = '{"t":"123","g": "foo bar"}';
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      form: {
-        "testValue": "{{t}}",
-        "testValue2": "{{g}}"
-      },
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile form body', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = '{"t":"123","g": "foo bar"}';
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    form: {
+      testValue: '{{t}}',
+      testValue2: '{{g}}',
+    },
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
       t.is(
@@ -169,33 +165,32 @@ test(
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile request auth header',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = `{"username":"123","password": "foobar"}`;
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      auth: {
-        "username": "{{username}}",
-        "password": "{{password}}"
-      },
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile request auth header', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = `{"username":"123","password": "foobar"}`;
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    auth: {
+      username: '{{username}}',
+      password: '{{password}}',
+    },
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(
         JSON.stringify(requestOptions.auth),
         JSON.stringify({
@@ -208,33 +203,32 @@ test(
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile request headers',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = `{"t":"123","g": "foobar"}`;
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      headers: {
-        "testHeader1": "{{t}}",
-        "testHeader2": "{{g}}"
-      },
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile request headers', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = `{"t":"123","g": "foobar"}`;
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    headers: {
+      testHeader1: '{{t}}',
+      testHeader2: '{{g}}',
+    },
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
       t.is(requestOptions.form, undefined);
@@ -247,30 +241,29 @@ test(
       );
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile request url',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = '{"t":"123","g": "foobar"}';
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      url: 'https://test.com/{{t}}/{{g}}',
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile request url', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = '{"t":"123","g": "foobar"}';
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    url: 'https://test.com/{{t}}/{{g}}',
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
       t.is(requestOptions.form, undefined);
@@ -278,33 +271,32 @@ test(
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
       t.is(requestOptions.url, 'https://test.com/123/foobar');
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile request query',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = '{"t":"123","g": "foobar"}';
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      query: {
-        testValue: '{{t}}',
-        testValue2: '{{g}}',
-      },
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile request query', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = '{"t":"123","g": "foobar"}';
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    query: {
+      testValue: '{{t}}',
+      testValue2: '{{g}}',
+    },
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
       t.is(requestOptions.form, undefined);
@@ -315,160 +307,146 @@ test(
         JSON.stringify({ testValue: '123', testValue2: 'foobar' }),
       );
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile requestType',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const data = `{"t":"123","requestType": "post"}`;
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      requestType: "{{requestType}}",
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-    manager._callWebhook = sinon.spy((
-      webhook: Webhook,
-      event: Event,
-      requestOptions: RequestOptions,
-    ) => {
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile requestType', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const data = `{"t":"123","requestType": "post"}`;
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    requestType: '{{requestType}}',
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  manager._callWebhook = sinon.spy(
+    (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
       t.is(requestOptions.form, undefined);
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, 'POST');
       t.is(requestOptions.url, WEBHOOK_BASE.url);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should throw an error if wrong requestType is provided',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const testRequestType = 'wrongRequestType';
-    const data = `{"t":"123","requestType": "${testRequestType}"}`;
-    const event = getEvent(data);
-    const webhook = {
-      ...WEBHOOK_BASE,
-      requestType: "{{requestType}}",
-    };
-    const defaultRequestData = getDefaultRequestData(event);
-
-
-
-    Logger.error = sinon.spy((message: string) => {
-      t.is(message, 'webhookError: Error: wrong requestType');
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should publish sent event',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const event = getEvent();
-
-    t.context.eventPublisher.publish = sinon.spy(({
-      data,
-      name,
-      userID,
-    }) => {
-      t.is(data, undefined);
-      t.is(name, `hook-sent/${event.name}`);
-      t.is(userID, event.userID);
-    });
-
-    manager.runWebhook(WEBHOOK_BASE, event);
-  },
-);
-
-test(
-  'should publish default topic',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const event = getEvent();
-    manager._callWebhook = sinon.stub().returns('data');
-
-    t.context.eventPublisher.publish = sinon.spy(({
-      data,
-      name,
-      userID,
-    }) => {
-      t.is(data.toString(), 'data');
-      t.is(name, `hook-response/${event.name}/0`);
-      t.is(userID, event.userID);
-    });
-
-    manager.runWebhook(WEBHOOK_BASE, event);
-  },
-);
-
-test(
-  'should compile response topic and publish',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const event = getEvent();
-    const webhook = {
-      ...WEBHOOK_BASE,
-      responseTopic: 'hook-response/tappt_request-pour-{{SPARK_CORE_ID}}',
-    };
-    manager._callWebhook = sinon.stub().returns('data');
-
-    t.context.eventPublisher.publish = sinon.spy(({
-      data,
-      name,
-      userID,
-    }) => {
-      t.is(data.toString(), 'data');
-      t.is(name, `hook-response/tappt_request-pour-${event.deviceID}/0`);
-      t.is(userID, event.userID);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
-
-test(
-  'should compile response body and publish',
-  async t => {
-    const manager =
-      new WebhookManager(t.context.eventPublisher, null, null, t.context.repository);
-    const event = getEvent();
-    const webhook = {
-      ...WEBHOOK_BASE,
-      responseTemplate: 'testVar: {{t}}, testVar2: {{g}}',
-    };
-    manager._callWebhook = sinon.stub().returns({
-      g: 'foobar',
-      t: 123,
-    });
-
-    t.context.eventPublisher.publish = sinon.spy(({
-      data,
-      name,
-      userID,
-    }) => {
-      t.is(data.toString(), 'testVar: 123, testVar2: foobar');
-      t.is(name, `hook-response/${event.name}/0`);
-      t.is(userID, event.userID);
-    });
-
-    manager.runWebhook(webhook, event);
-  },
-);
+    },
+  );
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should throw an error if wrong requestType is provided', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const testRequestType = 'wrongRequestType';
+  const data = `{"t":"123","requestType": "${testRequestType}"}`;
+  const event = getEvent(data);
+  const webhook = {
+    ...WEBHOOK_BASE,
+    requestType: '{{requestType}}',
+  };
+  const defaultRequestData = getDefaultRequestData(event);
+
+  Logger.error = sinon.spy((message: string) => {
+    t.is(message, 'webhookError: Error: wrong requestType');
+  });
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should publish sent event', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const event = getEvent();
+
+  t.context.eventPublisher.publish = sinon.spy(({ data, name, userID }) => {
+    t.is(data, undefined);
+    t.is(name, `hook-sent/${event.name}`);
+    t.is(userID, event.userID);
+  });
+
+  manager.runWebhook(WEBHOOK_BASE, event);
+});
+
+test('should publish default topic', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const event = getEvent();
+  manager._callWebhook = sinon.stub().returns('data');
+
+  t.context.eventPublisher.publish = sinon.spy(({ data, name, userID }) => {
+    t.is(data.toString(), 'data');
+    t.is(name, `hook-response/${event.name}/0`);
+    t.is(userID, event.userID);
+  });
+
+  manager.runWebhook(WEBHOOK_BASE, event);
+});
+
+test('should compile response topic and publish', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const event = getEvent();
+  const webhook = {
+    ...WEBHOOK_BASE,
+    responseTopic: 'hook-response/tappt_request-pour-{{SPARK_CORE_ID}}',
+  };
+  manager._callWebhook = sinon.stub().returns('data');
+
+  t.context.eventPublisher.publish = sinon.spy(({ data, name, userID }) => {
+    t.is(data.toString(), 'data');
+    t.is(name, `hook-response/tappt_request-pour-${event.deviceID}/0`);
+    t.is(userID, event.userID);
+  });
+
+  manager.runWebhook(webhook, event);
+});
+
+test('should compile response body and publish', async t => {
+  const manager = new WebhookManager(
+    t.context.eventPublisher,
+    null,
+    null,
+    t.context.repository,
+  );
+  const event = getEvent();
+  const webhook = {
+    ...WEBHOOK_BASE,
+    responseTemplate: 'testVar: {{t}}, testVar2: {{g}}',
+  };
+  manager._callWebhook = sinon.stub().returns({
+    g: 'foobar',
+    t: 123,
+  });
+
+  t.context.eventPublisher.publish = sinon.spy(({ data, name, userID }) => {
+    t.is(data.toString(), 'testVar: 123, testVar2: foobar');
+    t.is(name, `hook-response/${event.name}/0`);
+    t.is(userID, event.userID);
+  });
+
+  manager.runWebhook(webhook, event);
+});
diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js
index e1e5fe18..1bf859e5 100644
--- a/test/WebhooksController.test.js
+++ b/test/WebhooksController.test.js
@@ -25,7 +25,8 @@ test.before(async () => {
     .post('/v1/users')
     .send(USER_CREDENTIALS);
 
-  testUser = await container.constitute('UserRepository')
+  testUser = await container
+    .constitute('UserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
@@ -41,7 +42,7 @@ test.before(async () => {
 
   userToken = tokenResponse.body.access_token;
 
-  if(!userToken) {
+  if (!userToken) {
     throw new Error('test user creation fails');
   }
 });
@@ -62,7 +63,7 @@ test.serial('should create a new webhook object', async t => {
   t.truthy(testWebhook.id && testWebhook.event && testWebhook.url);
 });
 
-test('should throw an error if event isn\'t provided', async t => {
+test("should throw an error if event isn't provided", async t => {
   const response = await request(app)
     .post('/v1/webhooks')
     .query({ access_token: userToken })
@@ -75,7 +76,7 @@ test('should throw an error if event isn\'t provided', async t => {
   t.is(response.body.error, 'no event name provided');
 });
 
-test('should throw an error if url isn\'t provided', async t => {
+test("should throw an error if url isn't provided", async t => {
   const response = await request(app)
     .post('/v1/webhooks')
     .query({ access_token: userToken })
@@ -88,7 +89,7 @@ test('should throw an error if url isn\'t provided', async t => {
   t.is(response.body.error, 'no url provided');
 });
 
-test('should throw an error if requestType isn\'t provided', async t => {
+test("should throw an error if requestType isn't provided", async t => {
   const response = await request(app)
     .post('/v1/webhooks')
     .query({ access_token: userToken })
@@ -138,9 +139,9 @@ test.serial('should delete webhook', async t => {
 
   const webhooks = allWebhooksResponse.body;
 
-  t.falsy(webhooks.some((webhook: Webhook): boolean =>
-    webhook.id === testWebhook.id,
-  ));
+  t.falsy(
+    webhooks.some((webhook: Webhook): boolean => webhook.id === testWebhook.id),
+  );
 });
 
 test.after.always(async (): Promise => {
diff --git a/test/setup/TestData.js b/test/setup/TestData.js
index 59758cb9..e0d55ce9 100644
--- a/test/setup/TestData.js
+++ b/test/setup/TestData.js
@@ -17,43 +17,34 @@ type CreateCustomFirmwareResult = {
 
 class TestData {
   static createCustomFirmwareBinary = (): Promise =>
-    new Promise((
-      resolve: (result: CreateCustomFirmwareResult) => void,
-      reject: (error: Error) => void,
-    ) => {
-      const filePath =
-        `${settings.CUSTOM_FIRMWARE_DIRECTORY}/customApp-${TestData.getID()}.bin`;
-      const fileBuffer = crypto.randomBytes(100);
+    new Promise(
+      (
+        resolve: (result: CreateCustomFirmwareResult) => void,
+        reject: (error: Error) => void,
+      ) => {
+        const filePath = `${settings.CUSTOM_FIRMWARE_DIRECTORY}/customApp-${TestData.getID()}.bin`;
+        const fileBuffer = crypto.randomBytes(100);
 
-      fs.writeFile(
-        filePath,
-        fileBuffer,
-        (error: ?Error) => {
+        fs.writeFile(filePath, fileBuffer, (error: ?Error) => {
           if (error) {
             reject(error);
             return;
           }
           resolve({ fileBuffer, filePath });
-        },
-      );
-    });
+        });
+      },
+    );
 
   static deleteCustomFirmwareBinary = (filePath: string): Promise =>
-    new Promise((
-      resolve: () => void,
-      reject: (error: Error) => void,
-    ) => {
-      fs.unlink(
-        filePath,
-        (error: ?Error) => {
-          if (error) {
-            reject(error);
-            return;
-          }
+    new Promise((resolve: () => void, reject: (error: Error) => void) => {
+      fs.unlink(filePath, (error: ?Error) => {
+        if (error) {
+          reject(error);
+          return;
+        }
 
-          resolve();
-        },
-      );
+        resolve();
+      });
     });
 
   static getUser = (): UserCredentials => ({
diff --git a/test/setup/getDefaultContainer.js b/test/setup/getDefaultContainer.js
index 8eff6ee6..22cbca6c 100644
--- a/test/setup/getDefaultContainer.js
+++ b/test/setup/getDefaultContainer.js
@@ -5,7 +5,6 @@ import defaultBindings from '../../src/defaultBindings';
 import settings from './settings';
 import DeviceServerMock from './DeviceServerMock';
 
-
 const container = new Container();
 // TODO - we should be creating different bindings per test so we can mock out
 // different modules to test
diff --git a/test/setup/settings.js b/test/setup/settings.js
index 5be69bbb..fc6ff16c 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -11,7 +11,10 @@ export default {
   DEVICE_DIRECTORY: path.join(__dirname, '../__test_data__/deviceKeys'),
   ENABLE_SYSTEM_FIRWMARE_AUTOUPDATES: true,
   FIRMWARE_DIRECTORY: path.join(__dirname, '../__test_data__/knownApps'),
-  FIRMWARE_REPOSITORY_DIRECTORY: path.join(__dirname, '../__test_data__/firmware'),
+  FIRMWARE_REPOSITORY_DIRECTORY: path.join(
+    __dirname,
+    '../__test_data__/firmware',
+  ),
   SERVER_KEY_FILENAME: 'default_key.pem',
   SERVER_KEYS_DIRECTORY: path.join(__dirname, '../__test_data__'),
   USERS_DIRECTORY: path.join(__dirname, '../__test_data__/users'),

From 1d475050ecc3e5e80a9dd51dd9a57a592127ccd8 Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Tue, 27 Jun 2017 07:53:10 +0200
Subject: [PATCH 450/504] Changed Infos in package.json

---
 package.json | 31 ++++++++++++++++++++++++++-----
 1 file changed, 26 insertions(+), 5 deletions(-)

diff --git a/package.json b/package.json
index d70262df..94bef55e 100644
--- a/package.json
+++ b/package.json
@@ -30,23 +30,41 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
-    "examples:build" : "babel ./examples --out-dir ./dist/examples --ignore node_modules",
-    "examples:run:eventprovider" : "node ./dist/examples/eventProvider.js",
+    "git-add": "git add",
     "lint": "eslint --fix --max-warnings 0 -- .",
+    "lint-staged": "lint-staged",
     "migrate-files-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",
     "migrate-files-to-nedb": "babel-node ./src/scripts/migrateFilesToDatabase nedb",
     "prebuild": "npm run build:clean",
-    "start:warn": "babel-node ./src/main.js --trace-warnings",
+    "prettify": "prettier --single-quote --trailing-comma all --write",
     "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
     "start:debug": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
     "start:prod": "node ./dist/main.js",
+    "start:warn": "babel-node ./src/main.js --trace-warnings",
     "test": "ava --serial --no-cache",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",
     "watch": "babel ./src --out-dir ./dist --watch"
   },
+  "lint-staged": {
+    "examples/**/*.js": [
+      "lint",
+      "prettify",
+      "git add"
+    ],
+    "src/**/*.js": [
+      "lint",
+      "prettify",
+      "git add"
+    ],
+    "test/**/*.js": [
+      "lint",
+      "prettify",
+      "git add"
+    ]
+  },
   "pre-commit": [
-    "lint",
+    "lint-staged",
     "test"
   ],
   "ava": {
@@ -104,15 +122,18 @@
     "babel-register": "^6.18.0",
     "eslint": "^3.11.0",
     "eslint-config-airbnb-base": "^10.0.1",
+    "eslint-config-prettier": "^2.2.0",
     "eslint-plugin-flowtype": "^2.28.2",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-sorting": "^0.3.0",
     "flow-bin": "^0.37.4",
+    "lint-staged": "^4.0.0",
     "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
+    "prettier": "^1.4.4",
     "rimraf": "^2.5.4",
     "sinon": "^1.17.7",
     "supertest": "^2.0.1",
     "uglifyjs": "^2.4.10"
   }
-}
+}
\ No newline at end of file

From 13e4a2a89ab89ea3c33a3ea02b23535c2a58d3fb Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Tue, 27 Jun 2017 08:52:21 +0200
Subject: [PATCH 451/504] Added Bunyan Logger

---
 dist/app.js                                 |  30 ++-
 dist/controllers/DevicesController.js       |  12 +-
 dist/controllers/EventsController.js        |  12 +-
 dist/controllers/OauthClientsController.js  | 126 ++++++++++--
 dist/controllers/ProductsController.js      | 210 ++++++++++++++------
 dist/defaultBindings.js                     |  10 -
 dist/exports.js                             |  12 +-
 dist/lib/WebhookLogger.js                   |  10 +-
 dist/lib/logger.js                          |  56 +++---
 dist/main.js                                |  23 ++-
 dist/managers/FirmwareCompilationManager.js |  12 +-
 dist/managers/PermissionManager.js          |  16 +-
 dist/managers/WebhookManager.js             |  15 +-
 dist/repository/MongoDb.js                  |  14 +-
 dist/repository/NeDb.js                     |   1 +
 dist/settings.js                            |   2 +-
 dist/types.js                               |   8 +-
 package.json                                |   4 +-
 src/app.js                                  |  30 ++-
 src/controllers/DevicesController.js        |   7 +
 src/controllers/EventsController.js         |   5 +-
 src/defaultBindings.js                      |   6 -
 src/exports.js                              |   3 +-
 src/lib/DefaultLogger.js                    |  65 ------
 src/lib/WebhookLogger.js                    |  10 +-
 src/lib/logger.js                           |  38 ++--
 src/main.js                                 |  29 +--
 src/managers/FirmwareCompilationManager.js  |   5 +-
 src/managers/PermissionManager.js           |  11 +-
 src/managers/WebhookManager.js              |   8 +-
 src/repository/MongoDb.js                   |  13 +-
 src/settings.js                             |   2 +-
 src/types.js                                |  11 +-
 33 files changed, 497 insertions(+), 319 deletions(-)
 delete mode 100644 src/lib/DefaultLogger.js

diff --git a/dist/app.js b/dist/app.js
index eb9bb1c0..310d3ddf 100644
--- a/dist/app.js
+++ b/dist/app.js
@@ -12,16 +12,22 @@ var _express = require('express');
 
 var _express2 = _interopRequireDefault(_express);
 
-var _morgan = require('morgan');
+var _logger = require('./lib/logger');
 
-var _morgan2 = _interopRequireDefault(_morgan);
+var _logger2 = _interopRequireDefault(_logger);
 
 var _RouteConfig = require('./RouteConfig');
 
 var _RouteConfig2 = _interopRequireDefault(_RouteConfig);
 
+var _bunyanMiddleware = require('bunyan-middleware');
+
+var _bunyanMiddleware2 = _interopRequireDefault(_bunyanMiddleware);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 exports.default = function (container, settings, existingApp) {
   var app = existingApp || (0, _express2.default)();
 
@@ -35,16 +41,28 @@ exports.default = function (container, settings, existingApp) {
       });
       return response.sendStatus(204);
     }
-    response.set({ 'Access-Control-Allow-Origin': '*' });
+    response.set({
+      'Access-Control-Allow-Origin': '*'
+    });
     return next();
   };
 
-  if (settings.LOG_REQUESTS) {
-    app.use((0, _morgan2.default)('[:date[iso]] :remote-addr - :remote-user ":method :url ' + 'HTTP/:http-version" :status :res[content-length] ":referrer" ' + '":user-agent"'));
+  if (logger.debug()) {
+    app.use((0, _bunyanMiddleware2.default)({
+      headerName: 'X-Request-Id',
+      level: 'debug',
+      logger: logger,
+      logName: 'req_id',
+      obscureHeaders: [],
+      propertyName: 'reqId'
+    }));
+    logger.warn('Request logging enabled');
   }
 
   app.use(_bodyParser2.default.json());
-  app.use(_bodyParser2.default.urlencoded({ extended: true }));
+  app.use(_bodyParser2.default.urlencoded({
+    extended: true
+  }));
   app.use(setCORSHeaders);
 
   (0, _RouteConfig2.default)(app, container, ['DeviceClaimsController',
diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js
index 3af6ae27..2de6e28e 100644
--- a/dist/controllers/DevicesController.js
+++ b/dist/controllers/DevicesController.js
@@ -74,6 +74,10 @@ var _deviceToAPI = require('../lib/deviceToAPI');
 
 var _deviceToAPI2 = _interopRequireDefault(_deviceToAPI);
 
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
@@ -105,6 +109,8 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = function (_Controller) {
   (0, _inherits3.default)(DevicesController, _Controller);
 
@@ -263,9 +269,13 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
               case 7:
                 _context5.prev = 7;
                 _context5.t0 = _context5['catch'](0);
+
+                // I wish we could return no devices found but meh :/
+                // at least we should issue a warning
+                logger.warn({ err: _context5.t0 }, 'get devices throws error, possibly no devices found?');
                 return _context5.abrupt('return', this.ok([]));
 
-              case 10:
+              case 11:
               case 'end':
                 return _context5.stop();
             }
diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js
index 93e7d37c..d789b73b 100644
--- a/dist/controllers/EventsController.js
+++ b/dist/controllers/EventsController.js
@@ -70,14 +70,14 @@ var _serverSentEvents = require('../decorators/serverSentEvents');
 
 var _serverSentEvents2 = _interopRequireDefault(_serverSentEvents);
 
-var _logger = require('../lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _eventToApi = require('../lib/eventToApi');
 
 var _eventToApi2 = _interopRequireDefault(_eventToApi);
 
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
@@ -109,6 +109,8 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/ping'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec12 = (0, _serverSentEvents2.default)(), _dec13 = (0, _httpVerb2.default)('post'), _dec14 = (0, _route2.default)('/v1/devices/events'), (_class = function (_Controller) {
   (0, _inherits3.default)(EventsController, _Controller);
 
@@ -145,7 +147,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
         this.response.write('event: ' + event.name + '\n');
         this.response.write('data: ' + (0, _stringify2.default)((0, _eventToApi2.default)(event)) + '\n\n');
       } catch (error) {
-        _logger2.default.error('pipeEvents - write error: ' + error);
+        logger.error({ err: error }, 'pipeEvents - write error');
         throw error;
       }
     }
diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js
index febf6743..145943b9 100644
--- a/dist/controllers/OauthClientsController.js
+++ b/dist/controllers/OauthClientsController.js
@@ -4,6 +4,18 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
+var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor');
+
+var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
 var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
 
 var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
@@ -24,6 +36,8 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
+var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _desc, _value, _class;
+
 var _Controller2 = require('./Controller');
 
 var _Controller3 = _interopRequireDefault(_Controller2);
@@ -42,7 +56,36 @@ var _route2 = _interopRequireDefault(_route);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var OauthClientsController = function (_Controller) {
+function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
+  var desc = {};
+  Object['ke' + 'ys'](descriptor).forEach(function (key) {
+    desc[key] = descriptor[key];
+  });
+  desc.enumerable = !!desc.enumerable;
+  desc.configurable = !!desc.configurable;
+
+  if ('value' in desc || desc.initializer) {
+    desc.writable = true;
+  }
+
+  desc = decorators.slice().reverse().reduce(function (desc, decorator) {
+    return decorator(target, property, desc) || desc;
+  }, desc);
+
+  if (context && desc.initializer !== void 0) {
+    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
+    desc.initializer = undefined;
+  }
+
+  if (desc.initializer === void 0) {
+    Object['define' + 'Property'](target, property, desc);
+    desc = null;
+  }
+
+  return desc;
+}
+
+var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/'), _dec3 = (0, _httpVerb2.default)('put'), _dec4 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), _dec5 = (0, _httpVerb2.default)('delete'), _dec6 = (0, _route2.default)('/v1/products/:productIDorSlug/clients/:clientID'), (_class = function (_Controller) {
   (0, _inherits3.default)(OauthClientsController, _Controller);
 
   function OauthClientsController() {
@@ -52,24 +95,77 @@ var OauthClientsController = function (_Controller) {
 
   (0, _createClass3.default)(OauthClientsController, [{
     key: 'createClient',
-    // eslint-disable-next-line class-methods-use-this
-    value: function createClient() {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
+    value: function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context.stop();
+            }
+          }
+        }, _callee, this);
+      }));
+
+      function createClient() {
+        return _ref.apply(this, arguments);
+      }
+
+      return createClient;
+    }()
   }, {
     key: 'editClient',
-    // eslint-disable-next-line class-methods-use-this
-    value: function editClient() {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
+    value: function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, this);
+      }));
+
+      function editClient() {
+        return _ref2.apply(this, arguments);
+      }
+
+      return editClient;
+    }()
   }, {
     key: 'deleteClient',
-    // eslint-disable-next-line class-methods-use-this
-    value: function deleteClient() {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
+    value: function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context3.stop();
+            }
+          }
+        }, _callee3, this);
+      }));
+
+      function deleteClient() {
+        return _ref3.apply(this, arguments);
+      }
+
+      return deleteClient;
+    }()
   }]);
   return OauthClientsController;
-}(_Controller3.default);
-
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'createClient', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'editClient', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'editClient'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteClient', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteClient'), _class.prototype)), _class));
 exports.default = OauthClientsController;
\ No newline at end of file
diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index a372e304..09840b45 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -36,7 +36,7 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _desc, _value, _class;
+var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _desc, _value, _class;
 /* eslint-disable */
 
 var _Controller2 = require('./Controller');
@@ -86,7 +86,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec9 = (0, _httpVerb2.default)('put'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec11 = (0, _httpVerb2.default)('get'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), (_class = function (_Controller) {
+var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec7 = (0, _httpVerb2.default)('post'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/device_claims'), _dec9 = (0, _httpVerb2.default)('get'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec11 = (0, _httpVerb2.default)('post'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec15 = (0, _httpVerb2.default)('put'), _dec16 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec17 = (0, _httpVerb2.default)('delete'), _dec18 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec19 = (0, _httpVerb2.default)('get'), _dec20 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec21 = (0, _httpVerb2.default)('get'), _dec22 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec23 = (0, _httpVerb2.default)('delete'), _dec24 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) {
   (0, _inherits3.default)(ProductsController, _Controller);
 
   function ProductsController(deviceManager) {
@@ -100,25 +100,13 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
   (0, _createClass3.default)(ProductsController, [{
     key: 'getProducts',
-    // eslint-disable-next-line class-methods-use-this
-    value: function getProducts() {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
-  }, {
-    key: 'createProduct',
-    // eslint-disable-next-line class-methods-use-this
-    value: function createProduct() {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
-  }, {
-    key: 'getProduct',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(productIdOrSlug) {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                throw new _HttpError2.default('not supported in the current server version');
 
               case 1:
               case 'end':
@@ -128,27 +116,21 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee, this);
       }));
 
-      function getProduct(_x) {
+      function getProducts() {
         return _ref.apply(this, arguments);
       }
 
-      return getProduct;
+      return getProducts;
     }()
   }, {
-    key: 'generateClaimCode',
-    // eslint-disable-next-line class-methods-use-this
-    value: function generateClaimCode(productIdOrSlug) {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
-  }, {
-    key: 'getFirmware',
+    key: 'createProduct',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(productIdOrSlug) {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                throw new _HttpError2.default('not supported in the current server version');
 
               case 1:
               case 'end':
@@ -158,17 +140,14 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee2, this);
       }));
 
-      function getFirmware(_x2) {
+      function createProduct() {
         return _ref2.apply(this, arguments);
       }
 
-      return getFirmware;
+      return createProduct;
     }()
-
-    // {version: number, name: 'current', binary: File, title: string, description: string}
-
   }, {
-    key: 'getFirmware',
+    key: 'getProduct',
     value: function () {
       var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -185,21 +164,21 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee3, this);
       }));
 
-      function getFirmware(_x3) {
+      function getProduct(_x) {
         return _ref3.apply(this, arguments);
       }
 
-      return getFirmware;
+      return getProduct;
     }()
   }, {
-    key: 'getDevices',
+    key: 'generateClaimCode',
     value: function () {
       var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                throw new _HttpError2.default('not supported in the current server version');
 
               case 1:
               case 'end':
@@ -209,16 +188,16 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee4, this);
       }));
 
-      function getDevices(_x4) {
+      function generateClaimCode(_x2) {
         return _ref4.apply(this, arguments);
       }
 
-      return getDevices;
+      return generateClaimCode;
     }()
   }, {
-    key: 'setFirmwareVersion',
+    key: 'getFirmware',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIdOrSlug, deviceID, body) {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
@@ -233,20 +212,17 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee5, this);
       }));
 
-      function setFirmwareVersion(_x5, _x6, _x7) {
+      function getFirmware(_x3) {
         return _ref5.apply(this, arguments);
       }
 
-      return setFirmwareVersion;
+      return getFirmware;
     }()
+
+    // {version: number, name: 'current', binary: File, title: string, description: string}
+
   }, {
-    key: 'removeDeviceFromProduct',
-    // eslint-disable-next-line class-methods-use-this
-    value: function removeDeviceFromProduct(productIdOrSlug, deviceID) {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
-  }, {
-    key: 'getConfig',
+    key: 'getFirmware',
     value: function () {
       var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
@@ -263,16 +239,16 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee6, this);
       }));
 
-      function getConfig(_x8) {
+      function getFirmware(_x4) {
         return _ref6.apply(this, arguments);
       }
 
-      return getConfig;
+      return getFirmware;
     }()
   }, {
-    key: 'getEvents',
+    key: 'getDevices',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIdOrSlug, eventName) {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
@@ -287,20 +263,134 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee7, this);
       }));
 
-      function getEvents(_x9, _x10) {
+      function getDevices(_x5) {
         return _ref7.apply(this, arguments);
       }
 
+      return getDevices;
+    }()
+  }, {
+    key: 'setFirmwareVersion',
+    value: function () {
+      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productIdOrSlug, deviceID, body) {
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
+          while (1) {
+            switch (_context8.prev = _context8.next) {
+              case 0:
+                throw new _HttpError2.default('Not implemented');
+
+              case 1:
+              case 'end':
+                return _context8.stop();
+            }
+          }
+        }, _callee8, this);
+      }));
+
+      function setFirmwareVersion(_x6, _x7, _x8) {
+        return _ref8.apply(this, arguments);
+      }
+
+      return setFirmwareVersion;
+    }()
+  }, {
+    key: 'removeDeviceFromProduct',
+    value: function () {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIdOrSlug, deviceID) {
+        return _regenerator2.default.wrap(function _callee9$(_context9) {
+          while (1) {
+            switch (_context9.prev = _context9.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context9.stop();
+            }
+          }
+        }, _callee9, this);
+      }));
+
+      function removeDeviceFromProduct(_x9, _x10) {
+        return _ref9.apply(this, arguments);
+      }
+
+      return removeDeviceFromProduct;
+    }()
+  }, {
+    key: 'getConfig',
+    value: function () {
+      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(productIdOrSlug) {
+        return _regenerator2.default.wrap(function _callee10$(_context10) {
+          while (1) {
+            switch (_context10.prev = _context10.next) {
+              case 0:
+                throw new _HttpError2.default('Not implemented');
+
+              case 1:
+              case 'end':
+                return _context10.stop();
+            }
+          }
+        }, _callee10, this);
+      }));
+
+      function getConfig(_x11) {
+        return _ref10.apply(this, arguments);
+      }
+
+      return getConfig;
+    }()
+  }, {
+    key: 'getEvents',
+    value: function () {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(productIdOrSlug, eventName) {
+        return _regenerator2.default.wrap(function _callee11$(_context11) {
+          while (1) {
+            switch (_context11.prev = _context11.next) {
+              case 0:
+                throw new _HttpError2.default('Not implemented');
+
+              case 1:
+              case 'end':
+                return _context11.stop();
+            }
+          }
+        }, _callee11, this);
+      }));
+
+      function getEvents(_x12, _x13) {
+        return _ref11.apply(this, arguments);
+      }
+
       return getEvents;
     }()
   }, {
     key: 'removeTeamMember',
-    // eslint-disable-next-line class-methods-use-this
-    value: function removeTeamMember(productIdOrSlug, username) {
-      throw new _HttpError2.default('not supported in the current server version');
-    }
+    value: function () {
+      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug, username) {
+        return _regenerator2.default.wrap(function _callee12$(_context12) {
+          while (1) {
+            switch (_context12.prev = _context12.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context12.stop();
+            }
+          }
+        }, _callee12, this);
+      }));
+
+      function removeTeamMember(_x14, _x15) {
+        return _ref12.apply(this, arguments);
+      }
+
+      return removeTeamMember;
+    }()
   }]);
   return ProductsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'generateClaimCode', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'generateClaimCode'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec23, _dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
 exports.default = ProductsController;
 /* eslint-enable */
\ No newline at end of file
diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index a1c5e118..61081a1e 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -94,12 +94,6 @@ var _WebhookDatabaseRepository = require('./repository/WebhookDatabaseRepository
 
 var _WebhookDatabaseRepository2 = _interopRequireDefault(_WebhookDatabaseRepository);
 
-var _DefaultLogger = require('./lib/DefaultLogger');
-
-var _logger = require('./lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
@@ -115,10 +109,6 @@ exports.default = function (container, newSettings) {
   // spark protocol container bindings
   (0, _sparkProtocol.defaultBindings)(container, newSettings);
 
-  // Bind Logger Elements, Function and Class
-  container.bindValue('LOGGING_CLASS', _DefaultLogger.DefaultLogger);
-  _logger2.default.initialize(container.constitute('LOGGING_CLASS'));
-
   // settings
   container.bindValue('DATABASE_PATH', _settings2.default.DB_CONFIG.PATH);
   container.bindValue('DEVICE_DIRECTORY', _settings2.default.DEVICE_DIRECTORY);
diff --git a/dist/exports.js b/dist/exports.js
index 2f77acf3..b2cbfdc8 100644
--- a/dist/exports.js
+++ b/dist/exports.js
@@ -5,10 +5,6 @@ Object.defineProperty(exports, "__esModule", {
 });
 exports.settings = exports.logger = exports.defaultBindings = exports.createApp = exports.MongoDb = undefined;
 
-var _logger = require('./lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _app = require('./app');
 
 var _app2 = _interopRequireDefault(_app);
@@ -25,10 +21,16 @@ var _MongoDb = require('./repository/MongoDb');
 
 var _MongoDb2 = _interopRequireDefault(_MongoDb);
 
+var _logger = require('./lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 exports.MongoDb = _MongoDb2.default;
 exports.createApp = _app2.default;
 exports.defaultBindings = _defaultBindings2.default;
-exports.logger = _logger2.default;
+exports.logger = logger;
 exports.settings = _settings2.default;
\ No newline at end of file
diff --git a/dist/lib/WebhookLogger.js b/dist/lib/WebhookLogger.js
index 0f4e6fb5..afdb9839 100644
--- a/dist/lib/WebhookLogger.js
+++ b/dist/lib/WebhookLogger.js
@@ -18,6 +18,8 @@ var _logger2 = _interopRequireDefault(_logger);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var WebhookLogger = function () {
   function WebhookLogger() {
     (0, _classCallCheck3.default)(this, WebhookLogger);
@@ -26,12 +28,12 @@ var WebhookLogger = function () {
   (0, _createClass3.default)(WebhookLogger, [{
     key: 'log',
     value: function log() {
-      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
-        args[_key] = arguments[_key];
+      for (var _len = arguments.length, argsarray = Array(_len), _key = 0; _key < _len; _key++) {
+        argsarray[_key] = arguments[_key];
       }
 
-      this._lastLog = args;
-      _logger2.default.log.apply(_logger2.default, args);
+      this._lastLog = argsarray;
+      logger.info({ args: argsarray }, 'WebHook');
     }
   }]);
   return WebhookLogger;
diff --git a/dist/lib/logger.js b/dist/lib/logger.js
index d3bbd626..2314e164 100644
--- a/dist/lib/logger.js
+++ b/dist/lib/logger.js
@@ -13,10 +13,20 @@ var _createClass2 = require('babel-runtime/helpers/createClass');
 
 var _createClass3 = _interopRequireDefault(_createClass2);
 
-var _DefaultLogger = require('./DefaultLogger');
+var _bunyan = require('bunyan');
+
+var _bunyan2 = _interopRequireDefault(_bunyan);
 
 var _types = require('../types');
 
+var _path = require('path');
+
+var _path2 = _interopRequireDefault(_path);
+
+var _settings = require('../settings');
+
+var _settings2 = _interopRequireDefault(_settings);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /**
@@ -46,41 +56,25 @@ var Logger = function () {
   }
 
   (0, _createClass3.default)(Logger, null, [{
-    key: 'error',
-    value: function error() {
-      var _Logger$_logger;
-
-      (_Logger$_logger = Logger._logger).error.apply(_Logger$_logger, arguments);
+    key: 'createLogger',
+    value: function createLogger(applicationName) {
+      return _bunyan2.default.createLogger({
+        level: _settings2.default.LOG_LEVEL,
+        name: applicationName,
+        serializers: _bunyan2.default.stdSerializers
+      });
     }
   }, {
-    key: 'info',
-    value: function info() {
-      var _Logger$_logger2;
-
-      (_Logger$_logger2 = Logger._logger).info.apply(_Logger$_logger2, arguments);
-    }
-  }, {
-    key: 'initialize',
-    value: function initialize(logger) {
-      Logger._logger = logger;
-    }
-  }, {
-    key: 'log',
-    value: function log() {
-      var _Logger$_logger3;
-
-      (_Logger$_logger3 = Logger._logger).log.apply(_Logger$_logger3, arguments);
-    }
-  }, {
-    key: 'warn',
-    value: function warn() {
-      var _Logger$_logger4;
-
-      (_Logger$_logger4 = Logger._logger).warn.apply(_Logger$_logger4, arguments);
+    key: 'createModuleLogger',
+    value: function createModuleLogger(applicationModule) {
+      return _bunyan2.default.createLogger({
+        level: _settings2.default.LOG_LEVEL,
+        name: _path2.default.basename(applicationModule.filename),
+        serializers: _bunyan2.default.stdSerializers
+      });
     }
   }]);
   return Logger;
 }();
 
-Logger._logger = _DefaultLogger.DefaultLogger;
 exports.default = Logger;
\ No newline at end of file
diff --git a/dist/main.js b/dist/main.js
index e18e8cb8..794056c0 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -48,25 +48,24 @@ var _defaultBindings = require('./defaultBindings');
 
 var _defaultBindings2 = _interopRequireDefault(_defaultBindings);
 
-var _logger = require('./lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _settings = require('./settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
 var _constitute = require('constitute');
 
+var _logger = require('./lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var NODE_PORT = process.env.NODE_PORT || _settings2.default.EXPRESS_SERVER_CONFIG.PORT;
 
 process.on('uncaughtException', function (exception) {
-  _logger2.default.error('uncaughtException', {
-    message: exception.message,
-    stack: exception.stack
-  }); // logging with MetaData
+  logger.error({ err: exception }, 'uncaughtException');
   process.exit(1); // exit with failure
 });
 
@@ -90,7 +89,7 @@ deviceServer.start();
 var app = (0, _app2.default)(container, _settings2.default);
 
 var onServerStartListen = function onServerStartListen() {
-  return _logger2.default.info('express server started on port ' + NODE_PORT);
+  logger.info({ port: NODE_PORT }, 'express server started, with events');
 };
 
 var _settings$EXPRESS_SER = _settings2.default.EXPRESS_SERVER_CONFIG,
@@ -101,6 +100,7 @@ var _settings$EXPRESS_SER = _settings2.default.EXPRESS_SERVER_CONFIG,
 
 
 if (useSSL) {
+  logger.debug({ cert: certificateFilePath, key: privateKeyFilePath }, 'Use SSL');
   var options = (0, _extends3.default)({
     cert: certificateFilePath && _fs2.default.readFileSync((0, _nullthrows2.default)(certificateFilePath)),
     key: privateKeyFilePath && _fs2.default.readFileSync((0, _nullthrows2.default)(privateKeyFilePath))
@@ -118,11 +118,12 @@ function (_ref) {
       nic = _ref2[1];
 
   return nic.filter(function (address) {
+    logger.debug({ found: address }, 'Network Interface');
     return address.family === 'IPv4' && address.address !== '127.0.0.1';
   }).map(function (address) {
     return address.address;
   });
 }));
-addresses.forEach(function (address) {
-  return _logger2.default.info('Your device server IP address is: ' + address);
+addresses.forEach(function (aAddress) {
+  return logger.info({ address: aAddress }, 'Server IP address found');
 });
\ No newline at end of file
diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js
index 5dc4e9c0..ee9adc18 100644
--- a/dist/managers/FirmwareCompilationManager.js
+++ b/dist/managers/FirmwareCompilationManager.js
@@ -36,10 +36,6 @@ var _fs = require('fs');
 
 var _fs2 = _interopRequireDefault(_fs);
 
-var _logger = require('../lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _path = require('path');
 
 var _path2 = _interopRequireDefault(_path);
@@ -60,8 +56,14 @@ var _settings = require('../settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var IS_COMPILATION_ENABLED = _fs2.default.existsSync(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY);
 
 var USER_APP_PATH = _path2.default.join(_settings2.default.FIRMWARE_REPOSITORY_DIRECTORY, 'user/applications');
@@ -163,7 +165,7 @@ FirmwareCompilationManager.compileSource = function () {
             errors = [];
 
             makeProcess.stderr.on('data', function (data) {
-              _logger2.default.error('' + data);
+              logger.error({ data: data }, 'Error from MakeProcess');
               errors.push('' + data);
             });
 
diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index ecc4bc8d..dce8aca3 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -30,16 +30,18 @@ var _HttpError = require('../lib/HttpError');
 
 var _HttpError2 = _interopRequireDefault(_HttpError);
 
-var _logger = require('../lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _settings = require('../settings');
 
 var _settings2 = _interopRequireDefault(_settings);
 
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var PermissionManager = function PermissionManager(deviceAttributeRepository, userRepository, webhookRepository, oauthServer) {
   var _this = this;
 
@@ -163,7 +165,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
             token = _context4.sent;
 
 
-            _logger2.default.info('New default admin user created with token: ' + token);
+            logger.info({ token: token }, 'New default admin user created');
             _context4.next = 12;
             break;
 
@@ -171,7 +173,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
             _context4.prev = 9;
             _context4.t0 = _context4['catch'](0);
 
-            _logger2.default.error('Error during default admin user creating: ' + _context4.t0);
+            logger.error({ err: _context4.t0 }, 'Error during default admin user creating');
 
           case 12:
           case 'end':
@@ -244,7 +246,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
               break;
             }
 
-            _logger2.default.info('Default admin accessToken: ' + ('' + defaultAdminUser.accessTokens[0].accessToken));
+            logger.info({ token: defaultAdminUser.accessTokens[0].accessToken }, 'Default Admin token');
             _context6.next = 9;
             break;
 
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index b3ed0f03..bc58f8be 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -40,10 +40,6 @@ var _HttpError = require('../lib/HttpError');
 
 var _HttpError2 = _interopRequireDefault(_HttpError);
 
-var _logger = require('../lib/logger');
-
-var _logger2 = _interopRequireDefault(_logger);
-
 var _nullthrows = require('nullthrows');
 
 var _nullthrows2 = _interopRequireDefault(_nullthrows);
@@ -60,8 +56,14 @@ var _throttle = require('lodash/throttle');
 
 var _throttle2 = _interopRequireDefault(_throttle);
 
+var _logger = require('../lib/logger');
+
+var _logger2 = _interopRequireDefault(_logger);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var parseEventData = function parseEventData(event) {
   try {
     if (event.data) {
@@ -69,6 +71,7 @@ var parseEventData = function parseEventData(event) {
     }
     return {};
   } catch (error) {
+    logger.warn({ err: error, evdata: event.data }, 'parseEventData failed');
     return {};
   }
 };
@@ -298,7 +301,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
 
         _this.runWebhookThrottled(webhook, event);
       } catch (error) {
-        _logger2.default.error('webhookError: ' + error);
+        logger.error({ err: error }, 'webhookError');
       }
     };
   };
@@ -372,7 +375,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
               _context6.prev = 25;
               _context6.t0 = _context6['catch'](0);
 
-              _logger2.default.error('webhookError: ' + _context6.t0);
+              logger.error({ err: _context6.t0 }, 'webhookError');
 
             case 28:
             case 'end':
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index ada7022f..0752cb44 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -40,14 +40,16 @@ var _BaseMongoDb2 = require('./BaseMongoDb');
 
 var _BaseMongoDb3 = _interopRequireDefault(_BaseMongoDb2);
 
+var _mongodb = require('mongodb');
+
 var _logger = require('../lib/logger');
 
 var _logger2 = _interopRequireDefault(_logger);
 
-var _mongodb = require('mongodb');
-
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var logger = _logger2.default.createModuleLogger(module);
+
 var DB_READY_EVENT = 'dbReady';
 
 var MongoDb = function (_BaseMongoDb) {
@@ -360,7 +362,7 @@ var _initialiseProps = function _initialiseProps() {
 
             case 4:
               return _context12.abrupt('return', callback(_this3._database.collection(collectionName)).catch(function (error) {
-                return _logger2.default.error(error);
+                return logger.error({ err: error }, 'Run for Collection');
               }));
 
             case 5:
@@ -391,15 +393,15 @@ var _initialiseProps = function _initialiseProps() {
 
 
               database.on('error', function (error) {
-                return _logger2.default.error('DB connection Error: ', error);
+                return logger.error({ err: error }, 'DB connection Error: ');
               });
 
               database.on('open', function () {
-                return _logger2.default.log('DB connected');
+                return logger.info('DB connected');
               });
 
               database.on('close', function (str) {
-                return _logger2.default.log('DB disconnected: ', str);
+                return logger.info({ info: str }, 'DB disconnected: ');
               });
 
               _this3._database = database;
diff --git a/dist/repository/NeDb.js b/dist/repository/NeDb.js
index 48473f00..3143c968 100644
--- a/dist/repository/NeDb.js
+++ b/dist/repository/NeDb.js
@@ -195,6 +195,7 @@ var NeDb = function (_BaseMongoDb) {
                             _ref7 = _context5.sent;
                             _ref8 = (0, _slicedToArray3.default)(_ref7, 2);
                             count = _ref8[0];
+                            // eslint-disable-line no-unused-vars
                             resultItem = _ref8[1];
                             return _context5.abrupt('return', _this.__translateResultItem(resultItem));
 
diff --git a/dist/settings.js b/dist/settings.js
index 9e5bc38a..055ab9c2 100644
--- a/dist/settings.js
+++ b/dist/settings.js
@@ -26,7 +26,7 @@ exports.default = {
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_ALGORITHM: 'aes-128-cbc',
-  LOG_REQUESTS: true,
+  LOG_LEVEL: process.env.LOG_LEVEL || 'info',
   LOGIN_ROUTE: '/oauth/token',
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,
diff --git a/dist/types.js b/dist/types.js
index a726efc4..ee618de0 100644
--- a/dist/types.js
+++ b/dist/types.js
@@ -1 +1,7 @@
-'use strict';
\ No newline at end of file
+'use strict';
+
+var _bunyan = require('bunyan');
+
+var _bunyan2 = _interopRequireDefault(_bunyan);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
\ No newline at end of file
diff --git a/package.json b/package.json
index 94bef55e..f7a23dc6 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,8 @@
     "basic-auth-parser": "0.0.2",
     "binary-version-reader": "^0.5.0",
     "body-parser": "^1.15.2",
+    "bunyan": "^1.8.10",
+    "bunyan-middleware": "^0.8.0",
     "chalk": "^1.1.3",
     "constitute": "^1.6.2",
     "ec-key": "0.0.2",
@@ -136,4 +138,4 @@
     "supertest": "^2.0.1",
     "uglifyjs": "^2.4.10"
   }
-}
\ No newline at end of file
+}
diff --git a/src/app.js b/src/app.js
index 2104a100..bd7c73c3 100644
--- a/src/app.js
+++ b/src/app.js
@@ -12,8 +12,10 @@ import type { Settings } from './types';
 
 import bodyParser from 'body-parser';
 import express from 'express';
-import morgan from 'morgan';
+import Logger from './lib/logger';
 import routeConfig from './RouteConfig';
+import bunyanMiddleware from 'bunyan-middleware';
+const logger = Logger.createModuleLogger(module);
 
 export default (
   container: Container,
@@ -37,22 +39,32 @@ export default (
       });
       return response.sendStatus(204);
     }
-    response.set({ 'Access-Control-Allow-Origin': '*' });
+    response.set({
+      'Access-Control-Allow-Origin': '*',
+    });
     return next();
   };
 
-  if (settings.LOG_REQUESTS) {
+  if (logger.debug()) {
     app.use(
-      morgan(
-        '[:date[iso]] :remote-addr - :remote-user ":method :url ' +
-          'HTTP/:http-version" :status :res[content-length] ":referrer" ' +
-          '":user-agent"',
-      ),
+      bunyanMiddleware({
+        headerName: 'X-Request-Id',
+        level: 'debug',
+        logger,
+        logName: 'req_id',
+        obscureHeaders: [],
+        propertyName: 'reqId',
+      }),
     );
+    logger.warn('Request logging enabled');
   }
 
   app.use(bodyParser.json());
-  app.use(bodyParser.urlencoded({ extended: true }));
+  app.use(
+    bodyParser.urlencoded({
+      extended: true,
+    }),
+  );
   app.use(setCORSHeaders);
 
   routeConfig(
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index 638684bf..ccc40b53 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -12,6 +12,8 @@ import allowUpload from '../decorators/allowUpload';
 import httpVerb from '../decorators/httpVerb';
 import route from '../decorators/route';
 import deviceToAPI from '../lib/deviceToAPI';
+import Logger from '../lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 type CompileConfig = {
   platform_id?: string,
@@ -79,6 +81,11 @@ class DevicesController extends Controller {
       );
     } catch (error) {
       // I wish we could return no devices found but meh :/
+      // at least we should issue a warning
+      logger.warn(
+        { err: error },
+        'get devices throws error, possibly no devices found?',
+      );
       return this.ok([]);
     }
   }
diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js
index d77cfdd8..611c41a5 100644
--- a/src/controllers/EventsController.js
+++ b/src/controllers/EventsController.js
@@ -8,8 +8,9 @@ import anonymous from '../decorators/anonymous';
 import route from '../decorators/route';
 import httpVerb from '../decorators/httpVerb';
 import serverSentEvents from '../decorators/serverSentEvents';
-import logger from '../lib/logger';
 import eventToApi from '../lib/eventToApi';
+import Logger from '../lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 class EventsController extends Controller {
   _eventManager: EventManager;
@@ -39,7 +40,7 @@ class EventsController extends Controller {
       this.response.write(`event: ${event.name}\n`);
       this.response.write(`data: ${JSON.stringify(eventToApi(event))}\n\n`);
     } catch (error) {
-      logger.error(`pipeEvents - write error: ${error}`);
+      logger.error({ err: error }, 'pipeEvents - write error');
       throw error;
     }
   }
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index b210d322..a70f9a69 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -25,8 +25,6 @@ import DeviceAttributeDatabaseRepository from './repository/DeviceAttributeDatab
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
-import { DefaultLogger } from './lib/DefaultLogger';
-import logger from './lib/logger';
 import settings from './settings';
 
 export default (container: Container, newSettings: Settings) => {
@@ -38,10 +36,6 @@ export default (container: Container, newSettings: Settings) => {
   // spark protocol container bindings
   defaultBindings(container, newSettings);
 
-  // Bind Logger Elements, Function and Class
-  container.bindValue('LOGGING_CLASS', DefaultLogger);
-  logger.initialize(container.constitute('LOGGING_CLASS'));
-
   // settings
   container.bindValue('DATABASE_PATH', settings.DB_CONFIG.PATH);
   container.bindValue('DEVICE_DIRECTORY', settings.DEVICE_DIRECTORY);
diff --git a/src/exports.js b/src/exports.js
index fb04d2d4..61000e81 100644
--- a/src/exports.js
+++ b/src/exports.js
@@ -1,9 +1,10 @@
 // @flow
 
-import logger from './lib/logger';
 import createApp from './app';
 import defaultBindings from './defaultBindings';
 import settings from './settings';
 import MongoDb from './repository/MongoDb';
+import Logger from './lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 export { MongoDb, createApp, defaultBindings, logger, settings };
diff --git a/src/lib/DefaultLogger.js b/src/lib/DefaultLogger.js
deleted file mode 100644
index 5224f8c7..00000000
--- a/src/lib/DefaultLogger.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
-*    Copyright (C) 2013-2014 Spark Labs, Inc. All rights reserved. -  https://www.spark.io/
-*
-*    This program is free software: you can redistribute it and/or modify
-*    it under the terms of the GNU Affero General Public License, version 3,
-*    as published by the Free Software Foundation.
-*
-*    This program is distributed in the hope that it will be useful,
-*    but WITHOUT ANY WARRANTY; without even the implied warranty of
-*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-*    GNU Affero General Public License for more details.
-*
-*    You should have received a copy of the GNU Affero General Public License
-*    along with this program.  If not, see .
-*
-*    You can download the source here: https://github.com/spark/spark-server
-*
-* @flow
-*
-*/
-
-import chalk from 'chalk';
-import settings from '../settings';
-import { ILogger } from '../types';
-
-function isObject(obj: any): boolean {
-  return obj === Object(obj);
-}
-
-function _transform(...params: Array): Array {
-  return params.map((param: any): string => {
-    if (!isObject(param)) {
-      return param;
-    }
-
-    return JSON.stringify(param);
-  });
-}
-
-function getDate(): string {
-  return new Date().toISOString();
-}
-
-export class DefaultLogger implements ILogger {
-  static log(...params: Array) {
-    if (settings.SHOW_VERBOSE_DEVICE_LOGS) {
-      DefaultLogger._log(`[${getDate()}]`, _transform(...params));
-    }
-  }
-
-  static info(...params: Array) {
-    DefaultLogger._log(`[${getDate()}]`, chalk.cyan(_transform(...params)));
-  }
-
-  static warn(...params: Array) {
-    DefaultLogger._log(`[${getDate()}]`, chalk.yellow(_transform(...params)));
-  }
-
-  static error(...params: Array) {
-    DefaultLogger._log(`[${getDate()}]`, chalk.red(_transform(...params)));
-  }
-  static _log(...params: Array) {
-    console.log(...params);
-  }
-}
diff --git a/src/lib/WebhookLogger.js b/src/lib/WebhookLogger.js
index 79ef39a0..76e66f46 100644
--- a/src/lib/WebhookLogger.js
+++ b/src/lib/WebhookLogger.js
@@ -1,15 +1,15 @@
 // @flow
 
 import type { IWebhookLogger } from '../types';
-
-import logger from './logger';
+import Logger from './logger';
+const logger = Logger.createModuleLogger(module);
 
 class WebhookLogger implements IWebhookLogger {
   _lastLog: Array;
 
-  log(...args: Array) {
-    this._lastLog = args;
-    logger.log(...args);
+  log(...argsarray: Array) {
+    this._lastLog = argsarray;
+    logger.info({ args: argsarray }, 'WebHook');
   }
 }
 
diff --git a/src/lib/logger.js b/src/lib/logger.js
index cd59540d..4f4a2dbd 100644
--- a/src/lib/logger.js
+++ b/src/lib/logger.js
@@ -19,28 +19,24 @@
 *
 */
 
-import { DefaultLogger } from './DefaultLogger';
-import { ILogger } from '../types';
+import bunyan from 'bunyan';
+import { ILoggerCreate } from '../types';
+import path from 'path';
+import settings from '../settings';
 
-export default class Logger {
-  static _logger: ILogger = DefaultLogger;
-
-  static error(...params: Array) {
-    Logger._logger.error(...params);
-  }
-
-  static info(...params: Array) {
-    Logger._logger.info(...params);
-  }
-
-  static initialize(logger: ILogger) {
-    Logger._logger = logger;
-  }
-
-  static log(...params: Array) {
-    Logger._logger.log(...params);
+export default class Logger implements ILoggerCreate {
+  static createLogger(applicationName: string): bunyan.Logger {
+    return bunyan.createLogger({
+      level: settings.LOG_LEVEL,
+      name: applicationName,
+      serializers: bunyan.stdSerializers,
+    });
   }
-  static warn(...params: Array) {
-    Logger._logger.warn(...params);
+  static createModuleLogger(applicationModule: any): bunyan.Logger {
+    return bunyan.createLogger({
+      level: settings.LOG_LEVEL,
+      name: path.basename(applicationModule.filename),
+      serializers: bunyan.stdSerializers,
+    });
   }
 }
diff --git a/src/main.js b/src/main.js
index 20ab6622..f2e88f94 100644
--- a/src/main.js
+++ b/src/main.js
@@ -8,17 +8,15 @@ import http from 'http';
 import https from 'https';
 import os from 'os';
 import defaultBindings from './defaultBindings';
-import logger from './lib/logger';
 import settings from './settings';
 import { Container } from 'constitute';
+import Logger from './lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 const NODE_PORT = process.env.NODE_PORT || settings.EXPRESS_SERVER_CONFIG.PORT;
 
 process.on('uncaughtException', (exception: Error) => {
-  logger.error('uncaughtException', {
-    message: exception.message,
-    stack: exception.stack,
-  }); // logging with MetaData
+  logger.error({ err: exception }, 'uncaughtException');
   process.exit(1); // exit with failure
 });
 
@@ -41,8 +39,9 @@ deviceServer.start();
 
 const app = createApp(container, settings);
 
-const onServerStartListen = (): void =>
-  logger.info(`express server started on port ${NODE_PORT}`);
+const onServerStartListen = () => {
+  logger.info({ port: NODE_PORT }, 'express server started, with events');
+};
 
 const {
   SSL_PRIVATE_KEY_FILEPATH: privateKeyFilePath,
@@ -52,6 +51,10 @@ const {
 } = settings.EXPRESS_SERVER_CONFIG;
 
 if (useSSL) {
+  logger.debug(
+    { cert: certificateFilePath, key: privateKeyFilePath },
+    'Use SSL',
+  );
   const options = {
     cert:
       certificateFilePath && fs.readFileSync(nulltrhows(certificateFilePath)),
@@ -70,13 +73,13 @@ const addresses = arrayFlatten(
     // eslint-disable-next-line no-unused-vars
     ([name, nic]: [string, mixed]): Array =>
       (nic: any)
-        .filter(
-          (address: Object): boolean =>
-            address.family === 'IPv4' && address.address !== '127.0.0.1',
-        )
+        .filter((address: Object): boolean => {
+          logger.debug({ found: address }, 'Network Interface');
+          return address.family === 'IPv4' && address.address !== '127.0.0.1';
+        })
         .map((address: Object): boolean => address.address),
   ),
 );
-addresses.forEach((address: string): void =>
-  logger.info(`Your device server IP address is: ${address}`),
+addresses.forEach((aAddress: string): void =>
+  logger.info({ address: aAddress }, 'Server IP address found'),
 );
diff --git a/src/managers/FirmwareCompilationManager.js b/src/managers/FirmwareCompilationManager.js
index 87b98ffa..837fd3d7 100644
--- a/src/managers/FirmwareCompilationManager.js
+++ b/src/managers/FirmwareCompilationManager.js
@@ -4,13 +4,14 @@ import type { File } from 'express';
 
 import crypto from 'crypto';
 import fs from 'fs';
-import logger from '../lib/logger';
 import path from 'path';
 import mkdirp from 'mkdirp';
 import rmfr from 'rmfr';
 import { spawn } from 'child_process';
 import { knownPlatforms } from 'spark-protocol';
 import settings from '../settings';
+import Logger from '../lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 const IS_COMPILATION_ENABLED = fs.existsSync(
   settings.FIRMWARE_REPOSITORY_DIRECTORY,
@@ -117,7 +118,7 @@ class FirmwareCompilationManager {
 
     const errors = [];
     makeProcess.stderr.on('data', (data: string) => {
-      logger.error(`${data}`);
+      logger.error({ data }, 'Error from MakeProcess');
       errors.push(`${data}`);
     });
 
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index 036ca3d3..48f3cabb 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -10,8 +10,9 @@ import type {
 import nullthrows from 'nullthrows';
 import { Request, Response } from 'oauth2-server';
 import HttpError from '../lib/HttpError';
-import logger from '../lib/logger';
 import settings from '../settings';
+import Logger from '../lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 class PermissionManager {
   _userRepository: IUserRepository;
@@ -79,9 +80,9 @@ class PermissionManager {
 
       const token = await this._generateAdminToken();
 
-      logger.info(`New default admin user created with token: ${token}`);
+      logger.info({ token }, 'New default admin user created');
     } catch (error) {
-      logger.error(`Error during default admin user creating: ${error}`);
+      logger.error({ err: error }, 'Error during default admin user creating');
     }
   };
 
@@ -126,8 +127,8 @@ class PermissionManager {
     );
     if (defaultAdminUser) {
       logger.info(
-        'Default admin accessToken: ' +
-          `${defaultAdminUser.accessTokens[0].accessToken}`,
+        { token: defaultAdminUser.accessTokens[0].accessToken },
+        'Default Admin token',
       );
     } else {
       await this._createDefaultAdminUser();
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 7393b9f8..15c94d5f 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -14,11 +14,12 @@ import type {
 
 import hogan from 'hogan.js';
 import HttpError from '../lib/HttpError';
-import logger from '../lib/logger';
 import nullthrows from 'nullthrows';
 import request from 'request';
 import settings from '../settings';
 import throttle from 'lodash/throttle';
+import Logger from '../lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 const parseEventData = (event: Event): Object => {
   try {
@@ -27,6 +28,7 @@ const parseEventData = (event: Event): Object => {
     }
     return {};
   } catch (error) {
+    logger.warn({ err: error, evdata: event.data }, 'parseEventData failed');
     return {};
   }
 };
@@ -176,7 +178,7 @@ class WebhookManager {
 
       this.runWebhookThrottled(webhook, event);
     } catch (error) {
-      logger.error(`webhookError: ${error}`);
+      logger.error({ err: error }, 'webhookError');
     }
   };
 
@@ -288,7 +290,7 @@ class WebhookManager {
         responseEventData,
       );
     } catch (error) {
-      logger.error(`webhookError: ${error}`);
+      logger.error({ err: error }, 'webhookError');
     }
   };
 
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index 4c3cee17..afc8207a 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -4,8 +4,9 @@ import type { IBaseDatabase } from '../types';
 
 import EventEmitter from 'events';
 import BaseMongoDb from './BaseMongoDb';
-import Logger from '../lib/logger';
 import { MongoClient } from 'mongodb';
+import Logger from '../lib/logger';
+const logger = Logger.createModuleLogger(module);
 
 const DB_READY_EVENT = 'dbReady';
 
@@ -87,20 +88,22 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
     }
     return callback(
       this._database.collection(collectionName),
-    ).catch((error: Error): void => Logger.error(error));
+    ).catch((error: Error): void =>
+      logger.error({ err: error }, 'Run for Collection'),
+    );
   };
 
   _init = async (url: string, options: Object): Promise => {
     const database = await MongoClient.connect(url, options);
 
     database.on('error', (error: Error): void =>
-      Logger.error('DB connection Error: ', error),
+      logger.error({ err: error }, 'DB connection Error: '),
     );
 
-    database.on('open', (): void => Logger.log('DB connected'));
+    database.on('open', (): void => logger.info('DB connected'));
 
     database.on('close', (str: string): void =>
-      Logger.log('DB disconnected: ', str),
+      logger.info({ info: str }, 'DB disconnected: '),
     );
 
     this._database = database;
diff --git a/src/settings.js b/src/settings.js
index 2c1f08ea..c69f2772 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -37,7 +37,7 @@ export default {
   ACCESS_TOKEN_LIFETIME: 7776000, // 90 days,
   API_TIMEOUT: 30000, // Timeout for API requests.
   CRYPTO_ALGORITHM: 'aes-128-cbc',
-  LOG_REQUESTS: true,
+  LOG_LEVEL: (process.env.LOG_LEVEL: any) || 'info',
   LOGIN_ROUTE: '/oauth/token',
   EXPRESS_SERVER_CONFIG: {
     PORT: 8080,
diff --git a/src/types.js b/src/types.js
index 5102b06a..ef16c6d4 100644
--- a/src/types.js
+++ b/src/types.js
@@ -2,6 +2,7 @@
 /* eslint-disable */
 
 import type { File } from 'express';
+import bunyan from 'bunyan';
 
 export type Webhook = {
   auth?: { password: string, username: string },
@@ -154,7 +155,7 @@ export type Settings = {
   },
   FIRMWARE_DIRECTORY: string,
   FIRMWARE_REPOSITORY_DIRECTORY: string,
-  LOG_REQUESTS: boolean,
+  LOG_LEVEL: 'debug' | 'error' | 'fatal' | 'info' | 'warn' | 'trace',
   LOGIN_ROUTE: string,
   SERVER_KEY_FILENAME: string,
   SERVER_KEYS_DIRECTORY: string,
@@ -232,9 +233,7 @@ export interface IBaseDatabase {
   remove(collectionName: string, query: Object): Promise<*>,
 }
 
-export interface ILogger {
-  static error(params: Array): void,
-  static info(params: Array): void,
-  static log(params: Array): void,
-  static warn(params: Array): void,
+export interface ILoggerCreate {
+  static createLogger(applicationName: string): bunyan.Logger,
+  static createModuleLogger(applicationModule: any): bunyan.Logger,
 }

From bd02b96a2758340dbdd278a0fbbdc918c94f5600 Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Tue, 27 Jun 2017 08:56:19 +0200
Subject: [PATCH 452/504] added last NIT from last PR

---
 src/main.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main.js b/src/main.js
index f2e88f94..58d3e5e1 100644
--- a/src/main.js
+++ b/src/main.js
@@ -80,6 +80,6 @@ const addresses = arrayFlatten(
         .map((address: Object): boolean => address.address),
   ),
 );
-addresses.forEach((aAddress: string): void =>
-  logger.info({ address: aAddress }, 'Server IP address found'),
+addresses.forEach((address: string): void =>
+  logger.info({ address }, 'Server IP address found'),
 );

From 4c2c89cbd9fa7447eb28b059151de476f025f689 Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Tue, 27 Jun 2017 16:26:48 +0200
Subject: [PATCH 453/504] Added Changes from jlkalberer

---
 examples/eventProvider.js | 2 --
 src/main.js               | 8 ++++----
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/examples/eventProvider.js b/examples/eventProvider.js
index 497368a2..287b9e54 100644
--- a/examples/eventProvider.js
+++ b/examples/eventProvider.js
@@ -35,12 +35,10 @@ defaultBindings(container, settings);
 const deviceServer = container.constitute('DeviceServer');
 deviceServer.start();
 
-/*
 const evProvider = container.constitute('EVENT_PROVIDER');
 evProvider.onNewEvent((event: Event) => {
   logger.info('Event onNewEvent',event);
 });
-*/
 
 
 
diff --git a/src/main.js b/src/main.js
index 58d3e5e1..8044733b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -73,10 +73,10 @@ const addresses = arrayFlatten(
     // eslint-disable-next-line no-unused-vars
     ([name, nic]: [string, mixed]): Array =>
       (nic: any)
-        .filter((address: Object): boolean => {
-          logger.debug({ found: address }, 'Network Interface');
-          return address.family === 'IPv4' && address.address !== '127.0.0.1';
-        })
+       .filter((address: Object): boolean =>
+          address.family === 'IPv4' &&
+          address.address !== '127.0.0.1',
+        )
         .map((address: Object): boolean => address.address),
   ),
 );

From 1bcd2a2115fdb31471b63df1c3b33437eb97f66a Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Tue, 27 Jun 2017 21:37:22 +0200
Subject: [PATCH 454/504] Added Changes AntonPuko

---
 dist/lib/WebhookLogger.js       | 12 ++++++++----
 dist/main.js                    |  5 ++---
 dist/managers/WebhookManager.js |  2 +-
 examples/eventProvider.js       | 12 +++---------
 src/lib/WebhookLogger.js        |  6 +++---
 src/managers/WebhookManager.js  |  2 +-
 6 files changed, 18 insertions(+), 21 deletions(-)

diff --git a/dist/lib/WebhookLogger.js b/dist/lib/WebhookLogger.js
index afdb9839..780b8658 100644
--- a/dist/lib/WebhookLogger.js
+++ b/dist/lib/WebhookLogger.js
@@ -4,6 +4,10 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
@@ -28,12 +32,12 @@ var WebhookLogger = function () {
   (0, _createClass3.default)(WebhookLogger, [{
     key: 'log',
     value: function log() {
-      for (var _len = arguments.length, argsarray = Array(_len), _key = 0; _key < _len; _key++) {
-        argsarray[_key] = arguments[_key];
+      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+        args[_key] = arguments[_key];
       }
 
-      this._lastLog = argsarray;
-      logger.info({ args: argsarray }, 'WebHook');
+      this._lastLog = args;
+      logger.info((0, _extends3.default)({}, args), 'WebHookLogger called');
     }
   }]);
   return WebhookLogger;
diff --git a/dist/main.js b/dist/main.js
index 794056c0..38d02615 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -118,12 +118,11 @@ function (_ref) {
       nic = _ref2[1];
 
   return nic.filter(function (address) {
-    logger.debug({ found: address }, 'Network Interface');
     return address.family === 'IPv4' && address.address !== '127.0.0.1';
   }).map(function (address) {
     return address.address;
   });
 }));
-addresses.forEach(function (aAddress) {
-  return logger.info({ address: aAddress }, 'Server IP address found');
+addresses.forEach(function (address) {
+  return logger.info({ address: address }, 'Server IP address found');
 });
\ No newline at end of file
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index bc58f8be..4a15dcd1 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -71,7 +71,7 @@ var parseEventData = function parseEventData(event) {
     }
     return {};
   } catch (error) {
-    logger.warn({ err: error, evdata: event.data }, 'parseEventData failed');
+    logger.warn({ err: error, eventData: event.data }, 'parseEventData failed');
     return {};
   }
 };
diff --git a/examples/eventProvider.js b/examples/eventProvider.js
index 287b9e54..8301405a 100644
--- a/examples/eventProvider.js
+++ b/examples/eventProvider.js
@@ -35,13 +35,7 @@ defaultBindings(container, settings);
 const deviceServer = container.constitute('DeviceServer');
 deviceServer.start();
 
-const evProvider = container.constitute('EVENT_PROVIDER');
-evProvider.onNewEvent((event: Event) => {
-  logger.info('Event onNewEvent',event);
-});
-
-
-
-deviceServer._eventPublisher.subscribe('*', (ev: any) => {
-  logger.info({ data: ev.data, event: ev.name, name: ev.deviceID, event: ev }, 'Incomming Event');
+const eventProvider = container.constitute('EVENT_PROVIDER');
+eventProvider.onNewEvent((event: Event) => {
+  logger.info('Event onNewEvent', event);
 });
diff --git a/src/lib/WebhookLogger.js b/src/lib/WebhookLogger.js
index 76e66f46..c6dac17c 100644
--- a/src/lib/WebhookLogger.js
+++ b/src/lib/WebhookLogger.js
@@ -7,9 +7,9 @@ const logger = Logger.createModuleLogger(module);
 class WebhookLogger implements IWebhookLogger {
   _lastLog: Array;
 
-  log(...argsarray: Array) {
-    this._lastLog = argsarray;
-    logger.info({ args: argsarray }, 'WebHook');
+  log(...args: Array) {
+    this._lastLog = args;
+    logger.info({ ...args }, 'WebHookLogger called');
   }
 }
 
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 15c94d5f..1acc2fb6 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -28,7 +28,7 @@ const parseEventData = (event: Event): Object => {
     }
     return {};
   } catch (error) {
-    logger.warn({ err: error, evdata: event.data }, 'parseEventData failed');
+    logger.warn({ err: error, eventData: event.data }, 'parseEventData failed');
     return {};
   }
 };

From ad928979516249e7576cfaeee5ab806f18d6613e Mon Sep 17 00:00:00 2001
From: Andreas 
Date: Tue, 27 Jun 2017 21:40:22 +0200
Subject: [PATCH 455/504] Added WebHookLogger Log Line

---
 src/lib/WebhookLogger.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lib/WebhookLogger.js b/src/lib/WebhookLogger.js
index c6dac17c..677eaf74 100644
--- a/src/lib/WebhookLogger.js
+++ b/src/lib/WebhookLogger.js
@@ -9,7 +9,7 @@ class WebhookLogger implements IWebhookLogger {
 
   log(...args: Array) {
     this._lastLog = args;
-    logger.info({ ...args }, 'WebHookLogger called');
+    logger.info({ ...args }, 'webhook');
   }
 }
 

From 9d0a69d37a0b2a733bd8bf54f7d90a3450595abc Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 28 Jun 2017 16:01:27 +0200
Subject: [PATCH 456/504] remove gitAdd from npm scripts, its lint-staged own
 command

---
 package.json | 1 -
 1 file changed, 1 deletion(-)

diff --git a/package.json b/package.json
index 5841a412..455fb6f5 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,6 @@
     "build": "babel ./src --out-dir ./dist --copy-files",
     "build:clean": "rimraf ./build",
     "build:watch": "babel ./src --out-dir ./dist --watch",
-    "git-add": "git add",
     "lint": "eslint --fix --max-warnings 0 -- .",
     "lint-staged": "lint-staged",
     "migrate-files-to-mongo": "babel-node ./src/scripts/migrateFilesToDatabase mongo",

From b65a37bbf601f00a631c9615e7095a5ba2332bf9 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 30 Jun 2017 14:19:11 +0200
Subject: [PATCH 457/504] update package-lock.json, closes
 https://github.com/Brewskey/spark-server/issues/229

---
 package-lock.json | 164 +++++++++++++++++++++++++++++++++++-----------
 1 file changed, 127 insertions(+), 37 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 02d3f319..c63e2188 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3,6 +3,36 @@
   "version": "0.1.1",
   "lockfileVersion": 1,
   "dependencies": {
+    "@types/bunyan": {
+      "version": "0.0.36",
+      "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-0.0.36.tgz",
+      "integrity": "sha1-/dhlxY6OqvCtQBzQMtyGH1xXNCo="
+    },
+    "@types/express": {
+      "version": "4.0.36",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz",
+      "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A=="
+    },
+    "@types/express-serve-static-core": {
+      "version": "4.0.48",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz",
+      "integrity": "sha512-+W+fHO/hUI6JX36H8FlgdMHU3Dk4a/Fn08fW5qdd7MjPP/wJlzq9fkCrgaH0gES8vohVeqwefHwPa4ylVKyYIg=="
+    },
+    "@types/mime": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.1.tgz",
+      "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A=="
+    },
+    "@types/node": {
+      "version": "8.0.6",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.6.tgz",
+      "integrity": "sha512-WuMAU8y6bw1I7MKOEtkuoSUsxHhVYkdv4nuqq3sed+Yx2JFFlCj4EoZweu4TkcrKAIuPXAjhGa5ZcROUJbj0AA=="
+    },
+    "@types/serve-static": {
+      "version": "1.7.31",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz",
+      "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho="
+    },
     "abbrev": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
@@ -185,9 +215,9 @@
       "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI="
     },
     "async": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/async/-/async-2.4.1.tgz",
-      "integrity": "sha1-YqVrJ5yYoR0JhwlqAcw+6463u9c="
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
+      "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw=="
     },
     "async-each": {
       "version": "1.0.1",
@@ -871,6 +901,16 @@
       "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
       "dev": true
     },
+    "bunyan": {
+      "version": "1.8.10",
+      "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.10.tgz",
+      "integrity": "sha1-IB/t0mxwgLYy9BYHL1OpC5pSmBw="
+    },
+    "bunyan-middleware": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/bunyan-middleware/-/bunyan-middleware-0.8.0.tgz",
+      "integrity": "sha1-K6vEmGtHCsfq7303POAkjOqbXPA="
+    },
     "busboy": {
       "version": "0.2.14",
       "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
@@ -1086,9 +1126,9 @@
       "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
     },
     "commander": {
-      "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
-      "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q="
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz",
+      "integrity": "sha512-q/r9trjmuikWDRJNTBHAVnWhuU6w+z80KgBq7j9YDclik5E7X4xi0KnlZBNFA1zOQ+SH/vHMWd2mC9QTOz7GpA=="
     },
     "common-path-prefix": {
       "version": "1.0.0",
@@ -1382,6 +1422,12 @@
       "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
       "dev": true
     },
+    "dtrace-provider": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.3.tgz",
+      "integrity": "sha1-uhv8ZJMoXM/PxqtpzVxh10wqQ78=",
+      "optional": true
+    },
     "duplexer": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
@@ -1544,9 +1590,9 @@
       "dev": true
     },
     "eslint-config-prettier": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.2.0.tgz",
-      "integrity": "sha1-ykdmOFJ4mnXBD+umc+gCzB7/CF8=",
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.3.0.tgz",
+      "integrity": "sha1-t1seq+oMi5ezRANkfuJds0m52KA=",
       "dev": true,
       "dependencies": {
         "get-stdin": {
@@ -1558,9 +1604,9 @@
       }
     },
     "eslint-import-resolver-node": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz",
-      "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=",
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz",
+      "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==",
       "dev": true
     },
     "eslint-module-utils": {
@@ -1570,15 +1616,15 @@
       "dev": true
     },
     "eslint-plugin-flowtype": {
-      "version": "2.34.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.34.0.tgz",
-      "integrity": "sha1-uYdfMUZS5QgWI8nSsYo0a7t1nAk=",
+      "version": "2.34.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.34.1.tgz",
+      "integrity": "sha512-xwXpTW7Xv+wfuQdfPILmFl9HWBdWbDjE1aZWWQ4EgCpQtMzymEkDQfyD1ME0VA8C0HTXV7cufypQRvLi+Hk/og==",
       "dev": true
     },
     "eslint-plugin-import": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.5.0.tgz",
-      "integrity": "sha512-JoGslQQHFveBnS3oZGKtinynThbTAo2QEpY5PQaD2VUGxhjB6us1wGD9PbwYLnevBdaEbUUfjtEAWmsEu3CtrQ==",
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.6.1.tgz",
+      "integrity": "sha512-aAMb32eHCQaQmgdb1MOG1hfu/rPiNgGur2IF71VJeDfTXdLpPiKALKWlzxMdcxQOZZ2CmYVKabAxCvjACxH1uQ==",
       "dev": true,
       "dependencies": {
         "doctrine": {
@@ -2639,9 +2685,9 @@
       "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg="
     },
     "hosted-git-info": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz",
-      "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=",
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
+      "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==",
       "dev": true
     },
     "http-errors": {
@@ -2971,9 +3017,9 @@
       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
     },
     "js-tokens": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
-      "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc="
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+      "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
     },
     "js-yaml": {
       "version": "3.8.4",
@@ -3489,6 +3535,26 @@
       "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
       "dev": true
     },
+    "mv": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
+      "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
+      "optional": true,
+      "dependencies": {
+        "glob": {
+          "version": "6.0.4",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
+          "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+          "optional": true
+        },
+        "rimraf": {
+          "version": "2.4.5",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
+          "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
+          "optional": true
+        }
+      }
+    },
     "nan": {
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
@@ -3501,6 +3567,12 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
+    "ncp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
+      "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
+      "optional": true
+    },
     "nedb-core": {
       "version": "3.0.6",
       "resolved": "https://registry.npmjs.org/nedb-core/-/nedb-core-3.0.6.tgz",
@@ -3615,9 +3687,9 @@
       "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4="
     },
     "normalize-package-data": {
-      "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz",
-      "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+      "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
       "dev": true
     },
     "normalize-path": {
@@ -3986,9 +4058,9 @@
       "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
     },
     "prettier": {
-      "version": "1.4.4",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.4.4.tgz",
-      "integrity": "sha512-GuuPazIvjW1DG26yLQgO+nagmRF/h9M4RaCtZWqu/eFW7csdZkQEwPJUeXX10d+LzmCnR9DuIZndqIOn3p2YoA==",
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.5.2.tgz",
+      "integrity": "sha512-f55mvineQ5yc36cLX4n4RWP6JH6MLcfi5f9MVsjpfBs4MVSG2GYT4v6cukzmvkIOvmNOdCZfDSMY3hQcMcDQbQ==",
       "dev": true
     },
     "pretty-ms": {
@@ -4127,9 +4199,9 @@
       "dev": true
     },
     "readable-stream": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.2.tgz",
-      "integrity": "sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00="
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
+      "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ=="
     },
     "readdirp": {
       "version": "2.1.0",
@@ -4351,6 +4423,12 @@
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
       "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
     },
+    "safe-json-stringify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz",
+      "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=",
+      "optional": true
+    },
     "samsam": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz",
@@ -4469,7 +4547,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#b3f7fc088417384cf16f93c1025523b6d8008f7d"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#2d8d38120ce1862730cb36a91fbe35f183b8ed43"
     },
     "spawn-sync": {
       "version": "1.0.15",
@@ -4670,6 +4748,12 @@
       "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
       "dev": true,
       "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
         "is-fullwidth-code-point": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
@@ -4677,9 +4761,15 @@
           "dev": true
         },
         "string-width": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz",
-          "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=",
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz",
+          "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
           "dev": true
         }
       }

From d7a083491a42d1d76c281ffd46b899469cf51a37 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 1 Jul 2017 00:14:08 +0200
Subject: [PATCH 458/504] remove wrong logger.warn in webhookManager

---
 dist/lib/WebhookLogger.js       |  2 +-
 dist/managers/WebhookManager.js |  1 -
 src/managers/WebhookManager.js  | 15 ++++++++-------
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/dist/lib/WebhookLogger.js b/dist/lib/WebhookLogger.js
index 780b8658..6adfad1c 100644
--- a/dist/lib/WebhookLogger.js
+++ b/dist/lib/WebhookLogger.js
@@ -37,7 +37,7 @@ var WebhookLogger = function () {
       }
 
       this._lastLog = args;
-      logger.info((0, _extends3.default)({}, args), 'WebHookLogger called');
+      logger.info((0, _extends3.default)({}, args), 'webhook');
     }
   }]);
   return WebhookLogger;
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 4a15dcd1..9c40cef7 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -71,7 +71,6 @@ var parseEventData = function parseEventData(event) {
     }
     return {};
   } catch (error) {
-    logger.warn({ err: error, eventData: event.data }, 'parseEventData failed');
     return {};
   }
 };
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 1acc2fb6..4366211b 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -28,7 +28,6 @@ const parseEventData = (event: Event): Object => {
     }
     return {};
   } catch (error) {
-    logger.warn({ err: error, eventData: event.data }, 'parseEventData failed');
     return {};
   }
 };
@@ -229,12 +228,14 @@ class WebhookManager {
       const isJsonRequest = !!requestJson || !requestFormData;
       const requestOptions = {
         auth: (requestAuth: any),
-        body: isJsonRequest && requestJson
-          ? this._getRequestData(requestJson, event, webhook.noDefaults)
-          : undefined,
-        form: !isJsonRequest && requestFormData
-          ? this._getRequestData(requestFormData, event, webhook.noDefaults)
-          : undefined,
+        body:
+          isJsonRequest && requestJson
+            ? this._getRequestData(requestJson, event, webhook.noDefaults)
+            : undefined,
+        form:
+          !isJsonRequest && requestFormData
+            ? this._getRequestData(requestFormData, event, webhook.noDefaults)
+            : undefined,
         headers: requestHeaders,
         json: true,
         method: validateRequestType(nullthrows(requestType)),

From 92db5c4f517867b708ff6d75a6daa72fa904a89e Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 1 Jul 2017 00:14:08 +0200
Subject: [PATCH 459/504] remove wrong logger.warn in webhookManager

---
 dist/lib/WebhookLogger.js       |  2 +-
 dist/managers/WebhookManager.js |  1 -
 src/managers/WebhookManager.js  | 15 ++++++++-------
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/dist/lib/WebhookLogger.js b/dist/lib/WebhookLogger.js
index 780b8658..6adfad1c 100644
--- a/dist/lib/WebhookLogger.js
+++ b/dist/lib/WebhookLogger.js
@@ -37,7 +37,7 @@ var WebhookLogger = function () {
       }
 
       this._lastLog = args;
-      logger.info((0, _extends3.default)({}, args), 'WebHookLogger called');
+      logger.info((0, _extends3.default)({}, args), 'webhook');
     }
   }]);
   return WebhookLogger;
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 4a15dcd1..9c40cef7 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -71,7 +71,6 @@ var parseEventData = function parseEventData(event) {
     }
     return {};
   } catch (error) {
-    logger.warn({ err: error, eventData: event.data }, 'parseEventData failed');
     return {};
   }
 };
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 1acc2fb6..4366211b 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -28,7 +28,6 @@ const parseEventData = (event: Event): Object => {
     }
     return {};
   } catch (error) {
-    logger.warn({ err: error, eventData: event.data }, 'parseEventData failed');
     return {};
   }
 };
@@ -229,12 +228,14 @@ class WebhookManager {
       const isJsonRequest = !!requestJson || !requestFormData;
       const requestOptions = {
         auth: (requestAuth: any),
-        body: isJsonRequest && requestJson
-          ? this._getRequestData(requestJson, event, webhook.noDefaults)
-          : undefined,
-        form: !isJsonRequest && requestFormData
-          ? this._getRequestData(requestFormData, event, webhook.noDefaults)
-          : undefined,
+        body:
+          isJsonRequest && requestJson
+            ? this._getRequestData(requestJson, event, webhook.noDefaults)
+            : undefined,
+        form:
+          !isJsonRequest && requestFormData
+            ? this._getRequestData(requestFormData, event, webhook.noDefaults)
+            : undefined,
         headers: requestHeaders,
         json: true,
         method: validateRequestType(nullthrows(requestType)),

From 787e0208bf24b0d8c1fef026bfbcae5e7f23c0a5 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 6 Jul 2017 00:28:43 +0200
Subject: [PATCH 460/504] update package-lock.json, closes
 https://github.com/Brewskey/spark-server/issues/234

---
 package-lock.json | 43 +++++++++++++++++++------------------------
 1 file changed, 19 insertions(+), 24 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index c63e2188..08a44c05 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,9 +24,9 @@
       "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A=="
     },
     "@types/node": {
-      "version": "8.0.6",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.6.tgz",
-      "integrity": "sha512-WuMAU8y6bw1I7MKOEtkuoSUsxHhVYkdv4nuqq3sed+Yx2JFFlCj4EoZweu4TkcrKAIuPXAjhGa5ZcROUJbj0AA=="
+      "version": "8.0.7",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.7.tgz",
+      "integrity": "sha512-fuCPLPe4yY0nv6Z1rTLFCEC452jl0k7i3gF/c8hdEKpYtEpt6Sk67hTGbxx8C0wmifFGPvKYd/O8CvS6dpgxMQ=="
     },
     "@types/serve-static": {
       "version": "1.7.31",
@@ -146,9 +146,9 @@
       "dev": true
     },
     "arr-flatten": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz",
-      "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E="
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
     },
     "array-differ": {
       "version": "1.0.0",
@@ -1126,9 +1126,9 @@
       "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
     },
     "commander": {
-      "version": "2.10.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz",
-      "integrity": "sha512-q/r9trjmuikWDRJNTBHAVnWhuU6w+z80KgBq7j9YDclik5E7X4xi0KnlZBNFA1zOQ+SH/vHMWd2mC9QTOz7GpA=="
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+      "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
     },
     "common-path-prefix": {
       "version": "1.0.0",
@@ -1695,9 +1695,9 @@
       "dev": true,
       "dependencies": {
         "acorn": {
-          "version": "5.0.3",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
-          "integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.0.tgz",
+          "integrity": "sha512-WXZ0VTJT8EE25BmZjc+wr0qIwG7QaEna9csPKHS6WQp8gDo4V376wUWi222LXRiuAF6CAS4Ejv736DdRwuPK9g==",
           "dev": true
         }
       }
@@ -2614,11 +2614,6 @@
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
       "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
     },
-    "graceful-readlink": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
-      "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
-    },
     "h5.buffers": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/h5.buffers/-/h5.buffers-0.1.1.tgz",
@@ -2789,9 +2784,9 @@
       "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew="
     },
     "irregular-plurals": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.2.0.tgz",
-      "integrity": "sha1-OPKZg0uowAwwvpxVThNyaXUv86w=",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.3.0.tgz",
+      "integrity": "sha512-njf5A+Mxb3kojuHd1DzISjjIl+XhyzovXEOyPPSzdQozq/Lf2tN27mOrAAsxEPZxpn6I4MGzs1oo9TxXxPFpaA==",
       "dev": true
     },
     "is": {
@@ -4405,9 +4400,9 @@
       "dev": true
     },
     "rxjs": {
-      "version": "5.4.1",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.1.tgz",
-      "integrity": "sha1-ti91fyeURdJloYpY+wpw3JDpFiY=",
+      "version": "5.4.2",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz",
+      "integrity": "sha1-KjI2/L8D31e64G/Wly/ZnlwI/Pc=",
       "dev": true,
       "dependencies": {
         "symbol-observable": {
@@ -4547,7 +4542,7 @@
       "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#2d8d38120ce1862730cb36a91fbe35f183b8ed43"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#22ef7a1618ddeabd410463739edda477e10a42b0"
     },
     "spawn-sync": {
       "version": "1.0.15",

From 09a8d17426c43e7896f9fcb2cc36de3687fbe4a9 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 6 Jul 2017 01:10:15 +0200
Subject: [PATCH 461/504] update migrateScript, so it saves dates correctly.

---
 dist/scripts/migrateFilesToDatabase.js | 23 ++++++++++++++++++++---
 src/scripts/migrateFilesToDatabase.js  | 17 ++++++++++++++++-
 2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/dist/scripts/migrateFilesToDatabase.js b/dist/scripts/migrateFilesToDatabase.js
index afc01388..05eebbce 100644
--- a/dist/scripts/migrateFilesToDatabase.js
+++ b/dist/scripts/migrateFilesToDatabase.js
@@ -8,6 +8,10 @@ var _map = require('babel-runtime/core-js/map');
 
 var _map2 = _interopRequireDefault(_map);
 
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
 var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
 
 var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
@@ -140,6 +144,19 @@ var filterID = function filterID(_ref2) {
   return (0, _extends3.default)({}, otherProps);
 };
 
+var deepDateCast = function deepDateCast(node) {
+  (0, _keys2.default)(node).forEach(function (key) {
+    if (node[key] === Object(node[key])) {
+      deepDateCast(node[key]);
+    }
+    if (!isNaN(Date.parse(node[key]))) {
+      // eslint-disable-next-line
+      node[key] = new Date(node[key]);
+    }
+  });
+  return node;
+};
+
 var insertItem = function insertItem(database, collectionName) {
   return function () {
     var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(item) {
@@ -176,7 +193,7 @@ var insertUsers = function () {
           case 0:
             userIDsMap = new _map2.default();
             _context4.next = 3;
-            return _promise2.default.all(users.map(function () {
+            return _promise2.default.all(users.map(deepDateCast).map(function () {
               var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(user) {
                 var insertedUser;
                 return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -250,14 +267,14 @@ var insertUsers = function () {
           return _promise2.default.all(getFiles(_settings2.default.WEBHOOKS_DIRECTORY).map(function (_ref8) {
             var fileBuffer = _ref8.fileBuffer;
             return parseFile(fileBuffer);
-          }).map(mapOwnerID(userIDsMap)).map(filterID).map(insertItem(database, 'webhooks')));
+          }).map(deepDateCast).map(mapOwnerID(userIDsMap)).map(filterID).map(insertItem(database, 'webhooks')));
 
         case 12:
           _context5.next = 14;
           return _promise2.default.all(getFiles(_settings2.default.DEVICE_DIRECTORY).map(function (_ref9) {
             var fileBuffer = _ref9.fileBuffer;
             return parseFile(fileBuffer);
-          }).map(mapOwnerID(userIDsMap)).map(translateDeviceID).map(filterID).map(insertItem(database, 'deviceAttributes')));
+          }).map(deepDateCast).map(mapOwnerID(userIDsMap)).map(translateDeviceID).map(filterID).map(insertItem(database, 'deviceAttributes')));
 
         case 14:
           _context5.next = 16;
diff --git a/src/scripts/migrateFilesToDatabase.js b/src/scripts/migrateFilesToDatabase.js
index 1e3ca5b7..b2047178 100644
--- a/src/scripts/migrateFilesToDatabase.js
+++ b/src/scripts/migrateFilesToDatabase.js
@@ -74,6 +74,19 @@ const translateDeviceID = (item: Object): Object => ({
 // eslint-disable-next-line no-unused-vars
 const filterID = ({ id, ...otherProps }: Object): Object => ({ ...otherProps });
 
+const deepDateCast = (node: any): any => {
+  Object.keys(node).forEach((key: string) => {
+    if (node[key] === Object(node[key])) {
+      deepDateCast(node[key]);
+    }
+    if (!isNaN(Date.parse(node[key]))) {
+      // eslint-disable-next-line
+      node[key] = new Date(node[key]);
+    }
+  });
+  return node;
+};
+
 const insertItem = (
   database: Object,
   collectionName: string,
@@ -87,7 +100,7 @@ const insertUsers = async (
   const userIDsMap = new Map();
 
   await Promise.all(
-    users.map(async (user: Object): Promise => {
+    users.map(deepDateCast).map(async (user: Object): Promise => {
       const insertedUser = await database.insertOne('users', filterID(user));
       userIDsMap.set(user.id, insertedUser.id);
     }),
@@ -111,6 +124,7 @@ const insertUsers = async (
     await Promise.all(
       getFiles(settings.WEBHOOKS_DIRECTORY)
         .map(({ fileBuffer }: FileObject): Object => parseFile(fileBuffer))
+        .map(deepDateCast)
         .map(mapOwnerID(userIDsMap))
         .map(filterID)
         .map(insertItem(database, 'webhooks')),
@@ -119,6 +133,7 @@ const insertUsers = async (
     await Promise.all(
       getFiles(settings.DEVICE_DIRECTORY)
         .map(({ fileBuffer }: FileObject): Object => parseFile(fileBuffer))
+        .map(deepDateCast)
         .map(mapOwnerID(userIDsMap))
         .map(translateDeviceID)
         .map(filterID)

From cc8969f888f1bfd385484355fa677b3d8c9611f0 Mon Sep 17 00:00:00 2001
From: straccio 
Date: Thu, 6 Jul 2017 11:42:42 +0200
Subject: [PATCH 462/504] Packages update.

---
 package.json | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/package.json b/package.json
index 1b24af70..eeb45c25 100644
--- a/package.json
+++ b/package.json
@@ -80,13 +80,14 @@
   "dependencies": {
     "array-flatten": "^2.1.1",
     "babel-cli": "^6.22.2",
+    "babel-core": "^6.25.0",
     "babel-runtime": "^6.22.0",
     "basic-auth-parser": "0.0.2",
     "binary-version-reader": "^0.5.0",
     "body-parser": "^1.15.2",
     "bunyan": "^1.8.10",
     "bunyan-middleware": "^0.8.0",
-    "chalk": "^1.1.3",
+    "chalk": "^2.0.1",
     "constitute": "^1.6.2",
     "ec-key": "0.0.2",
     "express": "^4.14.0",
@@ -107,7 +108,7 @@
     "uuid": "^3.0.1"
   },
   "devDependencies": {
-    "ava": "^0.17.0",
+    "ava": "^0.20.0",
     "babel-eslint": "^7.1.1",
     "babel-plugin-transform-class-properties": "^6.19.0",
     "babel-plugin-transform-decorators": "^6.24.1",
@@ -121,20 +122,20 @@
     "babel-preset-stage-0": "^6.16.0",
     "babel-preset-stage-1": "^6.16.0",
     "babel-register": "^6.18.0",
-    "eslint": "^3.11.0",
-    "eslint-config-airbnb-base": "^10.0.1",
+    "eslint": "^4.1.1",
+    "eslint-config-airbnb-base": "^11.2.0",
     "eslint-config-prettier": "^2.2.0",
     "eslint-plugin-flowtype": "^2.28.2",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-sorting": "^0.3.0",
-    "flow-bin": "^0.37.4",
+    "flow-bin": "^0.49.1",
     "lint-staged": "^4.0.0",
     "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
     "prettier": "^1.4.4",
     "rimraf": "^2.5.4",
-    "sinon": "^1.17.7",
-    "supertest": "^2.0.1",
+    "sinon": "^2.3.6",
+    "supertest": "^3.0.0",
     "uglifyjs": "^2.4.10"
   }
 }

From 7d620b87cb8399f7a651db3fc2f9a54d78082005 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 7 Jul 2017 00:21:00 +0200
Subject: [PATCH 463/504] passing not json event.data as sting for webhooks as
 form-ulr-encoded

---
 dist/managers/WebhookManager.js | 25 ++++++++++++-------------
 src/managers/WebhookManager.js  | 19 ++++++++++---------
 test/WebhookManager.test.js     | 32 +++++++++++++++++++++++++-------
 3 files changed, 47 insertions(+), 29 deletions(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 9c40cef7..03a21217 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -307,7 +307,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
 
   this.runWebhook = function () {
     var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(webhook, event) {
-      var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, isJsonRequest, requestOptions, _responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks;
+      var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, requestOptions, _responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks;
 
       return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
@@ -323,11 +323,10 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
               requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject);
               responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject);
               requestType = _this._compileTemplate(webhook.requestType, webhookVariablesObject);
-              isJsonRequest = !!requestJson || !requestFormData;
               requestOptions = {
                 auth: requestAuth,
-                body: isJsonRequest && requestJson ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
-                form: !isJsonRequest && requestFormData ? _this._getRequestData(requestFormData, event, webhook.noDefaults) : undefined,
+                body: requestJson ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
+                form: !requestJson ? _this._getRequestData(requestFormData || null, event, webhook.noDefaults) : undefined,
                 headers: requestHeaders,
                 json: true,
                 method: validateRequestType((0, _nullthrows2.default)(requestType)),
@@ -335,20 +334,20 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
                 strictSSL: webhook.rejectUnauthorized,
                 url: (0, _nullthrows2.default)(requestUrl)
               };
-              _context6.next = 14;
+              _context6.next = 13;
               return _this._callWebhook(webhook, event, requestOptions);
 
-            case 14:
+            case 13:
               _responseBody = _context6.sent;
 
               if (_responseBody) {
-                _context6.next = 17;
+                _context6.next = 16;
                 break;
               }
 
               return _context6.abrupt('return');
 
-            case 17:
+            case 16:
               isResponseBodyAnObject = _responseBody === Object(_responseBody);
               responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(_responseBody);
               responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(_responseBody) : _responseBody);
@@ -367,21 +366,21 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
               });
 
               _this._webhookLogger.log(event, webhook, requestOptions, _responseBody, responseEventData);
-              _context6.next = 28;
+              _context6.next = 27;
               break;
 
-            case 25:
-              _context6.prev = 25;
+            case 24:
+              _context6.prev = 24;
               _context6.t0 = _context6['catch'](0);
 
               logger.error({ err: _context6.t0 }, 'webhookError');
 
-            case 28:
+            case 27:
             case 'end':
               return _context6.stop();
           }
         }
-      }, _callee6, _this, [[0, 25]]);
+      }, _callee6, _this, [[0, 24]]);
     }));
 
     return function (_x4, _x5) {
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 4366211b..8dd6844d 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -225,17 +225,18 @@ class WebhookManager {
         webhookVariablesObject,
       );
 
-      const isJsonRequest = !!requestJson || !requestFormData;
       const requestOptions = {
         auth: (requestAuth: any),
-        body:
-          isJsonRequest && requestJson
-            ? this._getRequestData(requestJson, event, webhook.noDefaults)
-            : undefined,
-        form:
-          !isJsonRequest && requestFormData
-            ? this._getRequestData(requestFormData, event, webhook.noDefaults)
-            : undefined,
+        body: requestJson
+          ? this._getRequestData(requestJson, event, webhook.noDefaults)
+          : undefined,
+        form: !requestJson
+          ? this._getRequestData(
+              requestFormData || null,
+              event,
+              webhook.noDefaults,
+            ) || event.data
+          : undefined,
         headers: requestHeaders,
         json: true,
         method: validateRequestType(nullthrows(requestType)),
diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js
index 26dfff28..3263bb1f 100644
--- a/test/WebhookManager.test.js
+++ b/test/WebhookManager.test.js
@@ -56,7 +56,10 @@ test('should run basic request', async t => {
     (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(requestOptions.form, undefined);
+      t.is(
+        JSON.stringify(requestOptions.form),
+        JSON.stringify(defaultRequestData),
+      );
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
@@ -85,7 +88,7 @@ test('should run basic request without default data', async t => {
     (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(requestOptions.form, undefined);
+      t.is(requestOptions.form, data);
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
@@ -199,7 +202,10 @@ test('should compile request auth header', async t => {
         }),
       );
       t.is(requestOptions.body, undefined);
-      t.is(requestOptions.form, undefined);
+      t.is(
+        JSON.stringify(requestOptions.form),
+        JSON.stringify(defaultRequestData),
+      );
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.url, WEBHOOK_BASE.url);
@@ -231,7 +237,10 @@ test('should compile request headers', async t => {
     (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(requestOptions.form, undefined);
+      t.is(
+        JSON.stringify(requestOptions.form),
+        JSON.stringify(defaultRequestData),
+      );
       t.is(
         JSON.stringify(requestOptions.headers),
         JSON.stringify({
@@ -266,7 +275,10 @@ test('should compile request url', async t => {
     (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(requestOptions.form, undefined);
+      t.is(
+        JSON.stringify(requestOptions.form),
+        JSON.stringify(defaultRequestData),
+      );
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(requestOptions.qs, undefined);
@@ -299,7 +311,10 @@ test('should compile request query', async t => {
     (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(requestOptions.form, undefined);
+      t.is(
+        JSON.stringify(requestOptions.form),
+        JSON.stringify(defaultRequestData),
+      );
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, WEBHOOK_BASE.requestType);
       t.is(
@@ -332,7 +347,10 @@ test('should compile requestType', async t => {
     (webhook: Webhook, event: Event, requestOptions: RequestOptions) => {
       t.is(requestOptions.auth, undefined);
       t.is(requestOptions.body, undefined);
-      t.is(requestOptions.form, undefined);
+      t.is(
+        JSON.stringify(requestOptions.form),
+        JSON.stringify(defaultRequestData),
+      );
       t.is(requestOptions.headers, undefined);
       t.is(requestOptions.method, 'POST');
       t.is(requestOptions.url, WEBHOOK_BASE.url);

From 6a803e43062fc3107e2a7b8be8ecddb31ea8c92d Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 8 Jul 2017 13:29:42 +0200
Subject: [PATCH 464/504] update build

---
 dist/managers/WebhookManager.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 03a21217..0963f2e9 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -326,7 +326,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
               requestOptions = {
                 auth: requestAuth,
                 body: requestJson ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
-                form: !requestJson ? _this._getRequestData(requestFormData || null, event, webhook.noDefaults) : undefined,
+                form: !requestJson ? _this._getRequestData(requestFormData || null, event, webhook.noDefaults) || event.data : undefined,
                 headers: requestHeaders,
                 json: true,
                 method: validateRequestType((0, _nullthrows2.default)(requestType)),

From 007470242a32bb35300f43fa7c679bdfef94fc70 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 8 Jul 2017 17:01:07 +0200
Subject: [PATCH 465/504] stringify device variables

---
 .../DeviceAttributeDatabaseRepository.js      | 58 +++++++++++++++----
 .../DeviceAttributeDatabaseRepository.js      | 37 ++++++++++--
 2 files changed, 77 insertions(+), 18 deletions(-)

diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index ffdf9bc1..801f0e52 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -4,10 +4,18 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
 var _extends2 = require('babel-runtime/helpers/extends');
 
 var _extends3 = _interopRequireDefault(_extends2);
 
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -85,9 +93,10 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
               return _this._database.find(_this._collectionName, query);
 
             case 3:
-              return _context3.abrupt('return', _context3.sent);
+              _context3.t0 = _this._parseVariables;
+              return _context3.abrupt('return', _context3.sent.map(_context3.t0));
 
-            case 4:
+            case 5:
             case 'end':
               return _context3.stop();
           }
@@ -106,13 +115,15 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
         while (1) {
           switch (_context4.prev = _context4.next) {
             case 0:
-              _context4.next = 2;
+              _context4.t0 = _this;
+              _context4.next = 3;
               return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
 
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
-
             case 3:
+              _context4.t1 = _context4.sent;
+              return _context4.abrupt('return', _context4.t0._parseVariables.call(_context4.t0, _context4.t1));
+
+            case 5:
             case 'end':
               return _context4.stop();
           }
@@ -126,18 +137,24 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
   }();
 
   this.updateByID = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, props) {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, _ref6) {
+      var variables = _ref6.variables,
+          props = (0, _objectWithoutProperties3.default)(_ref6, ['variables']);
+      var attributesToSave;
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
-              _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, props, { timeStamp: new Date() }) });
+              attributesToSave = (0, _extends3.default)({}, props, {
+                variables: variables ? (0, _stringify2.default)(variables) : undefined
+              });
+              _context5.next = 3;
+              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, attributesToSave, { timeStamp: new Date() }) });
 
-            case 2:
+            case 3:
               return _context5.abrupt('return', _context5.sent);
 
-            case 3:
+            case 4:
             case 'end':
               return _context5.stop();
           }
@@ -150,8 +167,25 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
     };
   }();
 
+  this._parseVariables = function (attributesFromDB) {
+    if (!attributesFromDB) {
+      return null;
+    }
+
+    var variables = attributesFromDB.variables;
+
+    return (0, _extends3.default)({}, attributesFromDB, {
+      variables: variables ? JSON.parse(variables) : undefined
+    });
+  };
+
   this._database = database;
   this._permissionManager = permissionManager;
-};
+}
+
+// mongo and neDB don't support dots in variables names
+// but some of the server users want to have dots in their device var names
+// so we have to stringify them and parse back.
+;
 
 exports.default = DeviceAttributeDatabaseRepository;
\ No newline at end of file
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 8b4a0544..81a59270 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -29,20 +29,45 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
 
   getAll = async (userID: ?string = null): Promise> => {
     const query = userID ? { ownerID: userID } : {};
-    return await this._database.find(this._collectionName, query);
+    return (await this._database.find(this._collectionName, query)).map(
+      this._parseVariables,
+    );
   };
 
   getByID = async (deviceID: string): Promise =>
-    await this._database.findOne(this._collectionName, { deviceID });
+    this._parseVariables(
+      await this._database.findOne(this._collectionName, { deviceID }),
+    );
 
   updateByID = async (
     deviceID: string,
-    props: $Shape,
-  ): Promise =>
-    await this._database.findAndModify(
+    { variables, ...props }: $Shape,
+  ): Promise => {
+    const attributesToSave = {
+      ...props,
+      variables: variables ? JSON.stringify(variables) : undefined,
+    };
+
+    return await this._database.findAndModify(
       this._collectionName,
       { deviceID },
-      { $set: { ...props, timeStamp: new Date() } },
+      { $set: { ...attributesToSave, timeStamp: new Date() } },
     );
+  };
+
+  // mongo and neDB don't support dots in variables names
+  // but some of the server users want to have dots in their device var names
+  // so we have to stringify them and parse back.
+  _parseVariables = (attributesFromDB: ?Object): ?DeviceAttributes => {
+    if (!attributesFromDB) {
+      return null;
+    }
+
+    const { variables } = attributesFromDB;
+    return {
+      ...attributesFromDB,
+      variables: variables ? JSON.parse(variables) : undefined,
+    };
+  };
 }
 export default DeviceAttributeDatabaseRepository;

From 84a0219d1bb1bf0b95b51b991cc8263cbb41272f Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Wed, 12 Jul 2017 21:17:07 -0700
Subject: [PATCH 466/504] try/catch around device attributes as JSON
 deserializer was breaking on existing devices.

---
 package-lock.json                             | 3679 ++++++++++++-----
 .../DeviceAttributeDatabaseRepository.js      |   12 +-
 2 files changed, 2697 insertions(+), 994 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 08a44c05..8f6690ce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2,21 +2,32 @@
   "name": "spark-server",
   "version": "0.1.1",
   "lockfileVersion": 1,
+  "requires": true,
   "dependencies": {
     "@types/bunyan": {
       "version": "0.0.36",
       "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-0.0.36.tgz",
-      "integrity": "sha1-/dhlxY6OqvCtQBzQMtyGH1xXNCo="
+      "integrity": "sha1-/dhlxY6OqvCtQBzQMtyGH1xXNCo=",
+      "requires": {
+        "@types/node": "8.0.7"
+      }
     },
     "@types/express": {
       "version": "4.0.36",
       "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz",
-      "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A=="
+      "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A==",
+      "requires": {
+        "@types/express-serve-static-core": "4.0.48",
+        "@types/serve-static": "1.7.31"
+      }
     },
     "@types/express-serve-static-core": {
       "version": "4.0.48",
       "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz",
-      "integrity": "sha512-+W+fHO/hUI6JX36H8FlgdMHU3Dk4a/Fn08fW5qdd7MjPP/wJlzq9fkCrgaH0gES8vohVeqwefHwPa4ylVKyYIg=="
+      "integrity": "sha512-+W+fHO/hUI6JX36H8FlgdMHU3Dk4a/Fn08fW5qdd7MjPP/wJlzq9fkCrgaH0gES8vohVeqwefHwPa4ylVKyYIg==",
+      "requires": {
+        "@types/node": "8.0.7"
+      }
     },
     "@types/mime": {
       "version": "1.3.1",
@@ -31,7 +42,11 @@
     "@types/serve-static": {
       "version": "1.7.31",
       "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz",
-      "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho="
+      "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=",
+      "requires": {
+        "@types/express-serve-static-core": "4.0.48",
+        "@types/mime": "1.3.1"
+      }
     },
     "abbrev": {
       "version": "1.1.0",
@@ -41,7 +56,11 @@
     "accepts": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
-      "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo="
+      "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
+      "requires": {
+        "mime-types": "2.1.15",
+        "negotiator": "0.6.1"
+      }
     },
     "acorn": {
       "version": "1.2.2",
@@ -53,6 +72,9 @@
       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
       "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
       "dev": true,
+      "requires": {
+        "acorn": "3.3.0"
+      },
       "dependencies": {
         "acorn": {
           "version": "3.3.0",
@@ -66,6 +88,10 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz",
       "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=",
+      "requires": {
+        "extend": "3.0.1",
+        "semver": "5.0.3"
+      },
       "dependencies": {
         "semver": {
           "version": "5.0.3",
@@ -77,7 +103,11 @@
     "ajv": {
       "version": "4.11.8",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
-      "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY="
+      "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+      "requires": {
+        "co": "4.6.0",
+        "json-stable-stringify": "1.0.1"
+      }
     },
     "ajv-keywords": {
       "version": "1.5.1",
@@ -94,7 +124,10 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-1.1.0.tgz",
       "integrity": "sha1-LwwWWIKXOa3V67FeawxuNCPwFro=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "string-width": "1.0.2"
+      }
     },
     "ansi-escapes": {
       "version": "1.4.0",
@@ -115,7 +148,11 @@
     "anymatch": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
-      "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc="
+      "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=",
+      "requires": {
+        "arrify": "1.0.1",
+        "micromatch": "2.3.11"
+      }
     },
     "app-root-path": {
       "version": "2.0.1",
@@ -132,12 +169,18 @@
       "version": "1.0.9",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
       "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "sprintf-js": "1.0.3"
+      }
     },
     "arr-diff": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
-      "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8="
+      "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+      "requires": {
+        "arr-flatten": "1.1.0"
+      }
     },
     "arr-exclude": {
       "version": "1.0.0",
@@ -171,7 +214,10 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
       "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "array-uniq": "1.0.3"
+      }
     },
     "array-uniq": {
       "version": "1.0.3",
@@ -197,7 +243,12 @@
     "asn1.js": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-1.0.6.tgz",
-      "integrity": "sha1-9DrnMISV4XvTlfjAP6QA/vck2iQ="
+      "integrity": "sha1-9DrnMISV4XvTlfjAP6QA/vck2iQ=",
+      "requires": {
+        "bn.js": "2.2.0",
+        "inherits": "2.0.3",
+        "minimalistic-assert": "1.0.0"
+      }
     },
     "assert-plus": {
       "version": "0.2.0",
@@ -217,7 +268,10 @@
     "async": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
-      "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw=="
+      "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==",
+      "requires": {
+        "lodash": "4.17.4"
+      }
     },
     "async-each": {
       "version": "1.0.1",
@@ -240,6 +294,83 @@
       "resolved": "https://registry.npmjs.org/ava/-/ava-0.17.0.tgz",
       "integrity": "sha1-NZ4qiWFoAe8Dkpw88QqdT45FHQI=",
       "dev": true,
+      "requires": {
+        "arr-flatten": "1.1.0",
+        "array-union": "1.0.2",
+        "array-uniq": "1.0.3",
+        "arrify": "1.0.1",
+        "auto-bind": "0.1.0",
+        "ava-files": "0.2.0",
+        "ava-init": "0.1.6",
+        "babel-code-frame": "6.22.0",
+        "babel-core": "6.25.0",
+        "babel-plugin-ava-throws-helper": "0.1.0",
+        "babel-plugin-detective": "2.0.0",
+        "babel-plugin-espower": "2.3.2",
+        "babel-plugin-transform-runtime": "6.23.0",
+        "babel-preset-es2015": "6.24.1",
+        "babel-preset-es2015-node4": "2.1.1",
+        "babel-preset-stage-2": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "bluebird": "3.5.0",
+        "caching-transform": "1.0.1",
+        "chalk": "1.1.3",
+        "chokidar": "1.7.0",
+        "clean-yaml-object": "0.1.0",
+        "cli-cursor": "1.0.2",
+        "cli-spinners": "0.1.2",
+        "cli-truncate": "0.2.1",
+        "co-with-promise": "4.6.0",
+        "common-path-prefix": "1.0.0",
+        "convert-source-map": "1.5.0",
+        "core-assert": "0.2.1",
+        "currently-unhandled": "0.4.1",
+        "debug": "2.6.8",
+        "empower-core": "0.6.2",
+        "figures": "1.7.0",
+        "find-cache-dir": "0.1.1",
+        "fn-name": "2.0.1",
+        "get-port": "2.1.0",
+        "has-flag": "2.0.0",
+        "ignore-by-default": "1.0.1",
+        "is-ci": "1.0.10",
+        "is-generator-fn": "1.0.0",
+        "is-obj": "1.0.1",
+        "is-observable": "0.2.0",
+        "is-promise": "2.1.0",
+        "last-line-stream": "1.0.0",
+        "lodash.debounce": "4.0.8",
+        "lodash.difference": "4.5.0",
+        "lodash.isequal": "4.5.0",
+        "loud-rejection": "1.6.0",
+        "matcher": "0.1.2",
+        "max-timeout": "1.0.0",
+        "md5-hex": "1.3.0",
+        "meow": "3.7.0",
+        "ms": "0.7.3",
+        "object-assign": "4.1.1",
+        "observable-to-promise": "0.4.0",
+        "option-chain": "0.1.1",
+        "package-hash": "1.2.0",
+        "pkg-conf": "1.1.3",
+        "plur": "2.1.2",
+        "power-assert-context-formatter": "1.1.1",
+        "power-assert-renderer-assertion": "1.1.1",
+        "power-assert-renderer-succinct": "1.1.1",
+        "pretty-ms": "2.1.0",
+        "repeating": "2.0.1",
+        "require-precompiled": "0.1.0",
+        "resolve-cwd": "1.0.0",
+        "semver": "5.3.0",
+        "set-immediate-shim": "1.0.1",
+        "source-map-support": "0.4.15",
+        "stack-utils": "0.4.0",
+        "strip-ansi": "3.0.1",
+        "strip-bom": "2.0.0",
+        "time-require": "0.1.2",
+        "unique-temp-dir": "1.0.0",
+        "update-notifier": "1.0.3"
+      },
       "dependencies": {
         "ms": {
           "version": "0.7.3",
@@ -253,13 +384,30 @@
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/ava-files/-/ava-files-0.2.0.tgz",
       "integrity": "sha1-x7i24uDOpjtXpuJ+DbFFx8Gc/iA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "auto-bind": "0.1.0",
+        "bluebird": "3.5.0",
+        "globby": "6.1.0",
+        "ignore-by-default": "1.0.1",
+        "lodash.flatten": "4.4.0",
+        "multimatch": "2.1.0",
+        "slash": "1.0.0"
+      }
     },
     "ava-init": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/ava-init/-/ava-init-0.1.6.tgz",
       "integrity": "sha1-7xntCyS2vzWdrW+63xoF2DY5XJE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "arr-exclude": "1.0.0",
+        "cross-spawn": "4.0.2",
+        "pinkie-promise": "2.0.1",
+        "read-pkg-up": "1.0.1",
+        "the-argv": "1.0.0",
+        "write-pkg": "1.0.0"
+      }
     },
     "aws-sign2": {
       "version": "0.6.0",
@@ -274,126 +422,271 @@
     "babel-cli": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.24.1.tgz",
-      "integrity": "sha1-IHzXBbumFImy6kG1MSNBz2rKIoM="
+      "integrity": "sha1-IHzXBbumFImy6kG1MSNBz2rKIoM=",
+      "requires": {
+        "babel-core": "6.25.0",
+        "babel-polyfill": "6.23.0",
+        "babel-register": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "chokidar": "1.7.0",
+        "commander": "2.11.0",
+        "convert-source-map": "1.5.0",
+        "fs-readdir-recursive": "1.0.0",
+        "glob": "7.1.2",
+        "lodash": "4.17.4",
+        "output-file-sync": "1.1.2",
+        "path-is-absolute": "1.0.1",
+        "slash": "1.0.0",
+        "source-map": "0.5.6",
+        "v8flags": "2.1.1"
+      }
     },
     "babel-code-frame": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
-      "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ="
+      "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=",
+      "requires": {
+        "chalk": "1.1.3",
+        "esutils": "2.0.2",
+        "js-tokens": "3.0.2"
+      }
     },
     "babel-core": {
       "version": "6.25.0",
       "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz",
-      "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk="
+      "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=",
+      "requires": {
+        "babel-code-frame": "6.22.0",
+        "babel-generator": "6.25.0",
+        "babel-helpers": "6.24.1",
+        "babel-messages": "6.23.0",
+        "babel-register": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0",
+        "babylon": "6.17.4",
+        "convert-source-map": "1.5.0",
+        "debug": "2.6.8",
+        "json5": "0.5.1",
+        "lodash": "4.17.4",
+        "minimatch": "3.0.4",
+        "path-is-absolute": "1.0.1",
+        "private": "0.1.7",
+        "slash": "1.0.0",
+        "source-map": "0.5.6"
+      }
     },
     "babel-eslint": {
       "version": "7.2.3",
       "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz",
       "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "6.22.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0",
+        "babylon": "6.17.4"
+      }
     },
     "babel-generator": {
       "version": "6.25.0",
       "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz",
-      "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw="
+      "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=",
+      "requires": {
+        "babel-messages": "6.23.0",
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0",
+        "detect-indent": "4.0.0",
+        "jsesc": "1.3.0",
+        "lodash": "4.17.4",
+        "source-map": "0.5.6",
+        "trim-right": "1.0.1"
+      }
     },
     "babel-helper-bindify-decorators": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
-      "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA="
+      "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=",
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-builder-binary-assignment-operator-visitor": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
       "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-explode-assignable-expression": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-call-delegate": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
       "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-hoist-variables": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-define-map": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz",
       "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-function-name": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0",
+        "lodash": "4.17.4"
+      }
     },
     "babel-helper-explode-assignable-expression": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz",
       "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-explode-class": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz",
-      "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes="
+      "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=",
+      "requires": {
+        "babel-helper-bindify-decorators": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-function-name": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
       "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-get-function-arity": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-get-function-arity": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
       "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-hoist-variables": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
       "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-optimise-call-expression": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
       "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-regex": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz",
       "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0",
+        "lodash": "4.17.4"
+      }
     },
     "babel-helper-remap-async-to-generator": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz",
       "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-function-name": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helper-replace-supers": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
       "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-optimise-call-expression": "6.24.1",
+        "babel-messages": "6.23.0",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-helpers": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
-      "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI="
+      "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-messages": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
-      "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4="
+      "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-ava-throws-helper": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-ava-throws-helper/-/babel-plugin-ava-throws-helper-0.1.0.tgz",
       "integrity": "sha1-lREHcIoSIIAmv4ykzvGKh7ybDP4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-template": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-check-es2015-constants": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
       "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-detective": {
       "version": "2.0.0",
@@ -405,7 +698,16 @@
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-2.3.2.tgz",
       "integrity": "sha1-VRa4/NsmyfDh2BYHSfbkxl5xJx4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-generator": "6.25.0",
+        "babylon": "6.17.4",
+        "call-matcher": "1.0.1",
+        "core-js": "2.4.1",
+        "espower-location-detector": "1.0.0",
+        "espurify": "1.7.0",
+        "estraverse": "4.2.0"
+      }
     },
     "babel-plugin-syntax-async-functions": {
       "version": "6.13.0",
@@ -488,306 +790,589 @@
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz",
       "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-remap-async-to-generator": "6.24.1",
+        "babel-plugin-syntax-async-generators": "6.13.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-async-to-generator": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz",
       "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-remap-async-to-generator": "6.24.1",
+        "babel-plugin-syntax-async-functions": "6.13.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-class-constructor-call": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz",
       "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-class-constructor-call": "6.18.0",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-plugin-transform-class-properties": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz",
       "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-function-name": "6.24.1",
+        "babel-plugin-syntax-class-properties": "6.13.0",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-plugin-transform-decorators": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz",
-      "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0="
+      "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=",
+      "requires": {
+        "babel-helper-explode-class": "6.24.1",
+        "babel-plugin-syntax-decorators": "6.13.0",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-decorators-legacy": {
       "version": "1.3.4",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz",
       "integrity": "sha1-dBtY9sW86eYCfgiC2cmU8E82aSU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-decorators": "6.13.0",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-plugin-transform-do-expressions": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz",
       "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-do-expressions": "6.13.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-arrow-functions": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
       "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-block-scoped-functions": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
       "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-block-scoping": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz",
       "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0",
+        "lodash": "4.17.4"
+      }
     },
     "babel-plugin-transform-es2015-classes": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
       "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-define-map": "6.24.1",
+        "babel-helper-function-name": "6.24.1",
+        "babel-helper-optimise-call-expression": "6.24.1",
+        "babel-helper-replace-supers": "6.24.1",
+        "babel-messages": "6.23.0",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-computed-properties": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
       "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-destructuring": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
       "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-duplicate-keys": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz",
       "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-for-of": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
       "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-function-name": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
       "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-function-name": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-literals": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
       "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-modules-amd": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz",
       "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-modules-commonjs": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz",
       "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-transform-strict-mode": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-modules-systemjs": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz",
       "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-hoist-variables": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-modules-umd": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz",
       "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-object-super": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
       "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-replace-supers": "6.24.1",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-parameters": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
       "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-call-delegate": "6.24.1",
+        "babel-helper-get-function-arity": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-template": "6.25.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-shorthand-properties": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
       "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-spread": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
       "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-sticky-regex": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz",
       "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-regex": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-plugin-transform-es2015-template-literals": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
       "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-typeof-symbol": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz",
       "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-es2015-unicode-regex": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz",
       "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-regex": "6.24.1",
+        "babel-runtime": "6.23.0",
+        "regexpu-core": "2.0.0"
+      }
     },
     "babel-plugin-transform-exponentiation-operator": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz",
       "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1",
+        "babel-plugin-syntax-exponentiation-operator": "6.13.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-export-extensions": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz",
       "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-export-extensions": "6.13.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-flow-strip-types": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz",
       "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-flow": "6.18.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-function-bind": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz",
       "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-function-bind": "6.13.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-object-rest-spread": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz",
       "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-object-rest-spread": "6.13.0",
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-regenerator": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz",
       "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "regenerator-transform": "0.9.11"
+      }
     },
     "babel-plugin-transform-runtime": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz",
       "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0"
+      }
     },
     "babel-plugin-transform-strict-mode": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
       "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0"
+      }
     },
     "babel-polyfill": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz",
-      "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0="
+      "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=",
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "core-js": "2.4.1",
+        "regenerator-runtime": "0.10.5"
+      }
     },
     "babel-preset-es2015": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
       "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-check-es2015-constants": "6.22.0",
+        "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
+        "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0",
+        "babel-plugin-transform-es2015-block-scoping": "6.24.1",
+        "babel-plugin-transform-es2015-classes": "6.24.1",
+        "babel-plugin-transform-es2015-computed-properties": "6.24.1",
+        "babel-plugin-transform-es2015-destructuring": "6.23.0",
+        "babel-plugin-transform-es2015-duplicate-keys": "6.24.1",
+        "babel-plugin-transform-es2015-for-of": "6.23.0",
+        "babel-plugin-transform-es2015-function-name": "6.24.1",
+        "babel-plugin-transform-es2015-literals": "6.22.0",
+        "babel-plugin-transform-es2015-modules-amd": "6.24.1",
+        "babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
+        "babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
+        "babel-plugin-transform-es2015-modules-umd": "6.24.1",
+        "babel-plugin-transform-es2015-object-super": "6.24.1",
+        "babel-plugin-transform-es2015-parameters": "6.24.1",
+        "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
+        "babel-plugin-transform-es2015-spread": "6.22.0",
+        "babel-plugin-transform-es2015-sticky-regex": "6.24.1",
+        "babel-plugin-transform-es2015-template-literals": "6.22.0",
+        "babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
+        "babel-plugin-transform-es2015-unicode-regex": "6.24.1",
+        "babel-plugin-transform-regenerator": "6.24.1"
+      }
     },
     "babel-preset-es2015-node4": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/babel-preset-es2015-node4/-/babel-preset-es2015-node4-2.1.1.tgz",
       "integrity": "sha1-4x8pCFm1hhnIz6JB0bC8kA+UHNs=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-transform-es2015-destructuring": "6.23.0",
+        "babel-plugin-transform-es2015-function-name": "6.24.1",
+        "babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
+        "babel-plugin-transform-es2015-parameters": "6.24.1",
+        "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
+        "babel-plugin-transform-es2015-spread": "6.22.0",
+        "babel-plugin-transform-es2015-sticky-regex": "6.24.1",
+        "babel-plugin-transform-es2015-unicode-regex": "6.24.1"
+      }
     },
     "babel-preset-es2016": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz",
       "integrity": "sha1-+QC/k+LrwNJ235uKtZck6/2Vn4s=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-transform-exponentiation-operator": "6.24.1"
+      }
     },
     "babel-preset-es2017": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-es2017/-/babel-preset-es2017-6.24.1.tgz",
       "integrity": "sha1-WXvq37n38gi8/YoS6bKym4svFNE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-trailing-function-commas": "6.22.0",
+        "babel-plugin-transform-async-to-generator": "6.24.1"
+      }
     },
     "babel-preset-latest": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-latest/-/babel-preset-latest-6.24.1.tgz",
       "integrity": "sha1-Z33gaRVKdIXC0lxXfAL2JLhbheg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-preset-es2015": "6.24.1",
+        "babel-preset-es2016": "6.24.1",
+        "babel-preset-es2017": "6.24.1"
+      }
     },
     "babel-preset-stage-0": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz",
       "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-transform-do-expressions": "6.22.0",
+        "babel-plugin-transform-function-bind": "6.22.0",
+        "babel-preset-stage-1": "6.24.1"
+      }
     },
     "babel-preset-stage-1": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz",
       "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-transform-class-constructor-call": "6.24.1",
+        "babel-plugin-transform-export-extensions": "6.22.0",
+        "babel-preset-stage-2": "6.24.1"
+      }
     },
     "babel-preset-stage-2": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz",
       "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-dynamic-import": "6.18.0",
+        "babel-plugin-transform-class-properties": "6.24.1",
+        "babel-plugin-transform-decorators": "6.24.1",
+        "babel-preset-stage-3": "6.24.1"
+      }
     },
     "babel-preset-stage-3": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz",
       "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-plugin-syntax-trailing-function-commas": "6.22.0",
+        "babel-plugin-transform-async-generator-functions": "6.24.1",
+        "babel-plugin-transform-async-to-generator": "6.24.1",
+        "babel-plugin-transform-exponentiation-operator": "6.24.1",
+        "babel-plugin-transform-object-rest-spread": "6.23.0"
+      }
     },
     "babel-register": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz",
-      "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118="
+      "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=",
+      "requires": {
+        "babel-core": "6.25.0",
+        "babel-runtime": "6.23.0",
+        "core-js": "2.4.1",
+        "home-or-tmp": "2.0.0",
+        "lodash": "4.17.4",
+        "mkdirp": "0.5.1",
+        "source-map-support": "0.4.15"
+      }
     },
     "babel-runtime": {
       "version": "6.23.0",
       "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
-      "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs="
+      "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
+      "requires": {
+        "core-js": "2.4.1",
+        "regenerator-runtime": "0.10.5"
+      }
     },
     "babel-template": {
       "version": "6.25.0",
       "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
-      "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE="
+      "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=",
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-traverse": "6.25.0",
+        "babel-types": "6.25.0",
+        "babylon": "6.17.4",
+        "lodash": "4.17.4"
+      }
     },
     "babel-traverse": {
       "version": "6.25.0",
       "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz",
-      "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE="
+      "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=",
+      "requires": {
+        "babel-code-frame": "6.22.0",
+        "babel-messages": "6.23.0",
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0",
+        "babylon": "6.17.4",
+        "debug": "2.6.8",
+        "globals": "9.18.0",
+        "invariant": "2.2.2",
+        "lodash": "4.17.4"
+      }
     },
     "babel-types": {
       "version": "6.25.0",
       "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
-      "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4="
+      "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=",
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "esutils": "2.0.2",
+        "lodash": "4.17.4",
+        "to-fast-properties": "1.0.3"
+      }
     },
     "babylon": {
       "version": "6.17.4",
@@ -818,7 +1403,10 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
       "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
-      "optional": true
+      "optional": true,
+      "requires": {
+        "tweetnacl": "0.14.5"
+      }
     },
     "binary-extensions": {
       "version": "1.8.0",
@@ -828,7 +1416,14 @@
     "binary-version-reader": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/binary-version-reader/-/binary-version-reader-0.5.1.tgz",
-      "integrity": "sha1-1LC9MGFlJlsPCcjHHiBd0K4ffHs="
+      "integrity": "sha1-1LC9MGFlJlsPCcjHHiBd0K4ffHs=",
+      "requires": {
+        "buffer-crc32": "0.2.13",
+        "chai": "3.5.0",
+        "h5.buffers": "0.1.1",
+        "when": "3.7.8",
+        "xtend": "4.0.1"
+      }
     },
     "bluebird": {
       "version": "3.5.0",
@@ -845,34 +1440,72 @@
       "version": "1.17.2",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz",
       "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=",
+      "requires": {
+        "bytes": "2.4.0",
+        "content-type": "1.0.2",
+        "debug": "2.6.7",
+        "depd": "1.1.0",
+        "http-errors": "1.6.1",
+        "iconv-lite": "0.4.15",
+        "on-finished": "2.3.0",
+        "qs": "6.4.0",
+        "raw-body": "2.2.0",
+        "type-is": "1.6.15"
+      },
       "dependencies": {
         "debug": {
           "version": "2.6.7",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
-          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
+          "requires": {
+            "ms": "2.0.0"
+          }
         }
       }
     },
     "boom": {
       "version": "2.10.1",
       "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
-      "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
+      "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+      "requires": {
+        "hoek": "2.16.3"
+      }
     },
     "boxen": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/boxen/-/boxen-0.6.0.tgz",
       "integrity": "sha1-g2TUJIrDT/DvGy8r9JpsYM4NgbY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "ansi-align": "1.1.0",
+        "camelcase": "2.1.1",
+        "chalk": "1.1.3",
+        "cli-boxes": "1.0.0",
+        "filled-array": "1.1.0",
+        "object-assign": "4.1.1",
+        "repeating": "2.0.1",
+        "string-width": "1.0.2",
+        "widest-line": "1.0.0"
+      }
     },
     "brace-expansion": {
       "version": "1.1.8",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
-      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI="
+      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+      "requires": {
+        "balanced-match": "1.0.0",
+        "concat-map": "0.0.1"
+      }
     },
     "braces": {
       "version": "1.8.5",
       "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
-      "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc="
+      "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+      "requires": {
+        "expand-range": "1.8.2",
+        "preserve": "0.2.0",
+        "repeat-element": "1.1.2"
+      }
     },
     "bson": {
       "version": "1.0.4",
@@ -904,17 +1537,32 @@
     "bunyan": {
       "version": "1.8.10",
       "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.10.tgz",
-      "integrity": "sha1-IB/t0mxwgLYy9BYHL1OpC5pSmBw="
+      "integrity": "sha1-IB/t0mxwgLYy9BYHL1OpC5pSmBw=",
+      "requires": {
+        "dtrace-provider": "0.8.3",
+        "moment": "2.18.1",
+        "mv": "2.1.1",
+        "safe-json-stringify": "1.0.4"
+      }
     },
     "bunyan-middleware": {
       "version": "0.8.0",
       "resolved": "https://registry.npmjs.org/bunyan-middleware/-/bunyan-middleware-0.8.0.tgz",
-      "integrity": "sha1-K6vEmGtHCsfq7303POAkjOqbXPA="
+      "integrity": "sha1-K6vEmGtHCsfq7303POAkjOqbXPA=",
+      "requires": {
+        "@types/bunyan": "0.0.36",
+        "@types/express": "4.0.36",
+        "uuid": "3.1.0"
+      }
     },
     "busboy": {
       "version": "0.2.14",
       "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
       "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
+      "requires": {
+        "dicer": "0.2.5",
+        "readable-stream": "1.1.14"
+      },
       "dependencies": {
         "isarray": {
           "version": "0.0.1",
@@ -924,7 +1572,13 @@
         "readable-stream": {
           "version": "1.1.14",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
-          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "requires": {
+            "core-util-is": "1.0.2",
+            "inherits": "2.0.3",
+            "isarray": "0.0.1",
+            "string_decoder": "0.10.31"
+          }
         },
         "string_decoder": {
           "version": "0.10.31",
@@ -942,13 +1596,24 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-1.0.1.tgz",
       "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "md5-hex": "1.3.0",
+        "mkdirp": "0.5.1",
+        "write-file-atomic": "1.3.4"
+      }
     },
     "call-matcher": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz",
       "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=",
       "dev": true,
+      "requires": {
+        "core-js": "2.4.1",
+        "deep-equal": "1.0.1",
+        "espurify": "1.7.0",
+        "estraverse": "4.2.0"
+      },
       "dependencies": {
         "deep-equal": {
           "version": "1.0.1",
@@ -968,7 +1633,10 @@
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
       "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "callsites": "0.2.0"
+      }
     },
     "callsites": {
       "version": "0.2.0",
@@ -986,7 +1654,11 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
       "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "camelcase": "2.1.1",
+        "map-obj": "1.0.1"
+      }
     },
     "capture-stack-trace": {
       "version": "1.0.0",
@@ -1002,17 +1674,39 @@
     "chai": {
       "version": "3.5.0",
       "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz",
-      "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc="
+      "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=",
+      "requires": {
+        "assertion-error": "1.0.2",
+        "deep-eql": "0.1.3",
+        "type-detect": "1.0.0"
+      }
     },
     "chalk": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg="
+      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+      "requires": {
+        "ansi-styles": "2.2.1",
+        "escape-string-regexp": "1.0.5",
+        "has-ansi": "2.0.0",
+        "strip-ansi": "3.0.1",
+        "supports-color": "2.0.0"
+      }
     },
     "chokidar": {
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
-      "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg="
+      "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
+      "requires": {
+        "anymatch": "1.3.0",
+        "async-each": "1.0.1",
+        "glob-parent": "2.0.0",
+        "inherits": "2.0.3",
+        "is-binary-path": "1.0.1",
+        "is-glob": "2.0.1",
+        "path-is-absolute": "1.0.1",
+        "readdirp": "2.1.0"
+      }
     },
     "ci-info": {
       "version": "1.0.0",
@@ -1042,7 +1736,10 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
       "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "restore-cursor": "1.0.1"
+      }
     },
     "cli-spinners": {
       "version": "0.1.2",
@@ -1054,7 +1751,11 @@
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
       "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "slice-ansi": "0.0.4",
+        "string-width": "1.0.2"
+      }
     },
     "cli-width": {
       "version": "2.1.0",
@@ -1076,6 +1777,10 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/co-bluebird/-/co-bluebird-1.1.0.tgz",
       "integrity": "sha1-yLnzqTIKftMJh9zKGlw8/1llXHw=",
+      "requires": {
+        "bluebird": "2.11.0",
+        "co-use": "1.1.0"
+      },
       "dependencies": {
         "bluebird": {
           "version": "2.11.0",
@@ -1094,6 +1799,9 @@
       "resolved": "https://registry.npmjs.org/co-with-promise/-/co-with-promise-4.6.0.tgz",
       "integrity": "sha1-QT59tvWJOmC5Qs9JLEvsk9tBWrc=",
       "dev": true,
+      "requires": {
+        "pinkie-promise": "1.0.0"
+      },
       "dependencies": {
         "pinkie": {
           "version": "1.0.0",
@@ -1105,7 +1813,10 @@
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
           "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "pinkie": "1.0.0"
+          }
         }
       }
     },
@@ -1123,7 +1834,10 @@
     "combined-stream": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-      "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
+      "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+      "requires": {
+        "delayed-stream": "1.0.0"
+      }
     },
     "commander": {
       "version": "2.11.0",
@@ -1145,7 +1859,10 @@
     "compact-array": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/compact-array/-/compact-array-0.0.1.tgz",
-      "integrity": "sha1-cSO7Rbeizoocco3oaBndOqaTo9U="
+      "integrity": "sha1-cSO7Rbeizoocco3oaBndOqaTo9U=",
+      "requires": {
+        "tape": "3.6.1"
+      }
     },
     "component-emitter": {
       "version": "1.2.1",
@@ -1161,13 +1878,29 @@
     "concat-stream": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
-      "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc="
+      "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+      "requires": {
+        "inherits": "2.0.3",
+        "readable-stream": "2.3.3",
+        "typedarray": "0.0.6"
+      }
     },
     "configstore": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/configstore/-/configstore-2.1.0.tgz",
       "integrity": "sha1-c3o6cDbpiGECqmCZ5HuzOrGroaE=",
       "dev": true,
+      "requires": {
+        "dot-prop": "3.0.0",
+        "graceful-fs": "4.1.11",
+        "mkdirp": "0.5.1",
+        "object-assign": "4.1.1",
+        "os-tmpdir": "1.0.2",
+        "osenv": "0.1.4",
+        "uuid": "2.0.3",
+        "write-file-atomic": "1.3.4",
+        "xdg-basedir": "2.0.0"
+      },
       "dependencies": {
         "uuid": {
           "version": "2.0.3",
@@ -1180,7 +1913,10 @@
     "constitute": {
       "version": "1.6.2",
       "resolved": "https://registry.npmjs.org/constitute/-/constitute-1.6.2.tgz",
-      "integrity": "sha1-up8rM/FRCsTRrD3cjUrnzlooqjI="
+      "integrity": "sha1-up8rM/FRCsTRrD3cjUrnzlooqjI=",
+      "requires": {
+        "clone": "1.0.2"
+      }
     },
     "contains-path": {
       "version": "0.1.0",
@@ -1223,7 +1959,11 @@
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz",
       "integrity": "sha1-+F4s+b/tKPdzzIs/pcW2m9wC/j8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "buf-compare": "1.0.1",
+        "is-error": "2.2.1"
+      }
     },
     "core-js": {
       "version": "2.4.1",
@@ -1240,6 +1980,16 @@
       "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz",
       "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=",
       "dev": true,
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "js-yaml": "3.8.4",
+        "minimist": "1.2.0",
+        "object-assign": "4.1.1",
+        "os-homedir": "1.0.2",
+        "parse-json": "2.2.0",
+        "pinkie-promise": "2.0.1",
+        "require-from-string": "1.2.1"
+      },
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
@@ -1253,42 +2003,65 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
       "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "capture-stack-trace": "1.0.0"
+      }
     },
     "cross-spawn": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
       "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
       "dev": true,
+      "requires": {
+        "lru-cache": "4.1.1",
+        "which": "1.2.14"
+      },
       "dependencies": {
         "lru-cache": {
           "version": "4.1.1",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
           "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "pseudomap": "1.0.2",
+            "yallist": "2.1.2"
+          }
         }
       }
     },
     "cryptiles": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
-      "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g="
+      "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+      "requires": {
+        "boom": "2.10.1"
+      }
     },
     "currently-unhandled": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
       "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "array-find-index": "1.0.2"
+      }
     },
     "d": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
-      "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8="
+      "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
+      "requires": {
+        "es5-ext": "0.10.23"
+      }
     },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
       "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "1.0.0"
+      },
       "dependencies": {
         "assert-plus": {
           "version": "1.0.0",
@@ -1312,7 +2085,10 @@
     "debug": {
       "version": "2.6.8",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
-      "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+      "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+      "requires": {
+        "ms": "2.0.0"
+      }
     },
     "decamelize": {
       "version": "1.2.0",
@@ -1324,6 +2100,9 @@
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
       "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=",
+      "requires": {
+        "type-detect": "0.1.1"
+      },
       "dependencies": {
         "type-detect": {
           "version": "0.1.1",
@@ -1359,12 +2138,29 @@
       "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
       "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
       "dev": true,
+      "requires": {
+        "globby": "5.0.0",
+        "is-path-cwd": "1.0.0",
+        "is-path-in-cwd": "1.0.0",
+        "object-assign": "4.1.1",
+        "pify": "2.3.0",
+        "pinkie-promise": "2.0.1",
+        "rimraf": "2.6.1"
+      },
       "dependencies": {
         "globby": {
           "version": "5.0.0",
           "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
           "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "array-union": "1.0.2",
+            "arrify": "1.0.1",
+            "glob": "7.1.2",
+            "object-assign": "4.1.1",
+            "pify": "2.3.0",
+            "pinkie-promise": "2.0.1"
+          }
         }
       }
     },
@@ -1386,12 +2182,19 @@
     "detect-indent": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
-      "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg="
+      "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
+      "requires": {
+        "repeating": "2.0.1"
+      }
     },
     "dicer": {
       "version": "0.2.5",
       "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
       "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
+      "requires": {
+        "readable-stream": "1.1.14",
+        "streamsearch": "0.1.2"
+      },
       "dependencies": {
         "isarray": {
           "version": "0.0.1",
@@ -1401,7 +2204,13 @@
         "readable-stream": {
           "version": "1.1.14",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
-          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "requires": {
+            "core-util-is": "1.0.2",
+            "inherits": "2.0.3",
+            "isarray": "0.0.1",
+            "string_decoder": "0.10.31"
+          }
         },
         "string_decoder": {
           "version": "0.10.31",
@@ -1414,19 +2223,29 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
       "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "esutils": "2.0.2",
+        "isarray": "1.0.0"
+      }
     },
     "dot-prop": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz",
       "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-obj": "1.0.1"
+      }
     },
     "dtrace-provider": {
       "version": "0.8.3",
       "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.3.tgz",
       "integrity": "sha1-uhv8ZJMoXM/PxqtpzVxh10wqQ78=",
-      "optional": true
+      "optional": true,
+      "requires": {
+        "nan": "2.6.2"
+      }
     },
     "duplexer": {
       "version": "0.1.1",
@@ -1438,13 +2257,22 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
       "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "readable-stream": "2.3.3"
+      }
     },
     "duplexify": {
       "version": "3.5.0",
       "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz",
       "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "end-of-stream": "1.0.0",
+        "inherits": "2.0.3",
+        "readable-stream": "2.3.3",
+        "stream-shift": "1.0.0"
+      }
     },
     "eastasianwidth": {
       "version": "0.1.1",
@@ -1455,13 +2283,19 @@
     "ec-key": {
       "version": "0.0.2",
       "resolved": "https://registry.npmjs.org/ec-key/-/ec-key-0.0.2.tgz",
-      "integrity": "sha1-UAUq6WGr1vGHEMI1qH80mnJGgYk="
+      "integrity": "sha1-UAUq6WGr1vGHEMI1qH80mnJGgYk=",
+      "requires": {
+        "asn1.js": "1.0.6"
+      }
     },
     "ecc-jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
       "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
-      "optional": true
+      "optional": true,
+      "requires": {
+        "jsbn": "0.1.1"
+      }
     },
     "ee-first": {
       "version": "1.1.1",
@@ -1478,7 +2312,11 @@
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz",
       "integrity": "sha1-Wt71ZgiOMfuoC6CjbfR9cJQWkUQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "call-signature": "0.0.2",
+        "core-js": "2.4.1"
+      }
     },
     "encodeurl": {
       "version": "1.0.1",
@@ -1490,12 +2328,18 @@
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
       "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=",
       "dev": true,
+      "requires": {
+        "once": "1.3.3"
+      },
       "dependencies": {
         "once": {
           "version": "1.3.3",
           "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
           "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "wrappy": "1.0.2"
+          }
         }
       }
     },
@@ -1503,28 +2347,53 @@
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
       "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-arrayish": "0.2.1"
+      }
     },
     "es3ify": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz",
-      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E="
+      "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=",
+      "requires": {
+        "esprima-fb": "3001.1.0-dev-harmony-fb",
+        "jstransform": "3.0.0",
+        "through": "2.3.8"
+      }
     },
     "es5-ext": {
       "version": "0.10.23",
       "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.23.tgz",
-      "integrity": "sha1-dXi1G+l0IHpUh4IbVlOMIk5Oezg="
+      "integrity": "sha1-dXi1G+l0IHpUh4IbVlOMIk5Oezg=",
+      "requires": {
+        "es6-iterator": "2.0.1",
+        "es6-symbol": "3.1.1"
+      }
     },
     "es6-iterator": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
-      "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI="
+      "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.23",
+        "es6-symbol": "3.1.1"
+      }
     },
     "es6-map": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
       "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.23",
+        "es6-iterator": "2.0.1",
+        "es6-set": "0.1.5",
+        "es6-symbol": "3.1.1",
+        "event-emitter": "0.3.5"
+      }
     },
     "es6-promise": {
       "version": "3.2.1",
@@ -1535,17 +2404,34 @@
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
       "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.23",
+        "es6-iterator": "2.0.1",
+        "es6-symbol": "3.1.1",
+        "event-emitter": "0.3.5"
+      }
     },
     "es6-symbol": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
-      "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc="
+      "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.23"
+      }
     },
     "es6-weak-map": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
-      "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8="
+      "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.23",
+        "es6-iterator": "2.0.1",
+        "es6-symbol": "3.1.1"
+      }
     },
     "escape-html": {
       "version": "1.0.3",
@@ -1561,13 +2447,56 @@
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
       "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "es6-map": "0.1.5",
+        "es6-weak-map": "2.0.2",
+        "esrecurse": "4.2.0",
+        "estraverse": "4.2.0"
+      }
     },
     "eslint": {
       "version": "3.19.0",
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
       "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=",
       "dev": true,
+      "requires": {
+        "babel-code-frame": "6.22.0",
+        "chalk": "1.1.3",
+        "concat-stream": "1.6.0",
+        "debug": "2.6.8",
+        "doctrine": "2.0.0",
+        "escope": "3.6.0",
+        "espree": "3.4.3",
+        "esquery": "1.0.0",
+        "estraverse": "4.2.0",
+        "esutils": "2.0.2",
+        "file-entry-cache": "2.0.0",
+        "glob": "7.1.2",
+        "globals": "9.18.0",
+        "ignore": "3.3.3",
+        "imurmurhash": "0.1.4",
+        "inquirer": "0.12.0",
+        "is-my-json-valid": "2.16.0",
+        "is-resolvable": "1.0.0",
+        "js-yaml": "3.8.4",
+        "json-stable-stringify": "1.0.1",
+        "levn": "0.3.0",
+        "lodash": "4.17.4",
+        "mkdirp": "0.5.1",
+        "natural-compare": "1.4.0",
+        "optionator": "0.8.2",
+        "path-is-inside": "1.0.2",
+        "pluralize": "1.2.1",
+        "progress": "1.1.8",
+        "require-uncached": "1.0.3",
+        "shelljs": "0.7.8",
+        "strip-bom": "3.0.0",
+        "strip-json-comments": "2.0.1",
+        "table": "3.8.3",
+        "text-table": "0.2.0",
+        "user-home": "2.0.0"
+      },
       "dependencies": {
         "strip-bom": {
           "version": "3.0.0",
@@ -1579,7 +2508,10 @@
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
           "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "os-homedir": "1.0.2"
+          }
         }
       }
     },
@@ -1594,6 +2526,9 @@
       "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.3.0.tgz",
       "integrity": "sha1-t1seq+oMi5ezRANkfuJds0m52KA=",
       "dev": true,
+      "requires": {
+        "get-stdin": "5.0.1"
+      },
       "dependencies": {
         "get-stdin": {
           "version": "5.0.1",
@@ -1607,61 +2542,109 @@
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz",
       "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "debug": "2.6.8",
+        "resolve": "1.3.3"
+      }
     },
     "eslint-module-utils": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz",
       "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "debug": "2.6.8",
+        "pkg-dir": "1.0.0"
+      }
     },
     "eslint-plugin-flowtype": {
       "version": "2.34.1",
       "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.34.1.tgz",
       "integrity": "sha512-xwXpTW7Xv+wfuQdfPILmFl9HWBdWbDjE1aZWWQ4EgCpQtMzymEkDQfyD1ME0VA8C0HTXV7cufypQRvLi+Hk/og==",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "lodash": "4.17.4"
+      }
     },
     "eslint-plugin-import": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.6.1.tgz",
       "integrity": "sha512-aAMb32eHCQaQmgdb1MOG1hfu/rPiNgGur2IF71VJeDfTXdLpPiKALKWlzxMdcxQOZZ2CmYVKabAxCvjACxH1uQ==",
       "dev": true,
+      "requires": {
+        "builtin-modules": "1.1.1",
+        "contains-path": "0.1.0",
+        "debug": "2.6.8",
+        "doctrine": "1.5.0",
+        "eslint-import-resolver-node": "0.3.1",
+        "eslint-module-utils": "2.1.1",
+        "has": "1.0.1",
+        "lodash.cond": "4.5.2",
+        "minimatch": "3.0.4",
+        "read-pkg-up": "2.0.0"
+      },
       "dependencies": {
         "doctrine": {
           "version": "1.5.0",
           "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
           "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "esutils": "2.0.2",
+            "isarray": "1.0.0"
+          }
         },
         "find-up": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
           "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "locate-path": "2.0.0"
+          }
         },
         "load-json-file": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
           "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "parse-json": "2.2.0",
+            "pify": "2.3.0",
+            "strip-bom": "3.0.0"
+          }
         },
         "path-type": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
           "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "pify": "2.3.0"
+          }
         },
         "read-pkg": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
           "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "load-json-file": "2.0.0",
+            "normalize-package-data": "2.4.0",
+            "path-type": "2.0.0"
+          }
         },
         "read-pkg-up": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
           "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "find-up": "2.1.0",
+            "read-pkg": "2.0.0"
+          }
         },
         "strip-bom": {
           "version": "3.0.0",
@@ -1686,13 +2669,23 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz",
       "integrity": "sha1-oXt+zFnTDheeK+9z+0E3cEyzMbU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-url": "1.2.2",
+        "path-is-absolute": "1.0.1",
+        "source-map": "0.5.6",
+        "xtend": "4.0.1"
+      }
     },
     "espree": {
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
       "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
       "dev": true,
+      "requires": {
+        "acorn": "5.1.0",
+        "acorn-jsx": "3.0.1"
+      },
       "dependencies": {
         "acorn": {
           "version": "5.1.0",
@@ -1711,19 +2704,29 @@
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.7.0.tgz",
       "integrity": "sha1-HFz2y8zDLm9jk4C9T5kfq5up0iY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "core-js": "2.4.1"
+      }
     },
     "esquery": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
       "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "estraverse": "4.2.0"
+      }
     },
     "esrecurse": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
       "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "estraverse": "4.2.0",
+        "object-assign": "4.1.1"
+      }
     },
     "estraverse": {
       "version": "4.2.0",
@@ -1744,31 +2747,62 @@
     "event-emitter": {
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
-      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk="
+      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.23"
+      }
     },
     "event-stream": {
       "version": "3.3.4",
       "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
       "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "duplexer": "0.1.1",
+        "from": "0.1.7",
+        "map-stream": "0.1.0",
+        "pause-stream": "0.0.11",
+        "split": "0.3.3",
+        "stream-combiner": "0.0.4",
+        "through": "2.3.8"
+      }
     },
     "execa": {
       "version": "0.7.0",
       "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
       "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
       "dev": true,
+      "requires": {
+        "cross-spawn": "5.1.0",
+        "get-stream": "3.0.0",
+        "is-stream": "1.1.0",
+        "npm-run-path": "2.0.2",
+        "p-finally": "1.0.0",
+        "signal-exit": "3.0.2",
+        "strip-eof": "1.0.0"
+      },
       "dependencies": {
         "cross-spawn": {
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
           "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "lru-cache": "4.1.1",
+            "shebang-command": "1.2.0",
+            "which": "1.2.14"
+          }
         },
         "lru-cache": {
           "version": "4.1.1",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
           "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "pseudomap": "1.0.2",
+            "yallist": "2.1.2"
+          }
         }
       }
     },
@@ -1781,17 +2815,53 @@
     "expand-brackets": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
-      "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s="
+      "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+      "requires": {
+        "is-posix-bracket": "0.1.1"
+      }
     },
     "expand-range": {
       "version": "1.8.2",
       "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
-      "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc="
+      "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
+      "requires": {
+        "fill-range": "2.2.3"
+      }
     },
     "express": {
       "version": "4.15.3",
       "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz",
       "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=",
+      "requires": {
+        "accepts": "1.3.3",
+        "array-flatten": "1.1.1",
+        "content-disposition": "0.5.2",
+        "content-type": "1.0.2",
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.7",
+        "depd": "1.1.0",
+        "encodeurl": "1.0.1",
+        "escape-html": "1.0.3",
+        "etag": "1.8.0",
+        "finalhandler": "1.0.3",
+        "fresh": "0.5.0",
+        "merge-descriptors": "1.0.1",
+        "methods": "1.1.2",
+        "on-finished": "2.3.0",
+        "parseurl": "1.3.1",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "1.1.4",
+        "qs": "6.4.0",
+        "range-parser": "1.2.0",
+        "send": "0.15.3",
+        "serve-static": "1.12.3",
+        "setprototypeof": "1.0.3",
+        "statuses": "1.3.1",
+        "type-is": "1.6.15",
+        "utils-merge": "1.0.0",
+        "vary": "1.1.1"
+      },
       "dependencies": {
         "array-flatten": {
           "version": "1.1.1",
@@ -1801,14 +2871,22 @@
         "debug": {
           "version": "2.6.7",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
-          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
+          "requires": {
+            "ms": "2.0.0"
+          }
         }
       }
     },
     "express-oauth-server": {
       "version": "2.0.0-b3",
       "resolved": "https://registry.npmjs.org/express-oauth-server/-/express-oauth-server-2.0.0-b3.tgz",
-      "integrity": "sha1-+Mgw2/kSkc6pJ+YdYD7C7Rr+fAw="
+      "integrity": "sha1-+Mgw2/kSkc6pJ+YdYD7C7Rr+fAw=",
+      "requires": {
+        "bluebird": "3.5.0",
+        "express": "4.15.3",
+        "oauth2-server": "3.0.0-b4"
+      }
     },
     "extend": {
       "version": "3.0.1",
@@ -1818,7 +2896,10 @@
     "extglob": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
-      "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE="
+      "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+      "requires": {
+        "is-extglob": "1.0.0"
+      }
     },
     "extsprintf": {
       "version": "1.0.2",
@@ -1829,6 +2910,12 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
       "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=",
+      "requires": {
+        "acorn": "1.2.2",
+        "foreach": "2.0.5",
+        "isarray": "0.0.1",
+        "object-keys": "1.0.11"
+      },
       "dependencies": {
         "isarray": {
           "version": "0.0.1",
@@ -1847,13 +2934,21 @@
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
       "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "1.0.5",
+        "object-assign": "4.1.1"
+      }
     },
     "file-entry-cache": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
       "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "flat-cache": "1.2.2",
+        "object-assign": "4.1.1"
+      }
     },
     "filename-regex": {
       "version": "2.0.1",
@@ -1863,7 +2958,14 @@
     "fill-range": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
-      "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM="
+      "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
+      "requires": {
+        "is-number": "2.1.0",
+        "isobject": "2.1.0",
+        "randomatic": "1.1.7",
+        "repeat-element": "1.1.2",
+        "repeat-string": "1.6.1"
+      }
     },
     "filled-array": {
       "version": "1.1.0",
@@ -1875,11 +2977,23 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz",
       "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=",
+      "requires": {
+        "debug": "2.6.7",
+        "encodeurl": "1.0.1",
+        "escape-html": "1.0.3",
+        "on-finished": "2.3.0",
+        "parseurl": "1.3.1",
+        "statuses": "1.3.1",
+        "unpipe": "1.0.0"
+      },
       "dependencies": {
         "debug": {
           "version": "2.6.7",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
-          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
+          "requires": {
+            "ms": "2.0.0"
+          }
         }
       }
     },
@@ -1887,643 +3001,130 @@
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
       "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "commondir": "1.0.1",
+        "mkdirp": "0.5.1",
+        "pkg-dir": "1.0.0"
+      }
     },
     "find-up": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
       "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
-      "dev": true
-    },
-    "flat-cache": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz",
-      "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=",
-      "dev": true
-    },
-    "flow-bin": {
-      "version": "0.37.4",
-      "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.37.4.tgz",
-      "integrity": "sha1-PY2i73RugOcw0WbgkED0GYlpt2s=",
-      "dev": true
-    },
-    "fn-name": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz",
-      "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=",
-      "dev": true
-    },
-    "follow-redirects": {
-      "version": "0.0.7",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz",
-      "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk="
-    },
-    "for-in": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
-      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
-    },
-    "for-own": {
-      "version": "0.1.5",
-      "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
-      "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4="
-    },
-    "foreach": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
-      "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
-    },
-    "forever-agent": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
-    },
-    "form-data": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
-      "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE="
-    },
-    "formatio": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz",
-      "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=",
-      "dev": true
-    },
-    "formidable": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz",
-      "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=",
-      "dev": true
-    },
-    "forwarded": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz",
-      "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M="
-    },
-    "fresh": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz",
-      "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44="
-    },
-    "from": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
-      "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
-      "dev": true
-    },
-    "fs-readdir-recursive": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz",
-      "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA="
-    },
-    "fs.realpath": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
-    },
-    "fsevents": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
-      "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
-      "optional": true,
-      "dependencies": {
-        "abbrev": {
-          "version": "1.1.0",
-          "bundled": true,
-          "optional": true
-        },
-        "ajv": {
-          "version": "4.11.8",
-          "bundled": true,
-          "optional": true
-        },
-        "ansi-regex": {
-          "version": "2.1.1",
-          "bundled": true
-        },
-        "aproba": {
-          "version": "1.1.1",
-          "bundled": true,
-          "optional": true
-        },
-        "are-we-there-yet": {
-          "version": "1.1.4",
-          "bundled": true,
-          "optional": true
-        },
-        "asn1": {
-          "version": "0.2.3",
-          "bundled": true,
-          "optional": true
-        },
-        "assert-plus": {
-          "version": "0.2.0",
-          "bundled": true,
-          "optional": true
-        },
-        "asynckit": {
-          "version": "0.4.0",
-          "bundled": true,
-          "optional": true
-        },
-        "aws-sign2": {
-          "version": "0.6.0",
-          "bundled": true,
-          "optional": true
-        },
-        "aws4": {
-          "version": "1.6.0",
-          "bundled": true,
-          "optional": true
-        },
-        "balanced-match": {
-          "version": "0.4.2",
-          "bundled": true
-        },
-        "bcrypt-pbkdf": {
-          "version": "1.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "block-stream": {
-          "version": "0.0.9",
-          "bundled": true
-        },
-        "boom": {
-          "version": "2.10.1",
-          "bundled": true
-        },
-        "brace-expansion": {
-          "version": "1.1.7",
-          "bundled": true
-        },
-        "buffer-shims": {
-          "version": "1.0.0",
-          "bundled": true
-        },
-        "caseless": {
-          "version": "0.12.0",
-          "bundled": true,
-          "optional": true
-        },
-        "co": {
-          "version": "4.6.0",
-          "bundled": true,
-          "optional": true
-        },
-        "code-point-at": {
-          "version": "1.1.0",
-          "bundled": true
-        },
-        "combined-stream": {
-          "version": "1.0.5",
-          "bundled": true
-        },
-        "concat-map": {
-          "version": "0.0.1",
-          "bundled": true
-        },
-        "console-control-strings": {
-          "version": "1.1.0",
-          "bundled": true
-        },
-        "core-util-is": {
-          "version": "1.0.2",
-          "bundled": true
-        },
-        "cryptiles": {
-          "version": "2.0.5",
-          "bundled": true,
-          "optional": true
-        },
-        "dashdash": {
-          "version": "1.14.1",
-          "bundled": true,
-          "optional": true,
-          "dependencies": {
-            "assert-plus": {
-              "version": "1.0.0",
-              "bundled": true,
-              "optional": true
-            }
-          }
-        },
-        "debug": {
-          "version": "2.6.8",
-          "bundled": true,
-          "optional": true
-        },
-        "deep-extend": {
-          "version": "0.4.2",
-          "bundled": true,
-          "optional": true
-        },
-        "delayed-stream": {
-          "version": "1.0.0",
-          "bundled": true
-        },
-        "delegates": {
-          "version": "1.0.0",
-          "bundled": true,
-          "optional": true
-        },
-        "ecc-jsbn": {
-          "version": "0.1.1",
-          "bundled": true,
-          "optional": true
-        },
-        "extend": {
-          "version": "3.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "extsprintf": {
-          "version": "1.0.2",
-          "bundled": true
-        },
-        "forever-agent": {
-          "version": "0.6.1",
-          "bundled": true,
-          "optional": true
-        },
-        "form-data": {
-          "version": "2.1.4",
-          "bundled": true,
-          "optional": true
-        },
-        "fs.realpath": {
-          "version": "1.0.0",
-          "bundled": true
-        },
-        "fstream": {
-          "version": "1.0.11",
-          "bundled": true
-        },
-        "fstream-ignore": {
-          "version": "1.0.5",
-          "bundled": true,
-          "optional": true
-        },
-        "gauge": {
-          "version": "2.7.4",
-          "bundled": true,
-          "optional": true
-        },
-        "getpass": {
-          "version": "0.1.7",
-          "bundled": true,
-          "optional": true,
-          "dependencies": {
-            "assert-plus": {
-              "version": "1.0.0",
-              "bundled": true,
-              "optional": true
-            }
-          }
-        },
-        "glob": {
-          "version": "7.1.2",
-          "bundled": true
-        },
-        "graceful-fs": {
-          "version": "4.1.11",
-          "bundled": true
-        },
-        "har-schema": {
-          "version": "1.0.5",
-          "bundled": true,
-          "optional": true
-        },
-        "har-validator": {
-          "version": "4.2.1",
-          "bundled": true,
-          "optional": true
-        },
-        "has-unicode": {
-          "version": "2.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "hawk": {
-          "version": "3.1.3",
-          "bundled": true,
-          "optional": true
-        },
-        "hoek": {
-          "version": "2.16.3",
-          "bundled": true
-        },
-        "http-signature": {
-          "version": "1.1.1",
-          "bundled": true,
-          "optional": true
-        },
-        "inflight": {
-          "version": "1.0.6",
-          "bundled": true
-        },
-        "inherits": {
-          "version": "2.0.3",
-          "bundled": true
-        },
-        "ini": {
-          "version": "1.3.4",
-          "bundled": true,
-          "optional": true
-        },
-        "is-fullwidth-code-point": {
-          "version": "1.0.0",
-          "bundled": true
-        },
-        "is-typedarray": {
-          "version": "1.0.0",
-          "bundled": true,
-          "optional": true
-        },
-        "isarray": {
-          "version": "1.0.0",
-          "bundled": true
-        },
-        "isstream": {
-          "version": "0.1.2",
-          "bundled": true,
-          "optional": true
-        },
-        "jodid25519": {
-          "version": "1.0.2",
-          "bundled": true,
-          "optional": true
-        },
-        "jsbn": {
-          "version": "0.1.1",
-          "bundled": true,
-          "optional": true
-        },
-        "json-schema": {
-          "version": "0.2.3",
-          "bundled": true,
-          "optional": true
-        },
-        "json-stable-stringify": {
-          "version": "1.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "json-stringify-safe": {
-          "version": "5.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "jsonify": {
-          "version": "0.0.0",
-          "bundled": true,
-          "optional": true
-        },
-        "jsprim": {
-          "version": "1.4.0",
-          "bundled": true,
-          "optional": true,
-          "dependencies": {
-            "assert-plus": {
-              "version": "1.0.0",
-              "bundled": true,
-              "optional": true
-            }
-          }
-        },
-        "mime-db": {
-          "version": "1.27.0",
-          "bundled": true
-        },
-        "mime-types": {
-          "version": "2.1.15",
-          "bundled": true
-        },
-        "minimatch": {
-          "version": "3.0.4",
-          "bundled": true
-        },
-        "minimist": {
-          "version": "0.0.8",
-          "bundled": true
-        },
-        "mkdirp": {
-          "version": "0.5.1",
-          "bundled": true
-        },
-        "ms": {
-          "version": "2.0.0",
-          "bundled": true,
-          "optional": true
-        },
-        "node-pre-gyp": {
-          "version": "0.6.36",
-          "bundled": true,
-          "optional": true
-        },
-        "nopt": {
-          "version": "4.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "npmlog": {
-          "version": "4.1.0",
-          "bundled": true,
-          "optional": true
-        },
-        "number-is-nan": {
-          "version": "1.0.1",
-          "bundled": true
-        },
-        "oauth-sign": {
-          "version": "0.8.2",
-          "bundled": true,
-          "optional": true
-        },
-        "object-assign": {
-          "version": "4.1.1",
-          "bundled": true,
-          "optional": true
-        },
-        "once": {
-          "version": "1.4.0",
-          "bundled": true
-        },
-        "os-homedir": {
-          "version": "1.0.2",
-          "bundled": true,
-          "optional": true
-        },
-        "os-tmpdir": {
-          "version": "1.0.2",
-          "bundled": true,
-          "optional": true
-        },
-        "osenv": {
-          "version": "0.1.4",
-          "bundled": true,
-          "optional": true
-        },
-        "path-is-absolute": {
-          "version": "1.0.1",
-          "bundled": true
-        },
-        "performance-now": {
-          "version": "0.2.0",
-          "bundled": true,
-          "optional": true
-        },
-        "process-nextick-args": {
-          "version": "1.0.7",
-          "bundled": true
-        },
-        "punycode": {
-          "version": "1.4.1",
-          "bundled": true,
-          "optional": true
-        },
-        "qs": {
-          "version": "6.4.0",
-          "bundled": true,
-          "optional": true
-        },
-        "rc": {
-          "version": "1.2.1",
-          "bundled": true,
-          "optional": true,
-          "dependencies": {
-            "minimist": {
-              "version": "1.2.0",
-              "bundled": true,
-              "optional": true
-            }
-          }
-        },
-        "readable-stream": {
-          "version": "2.2.9",
-          "bundled": true
-        },
-        "request": {
-          "version": "2.81.0",
-          "bundled": true,
-          "optional": true
-        },
-        "rimraf": {
-          "version": "2.6.1",
-          "bundled": true
-        },
-        "safe-buffer": {
-          "version": "5.0.1",
-          "bundled": true
-        },
-        "semver": {
-          "version": "5.3.0",
-          "bundled": true,
-          "optional": true
-        },
-        "set-blocking": {
-          "version": "2.0.0",
-          "bundled": true,
-          "optional": true
-        },
-        "signal-exit": {
-          "version": "3.0.2",
-          "bundled": true,
-          "optional": true
-        },
-        "sntp": {
-          "version": "1.0.9",
-          "bundled": true,
-          "optional": true
-        },
-        "sshpk": {
-          "version": "1.13.0",
-          "bundled": true,
-          "optional": true,
-          "dependencies": {
-            "assert-plus": {
-              "version": "1.0.0",
-              "bundled": true,
-              "optional": true
-            }
-          }
-        },
-        "string_decoder": {
-          "version": "1.0.1",
-          "bundled": true
-        },
-        "string-width": {
-          "version": "1.0.2",
-          "bundled": true
-        },
-        "stringstream": {
-          "version": "0.0.5",
-          "bundled": true,
-          "optional": true
-        },
-        "strip-ansi": {
-          "version": "3.0.1",
-          "bundled": true
-        },
-        "strip-json-comments": {
-          "version": "2.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "tar": {
-          "version": "2.2.1",
-          "bundled": true
-        },
-        "tar-pack": {
-          "version": "3.4.0",
-          "bundled": true,
-          "optional": true
-        },
-        "tough-cookie": {
-          "version": "2.3.2",
-          "bundled": true,
-          "optional": true
-        },
-        "tunnel-agent": {
-          "version": "0.6.0",
-          "bundled": true,
-          "optional": true
-        },
-        "tweetnacl": {
-          "version": "0.14.5",
-          "bundled": true,
-          "optional": true
-        },
-        "uid-number": {
-          "version": "0.0.6",
-          "bundled": true,
-          "optional": true
-        },
-        "util-deprecate": {
-          "version": "1.0.2",
-          "bundled": true
-        },
-        "uuid": {
-          "version": "3.0.1",
-          "bundled": true,
-          "optional": true
-        },
-        "verror": {
-          "version": "1.3.6",
-          "bundled": true,
-          "optional": true
-        },
-        "wide-align": {
-          "version": "1.1.2",
-          "bundled": true,
-          "optional": true
-        },
-        "wrappy": {
-          "version": "1.0.2",
-          "bundled": true
-        }
+      "dev": true,
+      "requires": {
+        "path-exists": "2.1.0",
+        "pinkie-promise": "2.0.1"
+      }
+    },
+    "flat-cache": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz",
+      "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=",
+      "dev": true,
+      "requires": {
+        "circular-json": "0.3.1",
+        "del": "2.2.2",
+        "graceful-fs": "4.1.11",
+        "write": "0.2.1"
+      }
+    },
+    "flow-bin": {
+      "version": "0.37.4",
+      "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.37.4.tgz",
+      "integrity": "sha1-PY2i73RugOcw0WbgkED0GYlpt2s=",
+      "dev": true
+    },
+    "fn-name": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz",
+      "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=",
+      "dev": true
+    },
+    "follow-redirects": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz",
+      "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=",
+      "requires": {
+        "debug": "2.6.8",
+        "stream-consume": "0.1.0"
+      }
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
+    },
+    "for-own": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+      "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+      "requires": {
+        "for-in": "1.0.2"
+      }
+    },
+    "foreach": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
+      "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+      "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+      "requires": {
+        "asynckit": "0.4.0",
+        "combined-stream": "1.0.5",
+        "mime-types": "2.1.15"
+      }
+    },
+    "formatio": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz",
+      "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=",
+      "dev": true,
+      "requires": {
+        "samsam": "1.1.2"
       }
     },
+    "formidable": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz",
+      "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=",
+      "dev": true
+    },
+    "forwarded": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz",
+      "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M="
+    },
+    "fresh": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz",
+      "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44="
+    },
+    "from": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
+      "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
+      "dev": true
+    },
+    "fs-readdir-recursive": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz",
+      "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA="
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
     "function-bind": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz",
@@ -2540,13 +3141,19 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
       "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-property": "1.0.2"
+      }
     },
     "get-port": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/get-port/-/get-port-2.1.0.tgz",
       "integrity": "sha1-h4P53OvR7qSVozThpqJR54iHqxo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "pinkie-promise": "2.0.1"
+      }
     },
     "get-stdin": {
       "version": "4.0.1",
@@ -2564,6 +3171,9 @@
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
       "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "requires": {
+        "assert-plus": "1.0.0"
+      },
       "dependencies": {
         "assert-plus": {
           "version": "1.0.0",
@@ -2575,22 +3185,43 @@
     "github": {
       "version": "8.2.1",
       "resolved": "https://registry.npmjs.org/github/-/github-8.2.1.tgz",
-      "integrity": "sha1-YWsiEfvNHMhjFmmu1nZT5i61OBY="
+      "integrity": "sha1-YWsiEfvNHMhjFmmu1nZT5i61OBY=",
+      "requires": {
+        "follow-redirects": "0.0.7",
+        "https-proxy-agent": "1.0.0",
+        "mime": "1.3.4",
+        "netrc": "0.1.4"
+      }
     },
     "glob": {
       "version": "7.1.2",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ=="
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "requires": {
+        "fs.realpath": "1.0.0",
+        "inflight": "1.0.6",
+        "inherits": "2.0.3",
+        "minimatch": "3.0.4",
+        "once": "1.4.0",
+        "path-is-absolute": "1.0.1"
+      }
     },
     "glob-base": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
-      "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q="
+      "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
+      "requires": {
+        "glob-parent": "2.0.0",
+        "is-glob": "2.0.1"
+      }
     },
     "glob-parent": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
-      "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg="
+      "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+      "requires": {
+        "is-glob": "2.0.1"
+      }
     },
     "globals": {
       "version": "9.18.0",
@@ -2601,13 +3232,37 @@
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
       "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "array-union": "1.0.2",
+        "glob": "7.1.2",
+        "object-assign": "4.1.1",
+        "pify": "2.3.0",
+        "pinkie-promise": "2.0.1"
+      }
     },
     "got": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
       "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "create-error-class": "3.0.2",
+        "duplexer2": "0.1.4",
+        "is-redirect": "1.0.0",
+        "is-retry-allowed": "1.1.0",
+        "is-stream": "1.1.0",
+        "lowercase-keys": "1.0.0",
+        "node-status-codes": "1.0.0",
+        "object-assign": "4.1.1",
+        "parse-json": "2.2.0",
+        "pinkie-promise": "2.0.1",
+        "read-all-stream": "3.1.0",
+        "readable-stream": "2.3.3",
+        "timed-out": "3.1.3",
+        "unzip-response": "1.0.2",
+        "url-parse-lax": "1.0.0"
+      }
     },
     "graceful-fs": {
       "version": "4.1.11",
@@ -2627,18 +3282,28 @@
     "har-validator": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
-      "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio="
+      "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+      "requires": {
+        "ajv": "4.11.8",
+        "har-schema": "1.0.5"
+      }
     },
     "has": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
       "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "function-bind": "1.1.0"
+      }
     },
     "has-ansi": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE="
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "requires": {
+        "ansi-regex": "2.1.1"
+      }
     },
     "has-color": {
       "version": "0.1.7",
@@ -2655,7 +3320,13 @@
     "hawk": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
-      "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ="
+      "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+      "requires": {
+        "boom": "2.10.1",
+        "cryptiles": "2.0.5",
+        "hoek": "2.16.3",
+        "sntp": "1.0.9"
+      }
     },
     "hoek": {
       "version": "2.16.3",
@@ -2666,6 +3337,10 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
       "integrity": "sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=",
+      "requires": {
+        "mkdirp": "0.3.0",
+        "nopt": "1.0.10"
+      },
       "dependencies": {
         "mkdirp": {
           "version": "0.3.0",
@@ -2677,7 +3352,11 @@
     "home-or-tmp": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
-      "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg="
+      "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=",
+      "requires": {
+        "os-homedir": "1.0.2",
+        "os-tmpdir": "1.0.2"
+      }
     },
     "hosted-git-info": {
       "version": "2.5.0",
@@ -2688,17 +3367,33 @@
     "http-errors": {
       "version": "1.6.1",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz",
-      "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc="
+      "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=",
+      "requires": {
+        "depd": "1.1.0",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.0.3",
+        "statuses": "1.3.1"
+      }
     },
     "http-signature": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
-      "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8="
+      "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+      "requires": {
+        "assert-plus": "0.2.0",
+        "jsprim": "1.4.0",
+        "sshpk": "1.13.1"
+      }
     },
     "https-proxy-agent": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz",
-      "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY="
+      "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=",
+      "requires": {
+        "agent-base": "2.1.1",
+        "debug": "2.6.8",
+        "extend": "3.0.1"
+      }
     },
     "iconv-lite": {
       "version": "0.4.15",
@@ -2732,7 +3427,10 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
       "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "repeating": "2.0.1"
+      }
     },
     "infinity-agent": {
       "version": "2.0.3",
@@ -2743,7 +3441,11 @@
     "inflight": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "1.4.0",
+        "wrappy": "1.0.2"
+      }
     },
     "inherits": {
       "version": "2.0.3",
@@ -2759,13 +3461,32 @@
     "inline-process-browser": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz",
-      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI="
+      "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=",
+      "requires": {
+        "falafel": "1.2.0",
+        "through2": "0.6.5"
+      }
     },
     "inquirer": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
       "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "1.4.0",
+        "ansi-regex": "2.1.1",
+        "chalk": "1.1.3",
+        "cli-cursor": "1.0.2",
+        "cli-width": "2.1.0",
+        "figures": "1.7.0",
+        "lodash": "4.17.4",
+        "readline2": "1.0.1",
+        "run-async": "0.1.0",
+        "rx-lite": "3.1.2",
+        "string-width": "1.0.2",
+        "strip-ansi": "3.0.1",
+        "through": "2.3.8"
+      }
     },
     "interpret": {
       "version": "1.0.3",
@@ -2776,7 +3497,10 @@
     "invariant": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
-      "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A="
+      "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+      "requires": {
+        "loose-envify": "1.3.1"
+      }
     },
     "ipaddr.js": {
       "version": "1.3.0",
@@ -2803,7 +3527,10 @@
     "is-binary-path": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
-      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg="
+      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+      "requires": {
+        "binary-extensions": "1.8.0"
+      }
     },
     "is-buffer": {
       "version": "1.1.5",
@@ -2814,13 +3541,19 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
       "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "builtin-modules": "1.1.1"
+      }
     },
     "is-ci": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz",
       "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "ci-info": "1.0.0"
+      }
     },
     "is-dotfile": {
       "version": "1.0.3",
@@ -2830,7 +3563,10 @@
     "is-equal-shallow": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
-      "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ="
+      "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+      "requires": {
+        "is-primitive": "2.0.0"
+      }
     },
     "is-error": {
       "version": "2.2.1",
@@ -2851,13 +3587,19 @@
     "is-finite": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
-      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko="
+      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+      "requires": {
+        "number-is-nan": "1.0.1"
+      }
     },
     "is-fullwidth-code-point": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
       "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "number-is-nan": "1.0.1"
+      }
     },
     "is-generator": {
       "version": "1.0.3",
@@ -2873,13 +3615,22 @@
     "is-glob": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
-      "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM="
+      "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+      "requires": {
+        "is-extglob": "1.0.0"
+      }
     },
     "is-my-json-valid": {
       "version": "2.16.0",
       "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
       "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "generate-function": "2.0.0",
+        "generate-object-property": "1.2.0",
+        "jsonpointer": "4.0.1",
+        "xtend": "4.0.1"
+      }
     },
     "is-npm": {
       "version": "1.0.0",
@@ -2890,7 +3641,10 @@
     "is-number": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
-      "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8="
+      "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+      "requires": {
+        "kind-of": "3.2.2"
+      }
     },
     "is-obj": {
       "version": "1.0.1",
@@ -2902,7 +3656,10 @@
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz",
       "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "symbol-observable": "0.2.4"
+      }
     },
     "is-path-cwd": {
       "version": "1.0.0",
@@ -2914,13 +3671,19 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
       "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-path-inside": "1.0.0"
+      }
     },
     "is-path-inside": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
       "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "path-is-inside": "1.0.2"
+      }
     },
     "is-plain-obj": {
       "version": "1.1.0",
@@ -2959,7 +3722,10 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
       "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "tryit": "1.0.3"
+      }
     },
     "is-retry-allowed": {
       "version": "1.1.0",
@@ -3004,7 +3770,10 @@
     "isobject": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
-      "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk="
+      "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+      "requires": {
+        "isarray": "1.0.0"
+      }
     },
     "isstream": {
       "version": "0.1.2",
@@ -3021,6 +3790,10 @@
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
       "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
       "dev": true,
+      "requires": {
+        "argparse": "1.0.9",
+        "esprima": "3.1.3"
+      },
       "dependencies": {
         "esprima": {
           "version": "3.1.3",
@@ -3049,7 +3822,10 @@
     "json-stable-stringify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
-      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8="
+      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+      "requires": {
+        "jsonify": "0.0.0"
+      }
     },
     "json-stringify-safe": {
       "version": "5.0.1",
@@ -3076,6 +3852,12 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
       "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.0.2",
+        "json-schema": "0.2.3",
+        "verror": "1.3.6"
+      },
       "dependencies": {
         "assert-plus": {
           "version": "1.0.0",
@@ -3088,30 +3870,48 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz",
       "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=",
+      "requires": {
+        "base62": "0.1.1",
+        "esprima-fb": "3001.1.0-dev-harmony-fb",
+        "source-map": "0.1.31"
+      },
       "dependencies": {
         "source-map": {
           "version": "0.1.31",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz",
-          "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE="
+          "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=",
+          "requires": {
+            "amdefine": "1.0.1"
+          }
         }
       }
     },
     "kind-of": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-      "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="
+      "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+      "requires": {
+        "is-buffer": "1.1.5"
+      }
     },
     "last-line-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz",
       "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=",
       "dev": true,
+      "requires": {
+        "through2": "2.0.3"
+      },
       "dependencies": {
         "through2": {
           "version": "2.0.3",
           "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
           "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "readable-stream": "2.3.3",
+            "xtend": "4.0.1"
+          }
         }
       }
     },
@@ -3119,7 +3919,10 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz",
       "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "package-json": "2.4.0"
+      }
     },
     "lazy-req": {
       "version": "1.1.0",
@@ -3131,24 +3934,63 @@
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
       "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "prelude-ls": "1.1.2",
+        "type-check": "0.3.2"
+      }
     },
     "lie": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/lie/-/lie-3.0.2.tgz",
-      "integrity": "sha1-/9oh17uibzd8rYZdNkmy/Izjn+o="
+      "integrity": "sha1-/9oh17uibzd8rYZdNkmy/Izjn+o=",
+      "requires": {
+        "es3ify": "0.1.4",
+        "immediate": "3.0.6",
+        "inline-process-browser": "1.0.0",
+        "unreachable-branch-transform": "0.3.0"
+      }
     },
     "lint-staged": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.0.0.tgz",
       "integrity": "sha1-wVZp9ZhhSm5oCQMD4XWnmdSODYU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "app-root-path": "2.0.1",
+        "cosmiconfig": "1.1.0",
+        "execa": "0.7.0",
+        "listr": "0.12.0",
+        "lodash.chunk": "4.2.0",
+        "minimatch": "3.0.4",
+        "npm-which": "3.0.1",
+        "p-map": "1.1.1",
+        "staged-git-files": "0.0.4"
+      }
     },
     "listr": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz",
       "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "chalk": "1.1.3",
+        "cli-truncate": "0.2.1",
+        "figures": "1.7.0",
+        "indent-string": "2.1.0",
+        "is-promise": "2.1.0",
+        "is-stream": "1.1.0",
+        "listr-silent-renderer": "1.1.1",
+        "listr-update-renderer": "0.2.0",
+        "listr-verbose-renderer": "0.4.0",
+        "log-symbols": "1.0.2",
+        "log-update": "1.0.2",
+        "ora": "0.2.3",
+        "p-map": "1.1.1",
+        "rxjs": "5.4.2",
+        "stream-to-observable": "0.1.0",
+        "strip-ansi": "3.0.1"
+      }
     },
     "listr-silent-renderer": {
       "version": "1.1.1",
@@ -3161,6 +4003,16 @@
       "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz",
       "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=",
       "dev": true,
+      "requires": {
+        "chalk": "1.1.3",
+        "cli-truncate": "0.2.1",
+        "elegant-spinner": "1.0.1",
+        "figures": "1.7.0",
+        "indent-string": "3.1.0",
+        "log-symbols": "1.0.2",
+        "log-update": "1.0.2",
+        "strip-ansi": "3.0.1"
+      },
       "dependencies": {
         "indent-string": {
           "version": "3.1.0",
@@ -3174,24 +4026,44 @@
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.0.tgz",
       "integrity": "sha1-RNwBuww0oDxXIVTU0Izemx3FYg8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "chalk": "1.1.3",
+        "cli-cursor": "1.0.2",
+        "date-fns": "1.28.5",
+        "figures": "1.7.0"
+      }
     },
     "load-json-file": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
       "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "parse-json": "2.2.0",
+        "pify": "2.3.0",
+        "pinkie-promise": "2.0.1",
+        "strip-bom": "2.0.0"
+      }
     },
     "localforage": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.5.0.tgz",
-      "integrity": "sha1-a5lOGbVmEfqF3zmS3zl6xKtm6BU="
+      "integrity": "sha1-a5lOGbVmEfqF3zmS3zl6xKtm6BU=",
+      "requires": {
+        "lie": "3.0.2"
+      }
     },
     "locate-path": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
       "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
       "dev": true,
+      "requires": {
+        "p-locate": "2.0.0",
+        "path-exists": "3.0.0"
+      },
       "dependencies": {
         "path-exists": {
           "version": "3.0.0",
@@ -3210,7 +4082,11 @@
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
       "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "lodash._basecopy": "3.0.1",
+        "lodash.keys": "3.1.2"
+      }
     },
     "lodash._basecopy": {
       "version": "3.0.1",
@@ -3228,7 +4104,12 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz",
       "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "lodash._bindcallback": "3.0.1",
+        "lodash._isiterateecall": "3.0.9",
+        "lodash.restparam": "3.6.1"
+      }
     },
     "lodash._getnative": {
       "version": "3.9.1",
@@ -3246,7 +4127,12 @@
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz",
       "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "lodash._baseassign": "3.2.0",
+        "lodash._createassigner": "3.1.1",
+        "lodash.keys": "3.1.2"
+      }
     },
     "lodash.chunk": {
       "version": "4.2.0",
@@ -3270,7 +4156,11 @@
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz",
       "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "lodash.assign": "3.2.0",
+        "lodash.restparam": "3.6.1"
+      }
     },
     "lodash.difference": {
       "version": "4.5.0",
@@ -3306,7 +4196,12 @@
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
       "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "lodash._getnative": "3.9.1",
+        "lodash.isarguments": "3.1.0",
+        "lodash.isarray": "3.0.4"
+      }
     },
     "lodash.restparam": {
       "version": "3.6.1",
@@ -3318,13 +4213,20 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
       "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "chalk": "1.1.3"
+      }
     },
     "log-update": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz",
       "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "1.4.0",
+        "cli-cursor": "1.0.2"
+      }
     },
     "lolex": {
       "version": "1.3.2",
@@ -3335,13 +4237,20 @@
     "loose-envify": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
-      "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg="
+      "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+      "requires": {
+        "js-tokens": "3.0.2"
+      }
     },
     "loud-rejection": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
       "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "currently-unhandled": "0.4.1",
+        "signal-exit": "3.0.2"
+      }
     },
     "lowercase-keys": {
       "version": "1.0.0",
@@ -3357,7 +4266,10 @@
     "lru-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
-      "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM="
+      "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
+      "requires": {
+        "es5-ext": "0.10.23"
+      }
     },
     "map-obj": {
       "version": "1.0.1",
@@ -3375,7 +4287,10 @@
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/matcher/-/matcher-0.1.2.tgz",
       "integrity": "sha1-7yDL3mTCTFDMYa9bg+4LG4/wAQE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "1.0.5"
+      }
     },
     "max-timeout": {
       "version": "1.0.0",
@@ -3387,7 +4302,10 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz",
       "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "md5-o-matic": "0.1.1"
+      }
     },
     "md5-o-matic": {
       "version": "0.1.1",
@@ -3403,13 +4321,35 @@
     "memoizee": {
       "version": "0.4.5",
       "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.5.tgz",
-      "integrity": "sha1-G8PqHkvgVt1HXVIZede+PV5bIcg="
+      "integrity": "sha1-G8PqHkvgVt1HXVIZede+PV5bIcg=",
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.23",
+        "es6-weak-map": "2.0.2",
+        "event-emitter": "0.3.5",
+        "is-promise": "2.1.0",
+        "lru-queue": "0.1.0",
+        "next-tick": "1.0.0",
+        "timers-ext": "0.1.2"
+      }
     },
     "meow": {
       "version": "3.7.0",
       "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
       "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
       "dev": true,
+      "requires": {
+        "camelcase-keys": "2.1.0",
+        "decamelize": "1.2.0",
+        "loud-rejection": "1.6.0",
+        "map-obj": "1.0.1",
+        "minimist": "1.2.0",
+        "normalize-package-data": "2.4.0",
+        "object-assign": "4.1.1",
+        "read-pkg-up": "1.0.1",
+        "redent": "1.0.0",
+        "trim-newlines": "1.0.0"
+      },
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
@@ -3432,7 +4372,22 @@
     "micromatch": {
       "version": "2.3.11",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
-      "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU="
+      "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+      "requires": {
+        "arr-diff": "2.0.0",
+        "array-unique": "0.2.1",
+        "braces": "1.8.5",
+        "expand-brackets": "0.1.5",
+        "extglob": "0.3.2",
+        "filename-regex": "2.0.1",
+        "is-extglob": "1.0.0",
+        "is-glob": "2.0.1",
+        "kind-of": "3.2.2",
+        "normalize-path": "2.1.1",
+        "object.omit": "2.0.1",
+        "parse-glob": "3.0.4",
+        "regex-cache": "0.4.3"
+      }
     },
     "mime": {
       "version": "1.3.4",
@@ -3447,7 +4402,10 @@
     "mime-types": {
       "version": "2.1.15",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
-      "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0="
+      "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
+      "requires": {
+        "mime-db": "1.27.0"
+      }
     },
     "minimalistic-assert": {
       "version": "1.0.0",
@@ -3457,7 +4415,10 @@
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA=="
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "1.1.8"
+      }
     },
     "minimist": {
       "version": "0.0.8",
@@ -3467,7 +4428,10 @@
     "mkdirp": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "requires": {
+        "minimist": "0.0.8"
+      }
     },
     "moment": {
       "version": "2.18.1",
@@ -3478,18 +4442,36 @@
       "version": "2.2.29",
       "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.29.tgz",
       "integrity": "sha512-MrQvIsN6zN80I4hdFo8w46w51cIqD2FJBGsUfApX9GmjXA1aCclEAJbOHaQWjCtabeWq57S3ECzqEKg/9bdBhA==",
+      "requires": {
+        "es6-promise": "3.2.1",
+        "mongodb-core": "2.1.13",
+        "readable-stream": "2.2.7"
+      },
       "dependencies": {
         "readable-stream": {
           "version": "2.2.7",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz",
-          "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE="
+          "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=",
+          "requires": {
+            "buffer-shims": "1.0.0",
+            "core-util-is": "1.0.2",
+            "inherits": "2.0.3",
+            "isarray": "1.0.0",
+            "process-nextick-args": "1.0.7",
+            "string_decoder": "1.0.3",
+            "util-deprecate": "1.0.2"
+          }
         }
       }
     },
     "mongodb-core": {
       "version": "2.1.13",
       "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.13.tgz",
-      "integrity": "sha512-mbcvqLLZwVcpTrsfBDY3hRNk2SDNJWOvKKxFJSc0pnUBhYojymBc/L0THfQsWwKJrkb2nIXSjfFll1mG/I5OqQ=="
+      "integrity": "sha512-mbcvqLLZwVcpTrsfBDY3hRNk2SDNJWOvKKxFJSc0pnUBhYojymBc/L0THfQsWwKJrkb2nIXSjfFll1mG/I5OqQ==",
+      "requires": {
+        "bson": "1.0.4",
+        "require_optional": "1.0.1"
+      }
     },
     "moniker": {
       "version": "0.1.2",
@@ -3499,7 +4481,14 @@
     "morgan": {
       "version": "1.8.2",
       "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz",
-      "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc="
+      "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=",
+      "requires": {
+        "basic-auth": "1.1.0",
+        "debug": "2.6.8",
+        "depd": "1.1.0",
+        "on-finished": "2.3.0",
+        "on-headers": "1.0.1"
+      }
     },
     "ms": {
       "version": "2.0.0",
@@ -3510,6 +4499,16 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz",
       "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=",
+      "requires": {
+        "append-field": "0.1.0",
+        "busboy": "0.2.14",
+        "concat-stream": "1.6.0",
+        "mkdirp": "0.5.1",
+        "object-assign": "3.0.0",
+        "on-finished": "2.3.0",
+        "type-is": "1.6.15",
+        "xtend": "4.0.1"
+      },
       "dependencies": {
         "object-assign": {
           "version": "3.0.0",
@@ -3522,7 +4521,13 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz",
       "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "array-differ": "1.0.0",
+        "array-union": "1.0.2",
+        "arrify": "1.0.1",
+        "minimatch": "3.0.4"
+      }
     },
     "mute-stream": {
       "version": "0.0.5",
@@ -3535,18 +4540,33 @@
       "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
       "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
       "optional": true,
+      "requires": {
+        "mkdirp": "0.5.1",
+        "ncp": "2.0.0",
+        "rimraf": "2.4.5"
+      },
       "dependencies": {
         "glob": {
           "version": "6.0.4",
           "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
           "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
-          "optional": true
+          "optional": true,
+          "requires": {
+            "inflight": "1.0.6",
+            "inherits": "2.0.3",
+            "minimatch": "3.0.4",
+            "once": "1.4.0",
+            "path-is-absolute": "1.0.1"
+          }
         },
         "rimraf": {
           "version": "2.4.5",
           "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
           "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
-          "optional": true
+          "optional": true,
+          "requires": {
+            "glob": "6.0.4"
+          }
         }
       }
     },
@@ -3572,11 +4592,21 @@
       "version": "3.0.6",
       "resolved": "https://registry.npmjs.org/nedb-core/-/nedb-core-3.0.6.tgz",
       "integrity": "sha1-4BrQ8iciF/UQkY2YY5MwPotMjTI=",
+      "requires": {
+        "async": "2.5.0",
+        "is": "3.2.1",
+        "localforage": "1.5.0",
+        "mkdirp": "0.5.1",
+        "underscore": "1.8.3"
+      },
       "dependencies": {
         "mkdirp": {
           "version": "0.5.1",
           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM="
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+          "requires": {
+            "minimist": "0.0.8"
+          }
         }
       }
     },
@@ -3589,7 +4619,10 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz",
       "integrity": "sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "inherits": "2.0.3"
+      }
     },
     "netrc": {
       "version": "0.1.4",
@@ -3604,7 +4637,10 @@
     "node-rsa": {
       "version": "0.4.2",
       "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
-      "integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA="
+      "integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=",
+      "requires": {
+        "asn1": "0.2.3"
+      }
     },
     "node-status-codes": {
       "version": "1.0.0",
@@ -3617,18 +4653,52 @@
       "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.11.0.tgz",
       "integrity": "sha1-ImxWK9KnsT09dRi0mtSCijYj0Gw=",
       "dev": true,
+      "requires": {
+        "chokidar": "1.7.0",
+        "debug": "2.6.8",
+        "es6-promise": "3.2.1",
+        "ignore-by-default": "1.0.1",
+        "lodash.defaults": "3.1.2",
+        "minimatch": "3.0.4",
+        "ps-tree": "1.1.0",
+        "touch": "1.0.0",
+        "undefsafe": "0.0.3",
+        "update-notifier": "0.5.0"
+      },
       "dependencies": {
         "configstore": {
           "version": "1.4.0",
           "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz",
           "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "graceful-fs": "4.1.11",
+            "mkdirp": "0.5.1",
+            "object-assign": "4.1.1",
+            "os-tmpdir": "1.0.2",
+            "osenv": "0.1.4",
+            "uuid": "2.0.3",
+            "write-file-atomic": "1.3.4",
+            "xdg-basedir": "2.0.0"
+          }
         },
         "got": {
           "version": "3.3.1",
           "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz",
           "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=",
           "dev": true,
+          "requires": {
+            "duplexify": "3.5.0",
+            "infinity-agent": "2.0.3",
+            "is-redirect": "1.0.0",
+            "is-stream": "1.1.0",
+            "lowercase-keys": "1.0.0",
+            "nested-error-stacks": "1.0.2",
+            "object-assign": "3.0.0",
+            "prepend-http": "1.0.4",
+            "read-all-stream": "3.1.0",
+            "timed-out": "2.0.0"
+          },
           "dependencies": {
             "object-assign": {
               "version": "3.0.0",
@@ -3642,19 +4712,29 @@
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-1.0.1.tgz",
           "integrity": "sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "package-json": "1.2.0"
+          }
         },
         "package-json": {
           "version": "1.2.0",
           "resolved": "https://registry.npmjs.org/package-json/-/package-json-1.2.0.tgz",
           "integrity": "sha1-yOysCUInzfdqMWh07QXifMk5oOA=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "got": "3.3.1",
+            "registry-url": "3.1.0"
+          }
         },
         "repeating": {
           "version": "1.1.3",
           "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz",
           "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "is-finite": "1.0.2"
+          }
         },
         "timed-out": {
           "version": "2.0.0",
@@ -3666,7 +4746,16 @@
           "version": "0.5.0",
           "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-0.5.0.tgz",
           "integrity": "sha1-B7XcIGazYnqztPUwEw9+3doHpMw=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "chalk": "1.1.3",
+            "configstore": "1.4.0",
+            "is-npm": "1.0.0",
+            "latest-version": "1.0.1",
+            "repeating": "1.1.3",
+            "semver-diff": "2.1.0",
+            "string-length": "1.0.1"
+          }
         },
         "uuid": {
           "version": "2.0.3",
@@ -3679,36 +4768,59 @@
     "nopt": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
-      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4="
+      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+      "requires": {
+        "abbrev": "1.1.0"
+      }
     },
     "normalize-package-data": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
       "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "2.5.0",
+        "is-builtin-module": "1.0.0",
+        "semver": "5.3.0",
+        "validate-npm-package-license": "3.0.1"
+      }
     },
     "normalize-path": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-      "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk="
+      "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+      "requires": {
+        "remove-trailing-separator": "1.0.2"
+      }
     },
     "npm-path": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz",
       "integrity": "sha1-Fc/04ciaONp39W9gVbJPl137K74=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "which": "1.2.14"
+      }
     },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
       "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "path-key": "2.0.1"
+      }
     },
     "npm-which": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
       "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "commander": "2.11.0",
+        "npm-path": "2.0.3",
+        "which": "1.2.14"
+      }
     },
     "nullthrows": {
       "version": "1.0.0",
@@ -3728,7 +4840,15 @@
     "oauth2-server": {
       "version": "3.0.0-b4",
       "resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.0-b4.tgz",
-      "integrity": "sha1-9915nX+JvJYR3YCvOd9SFECFa3M="
+      "integrity": "sha1-9915nX+JvJYR3YCvOd9SFECFa3M=",
+      "requires": {
+        "basic-auth": "1.1.0",
+        "bluebird": "3.5.0",
+        "lodash": "4.17.4",
+        "promisify-any": "2.0.1",
+        "statuses": "1.3.1",
+        "type-is": "1.6.15"
+      }
     },
     "object-assign": {
       "version": "4.1.1",
@@ -3748,18 +4868,29 @@
     "object.omit": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
-      "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo="
+      "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
+      "requires": {
+        "for-own": "0.1.5",
+        "is-extendable": "0.1.1"
+      }
     },
     "observable-to-promise": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-0.4.0.tgz",
       "integrity": "sha1-KK/nFkUwjy1B1x9HrT/s4aN35Ss=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-observable": "0.2.0",
+        "symbol-observable": "0.2.4"
+      }
     },
     "on-finished": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
-      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc="
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
     },
     "on-headers": {
       "version": "1.0.1",
@@ -3769,7 +4900,10 @@
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1.0.2"
+      }
     },
     "onetime": {
       "version": "1.1.0",
@@ -3781,19 +4915,36 @@
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/option-chain/-/option-chain-0.1.1.tgz",
       "integrity": "sha1-6bgR4AbxwPVIAvKClb/Ilw+Nz70=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "object-assign": "4.1.1"
+      }
     },
     "optionator": {
       "version": "0.8.2",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
       "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "deep-is": "0.1.3",
+        "fast-levenshtein": "2.0.6",
+        "levn": "0.3.0",
+        "prelude-ls": "1.1.2",
+        "type-check": "0.3.2",
+        "wordwrap": "1.0.0"
+      }
     },
     "ora": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
       "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "chalk": "1.1.3",
+        "cli-cursor": "1.0.2",
+        "cli-spinners": "0.1.2",
+        "object-assign": "4.1.1"
+      }
     },
     "os-homedir": {
       "version": "1.0.2",
@@ -3815,12 +4966,21 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
       "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "os-homedir": "1.0.2",
+        "os-tmpdir": "1.0.2"
+      }
     },
     "output-file-sync": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz",
-      "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY="
+      "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=",
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "mkdirp": "0.5.1",
+        "object-assign": "4.1.1"
+      }
     },
     "p-finally": {
       "version": "1.0.0",
@@ -3838,7 +4998,10 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
       "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "p-limit": "1.1.0"
+      }
     },
     "p-map": {
       "version": "1.1.1",
@@ -3850,24 +5013,42 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-1.2.0.tgz",
       "integrity": "sha1-AD5WzVe3NqbtYRTMK4FUJnJ3DkQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "md5-hex": "1.3.0"
+      }
     },
     "package-json": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
       "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "got": "5.7.1",
+        "registry-auth-token": "3.3.1",
+        "registry-url": "3.1.0",
+        "semver": "5.3.0"
+      }
     },
     "parse-glob": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
-      "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw="
+      "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
+      "requires": {
+        "glob-base": "0.3.0",
+        "is-dotfile": "1.0.3",
+        "is-extglob": "1.0.0",
+        "is-glob": "2.0.1"
+      }
     },
     "parse-json": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
       "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "error-ex": "1.3.1"
+      }
     },
     "parse-ms": {
       "version": "1.0.1",
@@ -3884,7 +5065,10 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
       "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "pinkie-promise": "2.0.1"
+      }
     },
     "path-is-absolute": {
       "version": "1.0.1",
@@ -3918,13 +5102,21 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
       "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "pify": "2.3.0",
+        "pinkie-promise": "2.0.1"
+      }
     },
     "pause-stream": {
       "version": "0.0.11",
       "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
       "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "through": "2.3.8"
+      }
     },
     "performance-now": {
       "version": "0.2.0",
@@ -3947,25 +5139,40 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
       "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "pinkie": "2.0.4"
+      }
     },
     "pkg-conf": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz",
       "integrity": "sha1-N45W1v0T6Iv7b0ol33qD+qvduls=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "find-up": "1.1.2",
+        "load-json-file": "1.1.0",
+        "object-assign": "4.1.1",
+        "symbol": "0.2.3"
+      }
     },
     "pkg-dir": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
       "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "find-up": "1.1.2"
+      }
     },
     "plur": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz",
       "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "irregular-plurals": "1.3.0"
+      }
     },
     "pluralize": {
       "version": "1.2.1",
@@ -3977,19 +5184,31 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/power-assert-context-formatter/-/power-assert-context-formatter-1.1.1.tgz",
       "integrity": "sha1-7bo1LT7YpgMRTWZyZazOYNaJzN8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "core-js": "2.4.1",
+        "power-assert-context-traversal": "1.1.1"
+      }
     },
     "power-assert-context-traversal": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/power-assert-context-traversal/-/power-assert-context-traversal-1.1.1.tgz",
       "integrity": "sha1-iMq8oNE7Y1nwfT0+ivppkmRXftk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "core-js": "2.4.1",
+        "estraverse": "4.2.0"
+      }
     },
     "power-assert-renderer-assertion": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/power-assert-renderer-assertion/-/power-assert-renderer-assertion-1.1.1.tgz",
       "integrity": "sha1-y/wOd+AIao+Wrz8djme57n4ozpg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "power-assert-renderer-base": "1.1.1",
+        "power-assert-util-string-width": "1.1.1"
+      }
     },
     "power-assert-renderer-base": {
       "version": "1.1.1",
@@ -4001,37 +5220,64 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/power-assert-renderer-diagram/-/power-assert-renderer-diagram-1.1.2.tgz",
       "integrity": "sha1-ZV+PcRk1qbbVQbhjJ2VHF8Y3qYY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "core-js": "2.4.1",
+        "power-assert-renderer-base": "1.1.1",
+        "power-assert-util-string-width": "1.1.1",
+        "stringifier": "1.3.0"
+      }
     },
     "power-assert-renderer-succinct": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/power-assert-renderer-succinct/-/power-assert-renderer-succinct-1.1.1.tgz",
       "integrity": "sha1-wqRosjgiq9b4Diq6UyI0ewnfR24=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "core-js": "2.4.1",
+        "power-assert-renderer-diagram": "1.1.2"
+      }
     },
     "power-assert-util-string-width": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/power-assert-util-string-width/-/power-assert-util-string-width-1.1.1.tgz",
       "integrity": "sha1-vmWet5N/3S5smncmjar2S9W3xZI=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "eastasianwidth": "0.1.1"
+      }
     },
     "pre-commit": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz",
       "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=",
       "dev": true,
+      "requires": {
+        "cross-spawn": "5.1.0",
+        "spawn-sync": "1.0.15",
+        "which": "1.2.14"
+      },
       "dependencies": {
         "cross-spawn": {
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
           "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "lru-cache": "4.1.1",
+            "shebang-command": "1.2.0",
+            "which": "1.2.14"
+          }
         },
         "lru-cache": {
           "version": "4.1.1",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
           "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "pseudomap": "1.0.2",
+            "yallist": "2.1.2"
+          }
         }
       }
     },
@@ -4063,6 +5309,11 @@
       "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz",
       "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=",
       "dev": true,
+      "requires": {
+        "is-finite": "1.0.2",
+        "parse-ms": "1.0.1",
+        "plur": "1.0.0"
+      },
       "dependencies": {
         "plur": {
           "version": "1.0.0",
@@ -4092,6 +5343,11 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/promisify-any/-/promisify-any-2.0.1.tgz",
       "integrity": "sha1-QD4AqIE/F1JCq1D+M6afjuzkcwU=",
+      "requires": {
+        "bluebird": "2.11.0",
+        "co-bluebird": "1.1.0",
+        "is-generator": "1.0.3"
+      },
       "dependencies": {
         "bluebird": {
           "version": "2.11.0",
@@ -4103,13 +5359,20 @@
     "proxy-addr": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz",
-      "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM="
+      "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=",
+      "requires": {
+        "forwarded": "0.1.0",
+        "ipaddr.js": "1.3.0"
+      }
     },
     "ps-tree": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
       "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "event-stream": "3.3.4"
+      }
     },
     "pseudomap": {
       "version": "1.0.2",
@@ -4131,23 +5394,36 @@
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
       "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
+      "requires": {
+        "is-number": "3.0.0",
+        "kind-of": "4.0.0"
+      },
       "dependencies": {
         "is-number": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
           "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+          "requires": {
+            "kind-of": "3.2.2"
+          },
           "dependencies": {
             "kind-of": {
               "version": "3.2.2",
               "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="
+              "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+              "requires": {
+                "is-buffer": "1.1.5"
+              }
             }
           }
         },
         "kind-of": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
-          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc="
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "requires": {
+            "is-buffer": "1.1.5"
+          }
         }
       }
     },
@@ -4159,13 +5435,24 @@
     "raw-body": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz",
-      "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y="
+      "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=",
+      "requires": {
+        "bytes": "2.4.0",
+        "iconv-lite": "0.4.15",
+        "unpipe": "1.0.0"
+      }
     },
     "rc": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz",
       "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=",
       "dev": true,
+      "requires": {
+        "deep-extend": "0.4.2",
+        "ini": "1.3.4",
+        "minimist": "1.2.0",
+        "strip-json-comments": "2.0.1"
+      },
       "dependencies": {
         "minimist": {
           "version": "1.2.0",
@@ -4179,40 +5466,79 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz",
       "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "pinkie-promise": "2.0.1",
+        "readable-stream": "2.3.3"
+      }
     },
     "read-pkg": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
       "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "load-json-file": "1.1.0",
+        "normalize-package-data": "2.4.0",
+        "path-type": "1.1.0"
+      }
     },
     "read-pkg-up": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
       "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "find-up": "1.1.2",
+        "read-pkg": "1.1.0"
+      }
     },
     "readable-stream": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
-      "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ=="
+      "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+      "requires": {
+        "core-util-is": "1.0.2",
+        "inherits": "2.0.3",
+        "isarray": "1.0.0",
+        "process-nextick-args": "1.0.7",
+        "safe-buffer": "5.1.1",
+        "string_decoder": "1.0.3",
+        "util-deprecate": "1.0.2"
+      }
     },
     "readdirp": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
-      "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg="
+      "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "minimatch": "3.0.4",
+        "readable-stream": "2.3.3",
+        "set-immediate-shim": "1.0.1"
+      }
     },
     "readline2": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
       "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "code-point-at": "1.1.0",
+        "is-fullwidth-code-point": "1.0.0",
+        "mute-stream": "0.0.5"
+      }
     },
     "recast": {
       "version": "0.10.43",
       "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz",
       "integrity": "sha1-uV1Q9tYHYaX2JS4V2AZ4FoSRzn8=",
+      "requires": {
+        "ast-types": "0.8.15",
+        "esprima-fb": "15001.1001.0-dev-harmony-fb",
+        "private": "0.1.7",
+        "source-map": "0.5.6"
+      },
       "dependencies": {
         "esprima-fb": {
           "version": "15001.1001.0-dev-harmony-fb",
@@ -4225,13 +5551,20 @@
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
       "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "resolve": "1.3.3"
+      }
     },
     "redent": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
       "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "indent-string": "2.1.0",
+        "strip-indent": "1.0.1"
+      }
     },
     "regenerate": {
       "version": "1.3.2",
@@ -4248,30 +5581,51 @@
       "version": "0.9.11",
       "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz",
       "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "babel-runtime": "6.23.0",
+        "babel-types": "6.25.0",
+        "private": "0.1.7"
+      }
     },
     "regex-cache": {
       "version": "0.4.3",
       "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz",
-      "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU="
+      "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=",
+      "requires": {
+        "is-equal-shallow": "0.1.3",
+        "is-primitive": "2.0.0"
+      }
     },
     "regexpu-core": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
       "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "regenerate": "1.3.2",
+        "regjsgen": "0.2.0",
+        "regjsparser": "0.1.5"
+      }
     },
     "registry-auth-token": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz",
       "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "rc": "1.2.1",
+        "safe-buffer": "5.1.1"
+      }
     },
     "registry-url": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
       "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "rc": "1.2.1"
+      }
     },
     "regjsgen": {
       "version": "0.2.0",
@@ -4284,6 +5638,9 @@
       "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
       "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
       "dev": true,
+      "requires": {
+        "jsesc": "0.5.0"
+      },
       "dependencies": {
         "jsesc": {
           "version": "0.5.0",
@@ -4311,17 +5668,48 @@
     "repeating": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
-      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo="
+      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+      "requires": {
+        "is-finite": "1.0.2"
+      }
     },
     "request": {
       "version": "2.81.0",
       "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
-      "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA="
+      "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+      "requires": {
+        "aws-sign2": "0.6.0",
+        "aws4": "1.6.0",
+        "caseless": "0.12.0",
+        "combined-stream": "1.0.5",
+        "extend": "3.0.1",
+        "forever-agent": "0.6.1",
+        "form-data": "2.1.4",
+        "har-validator": "4.2.1",
+        "hawk": "3.1.3",
+        "http-signature": "1.1.1",
+        "is-typedarray": "1.0.0",
+        "isstream": "0.1.2",
+        "json-stringify-safe": "5.0.1",
+        "mime-types": "2.1.15",
+        "oauth-sign": "0.8.2",
+        "performance-now": "0.2.0",
+        "qs": "6.4.0",
+        "safe-buffer": "5.1.1",
+        "stringstream": "0.0.5",
+        "tough-cookie": "2.3.2",
+        "tunnel-agent": "0.6.0",
+        "uuid": "3.1.0"
+      }
     },
     "require_optional": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
-      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g=="
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "requires": {
+        "resolve-from": "2.0.0",
+        "semver": "5.3.0"
+      }
     },
     "require-from-string": {
       "version": "1.2.1",
@@ -4340,6 +5728,10 @@
       "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
       "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
       "dev": true,
+      "requires": {
+        "caller-path": "0.1.0",
+        "resolve-from": "1.0.1"
+      },
       "dependencies": {
         "resolve-from": {
           "version": "1.0.1",
@@ -4353,13 +5745,19 @@
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
       "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "path-parse": "1.0.5"
+      }
     },
     "resolve-cwd": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-1.0.0.tgz",
       "integrity": "sha1-Tq7qQe0EDRcCRX32SkKysH0kb58=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "resolve-from": "2.0.0"
+      }
     },
     "resolve-from": {
       "version": "2.0.0",
@@ -4370,28 +5768,45 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
       "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "exit-hook": "1.1.1",
+        "onetime": "1.1.0"
+      }
     },
     "resumer": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz",
-      "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k="
+      "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=",
+      "requires": {
+        "through": "2.3.8"
+      }
     },
     "rimraf": {
       "version": "2.6.1",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
-      "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0="
+      "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
+      "requires": {
+        "glob": "7.1.2"
+      }
     },
     "rmfr": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/rmfr/-/rmfr-1.0.3.tgz",
-      "integrity": "sha1-QrN4U6gnso7/k7AYc3R3GnbpkHk="
+      "integrity": "sha1-QrN4U6gnso7/k7AYc3R3GnbpkHk=",
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "rimraf": "2.6.1"
+      }
     },
     "run-async": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
       "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "once": "1.4.0"
+      }
     },
     "rx-lite": {
       "version": "3.1.2",
@@ -4404,6 +5819,9 @@
       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz",
       "integrity": "sha1-KjI2/L8D31e64G/Wly/ZnlwI/Pc=",
       "dev": true,
+      "requires": {
+        "symbol-observable": "1.0.4"
+      },
       "dependencies": {
         "symbol-observable": {
           "version": "1.0.4",
@@ -4439,24 +5857,51 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
       "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "semver": "5.3.0"
+      }
     },
     "send": {
       "version": "0.15.3",
       "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz",
       "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=",
+      "requires": {
+        "debug": "2.6.7",
+        "depd": "1.1.0",
+        "destroy": "1.0.4",
+        "encodeurl": "1.0.1",
+        "escape-html": "1.0.3",
+        "etag": "1.8.0",
+        "fresh": "0.5.0",
+        "http-errors": "1.6.1",
+        "mime": "1.3.4",
+        "ms": "2.0.0",
+        "on-finished": "2.3.0",
+        "range-parser": "1.2.0",
+        "statuses": "1.3.1"
+      },
       "dependencies": {
         "debug": {
           "version": "2.6.7",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
-          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
+          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
+          "requires": {
+            "ms": "2.0.0"
+          }
         }
       }
     },
     "serve-static": {
       "version": "1.12.3",
       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz",
-      "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI="
+      "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=",
+      "requires": {
+        "encodeurl": "1.0.1",
+        "escape-html": "1.0.3",
+        "parseurl": "1.3.1",
+        "send": "0.15.3"
+      }
     },
     "set-immediate-shim": {
       "version": "1.0.1",
@@ -4472,7 +5917,10 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
       "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "shebang-regex": "1.0.0"
+      }
     },
     "shebang-regex": {
       "version": "1.0.0",
@@ -4484,7 +5932,12 @@
       "version": "0.7.8",
       "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz",
       "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "glob": "7.1.2",
+        "interpret": "1.0.3",
+        "rechoir": "0.6.2"
+      }
     },
     "sigmund": {
       "version": "1.0.1",
@@ -4501,7 +5954,13 @@
       "version": "1.17.7",
       "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz",
       "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "formatio": "1.1.1",
+        "lolex": "1.3.2",
+        "samsam": "1.1.2",
+        "util": "0.10.3"
+      }
     },
     "slash": {
       "version": "1.0.0",
@@ -4523,13 +5982,19 @@
     "sntp": {
       "version": "1.0.9",
       "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
-      "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg="
+      "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+      "requires": {
+        "hoek": "2.16.3"
+      }
     },
     "sort-keys": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
       "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-plain-obj": "1.1.0"
+      }
     },
     "source-map": {
       "version": "0.5.6",
@@ -4539,22 +6004,53 @@
     "source-map-support": {
       "version": "0.4.15",
       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz",
-      "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E="
+      "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=",
+      "requires": {
+        "source-map": "0.5.6"
+      }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#22ef7a1618ddeabd410463739edda477e10a42b0"
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#22ef7a1618ddeabd410463739edda477e10a42b0",
+      "requires": {
+        "babel-plugin-transform-decorators": "6.24.1",
+        "binary-version-reader": "0.5.1",
+        "buffer-crc32": "0.2.13",
+        "bunyan": "1.8.10",
+        "chalk": "1.1.3",
+        "coap-packet": "0.1.14",
+        "compact-array": "0.0.1",
+        "constitute": "1.6.2",
+        "ec-key": "0.0.2",
+        "github": "8.2.1",
+        "hogan.js": "3.0.2",
+        "memoizee": "0.4.5",
+        "mkdirp": "0.5.1",
+        "moment": "2.18.1",
+        "moniker": "0.1.2",
+        "node-rsa": "0.4.2",
+        "nullthrows": "1.0.0",
+        "request": "2.81.0",
+        "uuid": "3.1.0"
+      }
     },
     "spawn-sync": {
       "version": "1.0.15",
       "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
       "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "concat-stream": "1.6.0",
+        "os-shim": "0.1.3"
+      }
     },
     "spdx-correct": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
       "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "spdx-license-ids": "1.2.2"
+      }
     },
     "spdx-expression-parse": {
       "version": "1.0.4",
@@ -4572,7 +6068,10 @@
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
       "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "through": "2.3.8"
+      }
     },
     "sprintf-js": {
       "version": "1.0.3",
@@ -4584,6 +6083,16 @@
       "version": "1.13.1",
       "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
       "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
+      "requires": {
+        "asn1": "0.2.3",
+        "assert-plus": "1.0.0",
+        "bcrypt-pbkdf": "1.0.1",
+        "dashdash": "1.14.1",
+        "ecc-jsbn": "0.1.1",
+        "getpass": "0.1.7",
+        "jsbn": "0.1.1",
+        "tweetnacl": "0.14.5"
+      },
       "dependencies": {
         "assert-plus": {
           "version": "1.0.0",
@@ -4613,7 +6122,10 @@
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
       "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "duplexer": "0.1.1"
+      }
     },
     "stream-consume": {
       "version": "0.1.0",
@@ -4640,25 +6152,41 @@
     "string_decoder": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
-      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ=="
+      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+      "requires": {
+        "safe-buffer": "5.1.1"
+      }
     },
     "string-length": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
       "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "strip-ansi": "3.0.1"
+      }
     },
     "string-width": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
       "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "code-point-at": "1.1.0",
+        "is-fullwidth-code-point": "1.0.0",
+        "strip-ansi": "3.0.1"
+      }
     },
     "stringifier": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/stringifier/-/stringifier-1.3.0.tgz",
       "integrity": "sha1-3vGDQvaTPbDy2/yaoCF1tEjBeVk=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "core-js": "2.4.1",
+        "traverse": "0.6.6",
+        "type-name": "2.0.2"
+      }
     },
     "stringstream": {
       "version": "0.0.5",
@@ -4668,13 +6196,19 @@
     "strip-ansi": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8="
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "requires": {
+        "ansi-regex": "2.1.1"
+      }
     },
     "strip-bom": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
       "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "is-utf8": "0.2.1"
+      }
     },
     "strip-eof": {
       "version": "1.0.0",
@@ -4686,7 +6220,10 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
       "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "get-stdin": "4.0.1"
+      }
     },
     "strip-json-comments": {
       "version": "2.0.1",
@@ -4699,6 +6236,18 @@
       "resolved": "https://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz",
       "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
       "dev": true,
+      "requires": {
+        "component-emitter": "1.2.1",
+        "cookiejar": "2.1.1",
+        "debug": "2.6.8",
+        "extend": "3.0.1",
+        "form-data": "1.0.0-rc4",
+        "formidable": "1.1.1",
+        "methods": "1.1.2",
+        "mime": "1.3.4",
+        "qs": "6.4.0",
+        "readable-stream": "2.3.3"
+      },
       "dependencies": {
         "async": {
           "version": "1.5.2",
@@ -4710,7 +6259,12 @@
           "version": "1.0.0-rc4",
           "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
           "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "async": "1.5.2",
+            "combined-stream": "1.0.5",
+            "mime-types": "2.1.15"
+          }
         }
       }
     },
@@ -4718,7 +6272,11 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/supertest/-/supertest-2.0.1.tgz",
       "integrity": "sha1-oFgIHXiPFRXUcA11Aogea3WeRM0=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "methods": "1.1.2",
+        "superagent": "2.3.0"
+      }
     },
     "supports-color": {
       "version": "2.0.0",
@@ -4742,6 +6300,14 @@
       "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
       "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
       "dev": true,
+      "requires": {
+        "ajv": "4.11.8",
+        "ajv-keywords": "1.5.1",
+        "chalk": "1.1.3",
+        "lodash": "4.17.4",
+        "slice-ansi": "0.0.4",
+        "string-width": "2.1.0"
+      },
       "dependencies": {
         "ansi-regex": {
           "version": "3.0.0",
@@ -4759,13 +6325,20 @@
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz",
           "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "2.0.0",
+            "strip-ansi": "4.0.0"
+          }
         },
         "strip-ansi": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
           "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "ansi-regex": "3.0.0"
+          }
         }
       }
     },
@@ -4773,16 +6346,33 @@
       "version": "3.6.1",
       "resolved": "https://registry.npmjs.org/tape/-/tape-3.6.1.tgz",
       "integrity": "sha1-SJPdU+KApfWMDOswwsDrs7zVHh8=",
+      "requires": {
+        "deep-equal": "0.2.2",
+        "defined": "0.0.0",
+        "glob": "3.2.11",
+        "inherits": "2.0.3",
+        "object-inspect": "0.4.0",
+        "resumer": "0.0.0",
+        "through": "2.3.8"
+      },
       "dependencies": {
         "glob": {
           "version": "3.2.11",
           "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
-          "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0="
+          "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
+          "requires": {
+            "inherits": "2.0.3",
+            "minimatch": "0.3.0"
+          }
         },
         "minimatch": {
           "version": "0.3.0",
           "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
-          "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0="
+          "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
+          "requires": {
+            "lru-cache": "2.7.3",
+            "sigmund": "1.0.1"
+          }
         }
       }
     },
@@ -4807,6 +6397,10 @@
       "version": "0.6.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
       "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
+      "requires": {
+        "readable-stream": "1.0.34",
+        "xtend": "4.0.1"
+      },
       "dependencies": {
         "isarray": {
           "version": "0.0.1",
@@ -4816,7 +6410,13 @@
         "readable-stream": {
           "version": "1.0.34",
           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
-          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw="
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+          "requires": {
+            "core-util-is": "1.0.2",
+            "inherits": "2.0.3",
+            "isarray": "0.0.1",
+            "string_decoder": "0.10.31"
+          }
         },
         "string_decoder": {
           "version": "0.10.31",
@@ -4830,6 +6430,12 @@
       "resolved": "https://registry.npmjs.org/time-require/-/time-require-0.1.2.tgz",
       "integrity": "sha1-+eEss3D8JgXhFARYK6VO9corLZg=",
       "dev": true,
+      "requires": {
+        "chalk": "0.4.0",
+        "date-time": "0.1.1",
+        "pretty-ms": "0.2.2",
+        "text-table": "0.2.0"
+      },
       "dependencies": {
         "ansi-styles": {
           "version": "1.0.0",
@@ -4841,7 +6447,12 @@
           "version": "0.4.0",
           "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz",
           "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "ansi-styles": "1.0.0",
+            "has-color": "0.1.7",
+            "strip-ansi": "0.1.1"
+          }
         },
         "parse-ms": {
           "version": "0.1.2",
@@ -4853,7 +6464,10 @@
           "version": "0.2.2",
           "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-0.2.2.tgz",
           "integrity": "sha1-2oeaaC/zOjcBEEbxPWJ/Z8c7hPY=",
-          "dev": true
+          "dev": true,
+          "requires": {
+            "parse-ms": "0.1.2"
+          }
         },
         "strip-ansi": {
           "version": "0.1.1",
@@ -4872,7 +6486,11 @@
     "timers-ext": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
-      "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ="
+      "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=",
+      "requires": {
+        "es5-ext": "0.10.23",
+        "next-tick": "1.0.0"
+      }
     },
     "to-fast-properties": {
       "version": "1.0.3",
@@ -4883,12 +6501,18 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/touch/-/touch-1.0.0.tgz",
       "integrity": "sha1-RJy+LbrlqMgDjjDXH6D/RklHxN4=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "nopt": "1.0.10"
+      }
     },
     "tough-cookie": {
       "version": "2.3.2",
       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
-      "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo="
+      "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+      "requires": {
+        "punycode": "1.4.1"
+      }
     },
     "traverse": {
       "version": "0.6.6",
@@ -4916,7 +6540,10 @@
     "tunnel-agent": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0="
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "requires": {
+        "safe-buffer": "5.1.1"
+      }
     },
     "tweetnacl": {
       "version": "0.14.5",
@@ -4928,7 +6555,10 @@
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
       "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "prelude-ls": "1.1.2"
+      }
     },
     "type-detect": {
       "version": "1.0.0",
@@ -4938,7 +6568,11 @@
     "type-is": {
       "version": "1.6.15",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz",
-      "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA="
+      "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "2.1.15"
+      }
     },
     "type-name": {
       "version": "2.0.2",
@@ -4978,7 +6612,12 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz",
       "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "mkdirp": "0.5.1",
+        "os-tmpdir": "1.0.2",
+        "uid2": "0.0.3"
+      }
     },
     "unpipe": {
       "version": "1.0.0",
@@ -4988,7 +6627,12 @@
     "unreachable-branch-transform": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz",
-      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo="
+      "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=",
+      "requires": {
+        "esmangle-evaluator": "1.0.1",
+        "recast": "0.10.43",
+        "through2": "0.6.5"
+      }
     },
     "unzip-response": {
       "version": "1.0.2",
@@ -5000,13 +6644,26 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-1.0.3.tgz",
       "integrity": "sha1-j5LFFUgr1oMbfJMBPnD4dVLHz1o=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "boxen": "0.6.0",
+        "chalk": "1.1.3",
+        "configstore": "2.1.0",
+        "is-npm": "1.0.0",
+        "latest-version": "2.0.0",
+        "lazy-req": "1.1.0",
+        "semver-diff": "2.1.0",
+        "xdg-basedir": "2.0.0"
+      }
     },
     "url-parse-lax": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
       "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "prepend-http": "1.0.4"
+      }
     },
     "user-home": {
       "version": "1.1.1",
@@ -5018,6 +6675,9 @@
       "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
       "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
       "dev": true,
+      "requires": {
+        "inherits": "2.0.1"
+      },
       "dependencies": {
         "inherits": {
           "version": "2.0.1",
@@ -5045,13 +6705,20 @@
     "v8flags": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz",
-      "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ="
+      "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=",
+      "requires": {
+        "user-home": "1.1.1"
+      }
     },
     "validate-npm-package-license": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
       "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "spdx-correct": "1.0.2",
+        "spdx-expression-parse": "1.0.4"
+      }
     },
     "vary": {
       "version": "1.1.1",
@@ -5061,7 +6728,10 @@
     "verror": {
       "version": "1.3.6",
       "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
-      "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw="
+      "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+      "requires": {
+        "extsprintf": "1.0.2"
+      }
     },
     "when": {
       "version": "3.7.8",
@@ -5072,13 +6742,19 @@
       "version": "1.2.14",
       "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
       "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "isexe": "2.0.0"
+      }
     },
     "widest-line": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz",
       "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "string-width": "1.0.2"
+      }
     },
     "wordwrap": {
       "version": "1.0.0",
@@ -5095,31 +6771,54 @@
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
       "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "mkdirp": "0.5.1"
+      }
     },
     "write-file-atomic": {
       "version": "1.3.4",
       "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz",
       "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "imurmurhash": "0.1.4",
+        "slide": "1.1.6"
+      }
     },
     "write-json-file": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-1.2.0.tgz",
       "integrity": "sha1-LV3+lqvDyIkFfJOXGqQAXvtUgTQ=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "mkdirp": "0.5.1",
+        "object-assign": "4.1.1",
+        "pify": "2.3.0",
+        "pinkie-promise": "2.0.1",
+        "sort-keys": "1.1.2",
+        "write-file-atomic": "1.3.4"
+      }
     },
     "write-pkg": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-1.0.0.tgz",
       "integrity": "sha1-rriqnU14jh2JPfsIVJaLVDqRn1c=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "write-json-file": "1.2.0"
+      }
     },
     "xdg-basedir": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz",
       "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=",
-      "dev": true
+      "dev": true,
+      "requires": {
+        "os-homedir": "1.0.2"
+      }
     },
     "xtend": {
       "version": "4.0.1",
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 81a59270..b9a38d03 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -64,10 +64,14 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     }
 
     const { variables } = attributesFromDB;
-    return {
-      ...attributesFromDB,
-      variables: variables ? JSON.parse(variables) : undefined,
-    };
+    try {
+      return {
+        ...attributesFromDB,
+        variables: variables ? JSON.parse(variables) : undefined,
+      };
+    } catch (ignore) {
+      return attributesFromDB;
+    }
   };
 }
 export default DeviceAttributeDatabaseRepository;

From 4451e05e55a73678b471f748cf22c6b9d3dd6d97 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Fri, 14 Jul 2017 08:34:25 -0700
Subject: [PATCH 467/504] Update to newer version of spark protocol

---
 package-lock.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package-lock.json b/package-lock.json
index 8f6690ce..c005ba9c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6010,7 +6010,7 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#22ef7a1618ddeabd410463739edda477e10a42b0",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#6313b7238cf181fcbbde97c1cd7f3f8d38731da7",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.1",

From a4fea5d94bc16e4442f42d1bec9dbd62710a2ff6 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 14 Jul 2017 19:45:18 +0200
Subject: [PATCH 468/504] build deviceAttributesDatabaseRepository.js

---
 dist/repository/DeviceAttributeDatabaseRepository.js | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 801f0e52..89b07ea9 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -174,9 +174,13 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
 
     var variables = attributesFromDB.variables;
 
-    return (0, _extends3.default)({}, attributesFromDB, {
-      variables: variables ? JSON.parse(variables) : undefined
-    });
+    try {
+      return (0, _extends3.default)({}, attributesFromDB, {
+        variables: variables ? JSON.parse(variables) : undefined
+      });
+    } catch (ignore) {
+      return attributesFromDB;
+    }
   };
 
   this._database = database;

From 2105dcde572a67f0e067c18101ac9f99be64b019 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sat, 22 Jul 2017 11:18:13 -0700
Subject: [PATCH 469/504] Starting work on products API. Still need to add in
 device endpoint and update the firmware manager so it will auto-update
 devices.

---
 dist/RouteConfig.js                           |  52 +-
 dist/controllers/DevicesController.js         |  14 +-
 dist/controllers/ProductsController.js        | 660 ++++++++++++++++--
 dist/defaultBindings.js                       |  45 +-
 dist/lib/WebhookLogger.js                     |   6 +-
 dist/managers/PermissionManager.js            |  34 +-
 dist/repository/MongoDb.js                    | 168 +++--
 dist/repository/NeDb.js                       | 166 +++--
 .../OrganizationDatabaseRepository.js         | 184 +++++
 .../ProductConfigDatabaseRepository.js        | 184 +++++
 dist/repository/ProductDatabaseRepository.js  | 261 +++++++
 .../ProductFirmwareDatabaseRepository.js      | 194 +++++
 dist/repository/collectionNames.js            |   4 +
 examples/Products.md                          | 330 +++++++++
 examples/azure-mongodb/main.js                |   2 +-
 package.json                                  |   2 +-
 src/RouteConfig.js                            |  47 +-
 src/controllers/DevicesController.js          |  14 +-
 src/controllers/ProductsController.js         | 396 +++++++++--
 src/defaultBindings.js                        |  67 +-
 src/lib/WebhookLogger.js                      |   2 +-
 src/managers/PermissionManager.js             |  29 +-
 src/repository/MongoDb.js                     |   9 +
 src/repository/NeDb.js                        |   7 +
 .../OrganizationDatabaseRepository.js         |  47 ++
 .../ProductConfigDatabaseRepository.js        |  47 ++
 src/repository/ProductDatabaseRepository.js   |  73 ++
 .../ProductFirmwareDatabaseRepository.js      |  54 ++
 src/repository/collectionNames.js             |   8 +
 src/types.js                                  |  65 +-
 test/DeviceClaimsController.test.js           |  10 +-
 test/DevicesController.test.js                |  16 +-
 test/ProvisioningController.test.js           |  10 +-
 test/UsersController.test.js                  |   4 +-
 test/WebhooksController.test.js               |   6 +-
 test/setup/settings.js                        |   1 +
 36 files changed, 2833 insertions(+), 385 deletions(-)
 create mode 100644 dist/repository/OrganizationDatabaseRepository.js
 create mode 100644 dist/repository/ProductConfigDatabaseRepository.js
 create mode 100644 dist/repository/ProductDatabaseRepository.js
 create mode 100644 dist/repository/ProductFirmwareDatabaseRepository.js
 create mode 100644 examples/Products.md
 create mode 100644 src/repository/OrganizationDatabaseRepository.js
 create mode 100644 src/repository/ProductConfigDatabaseRepository.js
 create mode 100644 src/repository/ProductDatabaseRepository.js
 create mode 100644 src/repository/ProductFirmwareDatabaseRepository.js

diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js
index d8ed0af9..b2899bc3 100644
--- a/dist/RouteConfig.js
+++ b/dist/RouteConfig.js
@@ -68,7 +68,7 @@ var injectUserMiddleware = function injectUserMiddleware(container) {
       var user = token && token.user;
       // eslint-disable-next-line no-param-reassign
       request.user = user;
-      container.constitute('UserRepository').setCurrentUser(user);
+      container.constitute('IUserRepository').setCurrentUser(user);
     }
     next();
   };
@@ -148,55 +148,71 @@ exports.default = function (app, container, controllers, settings) {
                   // Take access token out if it's posted.
                   _request$body = request.body, access_token = _request$body.access_token, body = (0, _objectWithoutProperties3.default)(_request$body, ['access_token']);
                   _context.prev = 8;
+
+                  (allowedUploads || []).forEach(function (_ref2) {
+                    var maxCount = _ref2.maxCount,
+                        name = _ref2.name;
+
+                    if (!name || !request.files) {
+                      return;
+                    }
+                    var file = request.files[name];
+                    if (!file) {
+                      return;
+                    }
+                    body[name] = maxCount === 1 ? file[0] : file;
+                  });
                   functionResult = mappedFunction.call.apply(mappedFunction, [controllerInstance].concat((0, _toConsumableArray3.default)(values), [body]));
 
                   if (!functionResult.then) {
-                    _context.next = 24;
+                    _context.next = 25;
                     break;
                   }
 
                   if (serverSentEvents) {
-                    _context.next = 17;
+                    _context.next = 18;
                     break;
                   }
 
-                  _context.next = 14;
+                  _context.next = 15;
                   return _promise2.default.race([functionResult, new _promise2.default(function (resolve, reject) {
                     return setTimeout(function () {
                       return reject(new Error('timeout'));
                     }, settings.API_TIMEOUT);
                   })]);
 
-                case 14:
+                case 15:
                   _context.t0 = _context.sent;
-                  _context.next = 20;
+                  _context.next = 21;
                   break;
 
-                case 17:
-                  _context.next = 19;
+                case 18:
+                  _context.next = 20;
                   return functionResult;
 
-                case 19:
+                case 20:
                   _context.t0 = _context.sent;
 
-                case 20:
+                case 21:
                   result = _context.t0;
 
 
                   response.status((0, _nullthrows2.default)(result).status).json((0, _nullthrows2.default)(result).data);
-                  _context.next = 25;
+                  _context.next = 26;
                   break;
 
-                case 24:
+                case 25:
                   response.status(functionResult.status).json(functionResult.data);
 
-                case 25:
-                  _context.next = 31;
+                case 26:
+                  _context.next = 33;
                   break;
 
-                case 27:
-                  _context.prev = 27;
+                case 28:
+                  _context.prev = 28;
                   _context.t1 = _context['catch'](8);
+
+                  console.log(_context.t1);
                   httpError = new _HttpError2.default(_context.t1);
 
                   response.status(httpError.status).json({
@@ -204,12 +220,12 @@ exports.default = function (app, container, controllers, settings) {
                     ok: false
                   });
 
-                case 31:
+                case 33:
                 case 'end':
                   return _context.stop();
               }
             }
-          }, _callee, undefined, [[8, 27]]);
+          }, _callee, undefined, [[8, 28]]);
         }));
 
         return function (_x2, _x3) {
diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js
index 2de6e28e..43dc55d2 100644
--- a/dist/controllers/DevicesController.js
+++ b/dist/controllers/DevicesController.js
@@ -421,17 +421,19 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
                 return _context8.abrupt('return', this.ok({ id: deviceID, status: flashResult.status }));
 
               case 16:
-                if (!(this.request.files && !this.request.files.file)) {
-                  _context8.next = 18;
+
+                // 4 flash device with custom application
+                file = postBody.file;
+
+                if (file) {
+                  _context8.next = 19;
                   break;
                 }
 
                 throw new Error('Firmware file not provided');
 
-              case 18:
-                file = this.request.files && this.request.files.file[0];
-
-                if (!(file && (file.originalname === 'binary' || file.originalname.endsWith('.bin')))) {
+              case 19:
+                if (!(file.originalname === 'binary' || file.originalname.endsWith('.bin'))) {
                   _context8.next = 24;
                   break;
                 }
diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 09840b45..f8391b79 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -8,6 +8,18 @@ var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-pr
 
 var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
 
+var _promise = require('babel-runtime/core-js/promise');
+
+var _promise2 = _interopRequireDefault(_promise);
+
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -36,13 +48,19 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _desc, _value, _class;
+var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _dec25, _dec26, _dec27, _dec28, _dec29, _dec30, _dec31, _dec32, _dec33, _desc, _value, _class;
 /* eslint-disable */
 
 var _Controller2 = require('./Controller');
 
 var _Controller3 = _interopRequireDefault(_Controller2);
 
+var _allowUpload = require('../decorators/allowUpload');
+
+var _allowUpload2 = _interopRequireDefault(_allowUpload);
+
+var _binaryVersionReader = require('binary-version-reader');
+
 var _httpVerb = require('../decorators/httpVerb');
 
 var _httpVerb2 = _interopRequireDefault(_httpVerb);
@@ -86,15 +104,18 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIdOrSlug'), _dec7 = (0, _httpVerb2.default)('post'), _dec8 = (0, _route2.default)('/v1/products/:productIdOrSlug/device_claims'), _dec9 = (0, _httpVerb2.default)('get'), _dec10 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec11 = (0, _httpVerb2.default)('post'), _dec12 = (0, _route2.default)('/v1/products/:productIdOrSlug/firmware'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec15 = (0, _httpVerb2.default)('put'), _dec16 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec17 = (0, _httpVerb2.default)('delete'), _dec18 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec19 = (0, _httpVerb2.default)('get'), _dec20 = (0, _route2.default)('/v1/products/:productIdOrSlug/config'), _dec21 = (0, _httpVerb2.default)('get'), _dec22 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec23 = (0, _httpVerb2.default)('delete'), _dec24 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) {
+var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec7 = (0, _httpVerb2.default)('put'), _dec8 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec9 = (0, _httpVerb2.default)('delete'), _dec10 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec11 = (0, _httpVerb2.default)('get'), _dec12 = (0, _route2.default)('/v1/products/:productIDOrSlug/config'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec15 = (0, _httpVerb2.default)('get'), _dec16 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec17 = (0, _httpVerb2.default)('post'), _dec18 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec19 = (0, _allowUpload2.default)('binary', 1), _dec20 = (0, _httpVerb2.default)('put'), _dec21 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec22 = (0, _httpVerb2.default)('delete'), _dec23 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec24 = (0, _httpVerb2.default)('get'), _dec25 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec26 = (0, _httpVerb2.default)('put'), _dec27 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec28 = (0, _httpVerb2.default)('delete'), _dec29 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec30 = (0, _httpVerb2.default)('get'), _dec31 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec32 = (0, _httpVerb2.default)('delete'), _dec33 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) {
   (0, _inherits3.default)(ProductsController, _Controller);
 
-  function ProductsController(deviceManager) {
+  function ProductsController(organizationRepository, productRepository, productConfigRepository, productFirmwareRepository) {
     (0, _classCallCheck3.default)(this, ProductsController);
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (ProductsController.__proto__ || (0, _getPrototypeOf2.default)(ProductsController)).call(this));
 
-    _this._deviceManager = deviceManager;
+    _this._organizationRepository = organizationRepository;
+    _this._productConfigRepository = productConfigRepository;
+    _this._productFirmwareRepository = productFirmwareRepository;
+    _this._productRepository = productRepository;
     return _this;
   }
 
@@ -102,13 +123,19 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     key: 'getProducts',
     value: function () {
       var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+        var products;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                _context.next = 2;
+                return this._productRepository.getAll();
 
-              case 1:
+              case 2:
+                products = _context.sent;
+                return _context.abrupt('return', this.ok({ products: products.map(this._formatProduct) }));
+
+              case 4:
               case 'end':
                 return _context.stop();
             }
@@ -125,14 +152,71 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'createProduct',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(model) {
+        var missingFields, organizations, organizationID, product, config;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                if (model.product) {
+                  _context2.next = 2;
+                  break;
+                }
 
-              case 1:
+                return _context2.abrupt('return', this.bad('You must provide a product'));
+
+              case 2:
+                missingFields = ['description', 'hardware_version', 'name', 'platform_id', 'type'].filter(function (key) {
+                  return !model.product[key];
+                });
+
+                if (!missingFields.length) {
+                  _context2.next = 5;
+                  break;
+                }
+
+                return _context2.abrupt('return', this.bad('Missing fields: ' + missingFields.join(', ')));
+
+              case 5:
+                _context2.next = 7;
+                return this._organizationRepository.getByUserID(this.user.id);
+
+              case 7:
+                organizations = _context2.sent;
+
+                if (organizations.length) {
+                  _context2.next = 10;
+                  break;
+                }
+
+                return _context2.abrupt('return', this.bad("You don't have access to any organizations"));
+
+              case 10:
+                organizationID = organizations[0].id;
+
+                model.product.organization = organizationID;
+                _context2.next = 14;
+                return this._productRepository.create(model.product);
+
+              case 14:
+                product = _context2.sent;
+                _context2.next = 17;
+                return this._productConfigRepository.create({
+                  org_id: organizationID,
+                  product_id: product.id
+                });
+
+              case 17:
+                config = _context2.sent;
+
+                product.config_id = config.id;
+                _context2.next = 21;
+                return this._productRepository.updateByID(product.id, product);
+
+              case 21:
+                return _context2.abrupt('return', this.ok({ product: [this._formatProduct(product)] }));
+
+              case 22:
               case 'end':
                 return _context2.stop();
             }
@@ -140,7 +224,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee2, this);
       }));
 
-      function createProduct() {
+      function createProduct(_x) {
         return _ref2.apply(this, arguments);
       }
 
@@ -149,14 +233,29 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getProduct',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(productIdOrSlug) {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(productIDOrSlug) {
+        var product;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
             switch (_context3.prev = _context3.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context3.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context3.sent;
+
+                if (product) {
+                  _context3.next = 5;
+                  break;
+                }
+
+                return _context3.abrupt('return', this.bad('Product does not exist', 404));
+
+              case 5:
+                return _context3.abrupt('return', this.ok({ product: [this._formatProduct(product)] }));
+
+              case 6:
               case 'end':
                 return _context3.stop();
             }
@@ -164,23 +263,63 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee3, this);
       }));
 
-      function getProduct(_x) {
+      function getProduct(_x2) {
         return _ref3.apply(this, arguments);
       }
 
       return getProduct;
     }()
   }, {
-    key: 'generateClaimCode',
+    key: 'updateProduct',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIdOrSlug) {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIDOrSlug, model) {
+        var missingFields, product;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                if (model.product) {
+                  _context4.next = 2;
+                  break;
+                }
 
-              case 1:
+                return _context4.abrupt('return', this.bad('You must provide a product'));
+
+              case 2:
+                missingFields = ['config_id', 'description', 'hardware_version', 'id', 'name', 'organization', 'platform_id', 'type'].filter(function (key) {
+                  return !model.product[key];
+                });
+
+                if (!missingFields.length) {
+                  _context4.next = 5;
+                  break;
+                }
+
+                return _context4.abrupt('return', this.bad('Missing fields: ' + missingFields.join(', ')));
+
+              case 5:
+                _context4.next = 7;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
+
+              case 7:
+                product = _context4.sent;
+
+                if (product) {
+                  _context4.next = 10;
+                  break;
+                }
+
+                return _context4.abrupt('return', this.bad('Product ' + productIDOrSlug + ' doesn\'t exist'));
+
+              case 10:
+                _context4.next = 12;
+                return this._productRepository.updateByID(product.id, (0, _extends3.default)({}, product, model.product));
+
+              case 12:
+                product = _context4.sent;
+                return _context4.abrupt('return', this.ok({ product: [this._formatProduct(product)] }));
+
+              case 14:
               case 'end':
                 return _context4.stop();
             }
@@ -188,23 +327,42 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee4, this);
       }));
 
-      function generateClaimCode(_x2) {
+      function updateProduct(_x3, _x4) {
         return _ref4.apply(this, arguments);
       }
 
-      return generateClaimCode;
+      return updateProduct;
     }()
   }, {
-    key: 'getFirmware',
+    key: 'deleteProduct',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIdOrSlug) {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIDOrSlug) {
+        var product;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context5.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context5.sent;
+
+                if (product) {
+                  _context5.next = 5;
+                  break;
+                }
+
+                return _context5.abrupt('return', this.bad('Product does not exist', 404));
+
+              case 5:
+                _context5.next = 7;
+                return this._productRepository.deleteByID(product.id);
+
+              case 7:
+                return _context5.abrupt('return', this.ok());
+
+              case 8:
               case 'end':
                 return _context5.stop();
             }
@@ -212,26 +370,43 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee5, this);
       }));
 
-      function getFirmware(_x3) {
+      function deleteProduct(_x5) {
         return _ref5.apply(this, arguments);
       }
 
-      return getFirmware;
+      return deleteProduct;
     }()
-
-    // {version: number, name: 'current', binary: File, title: string, description: string}
-
   }, {
-    key: 'getFirmware',
+    key: 'getConfig',
     value: function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productIdOrSlug) {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productIDOrSlug) {
+        var product, config;
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context6.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context6.sent;
+
+                if (product) {
+                  _context6.next = 5;
+                  break;
+                }
+
+                return _context6.abrupt('return', this.bad('Product does not exist', 404));
+
+              case 5:
+                _context6.next = 7;
+                return this._productConfigRepository.getByProductID(product.id);
+
+              case 7:
+                config = _context6.sent;
+                return _context6.abrupt('return', this.ok({ product_configuration: config }));
+
+              case 9:
               case 'end':
                 return _context6.stop();
             }
@@ -239,23 +414,47 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee6, this);
       }));
 
-      function getFirmware(_x4) {
+      function getConfig(_x6) {
         return _ref6.apply(this, arguments);
       }
 
-      return getFirmware;
+      return getConfig;
     }()
   }, {
-    key: 'getDevices',
+    key: 'getFirmware',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIdOrSlug) {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIDOrSlug) {
+        var product, firmwares;
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context7.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context7.sent;
+
+                if (product) {
+                  _context7.next = 5;
+                  break;
+                }
+
+                return _context7.abrupt('return', this.bad('Product does not exist', 404));
+
+              case 5:
+                _context7.next = 7;
+                return this._productFirmwareRepository.getAllByProductID(product.product_id);
+
+              case 7:
+                firmwares = _context7.sent;
+                return _context7.abrupt('return', this.ok(firmwares.map(function (_ref8) {
+                  var data = _ref8.data,
+                      firmware = (0, _objectWithoutProperties3.default)(_ref8, ['data']);
+                  return firmware;
+                })));
+
+              case 9:
               case 'end':
                 return _context7.stop();
             }
@@ -263,23 +462,56 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee7, this);
       }));
 
-      function getDevices(_x5) {
+      function getFirmware(_x7) {
         return _ref7.apply(this, arguments);
       }
 
-      return getDevices;
+      return getFirmware;
     }()
   }, {
-    key: 'setFirmwareVersion',
+    key: 'getSingleFirmware',
     value: function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productIdOrSlug, deviceID, body) {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productIDOrSlug, version) {
+        var product, firmwareList, existingFirmware, data, id, output;
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
             switch (_context8.prev = _context8.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context8.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context8.sent;
+
+                if (product) {
+                  _context8.next = 5;
+                  break;
+                }
+
+                return _context8.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 5:
+                _context8.next = 7;
+                return this._productFirmwareRepository.getAllByProductID(product.product_id);
+
+              case 7:
+                firmwareList = _context8.sent;
+                existingFirmware = firmwareList.find(function (firmware) {
+                  return firmware.version === version;
+                });
+
+                if (existingFirmware) {
+                  _context8.next = 11;
+                  break;
+                }
+
+                return _context8.abrupt('return', this.bad('Firmware version ' + version + ' does not exist'));
+
+              case 11:
+                data = existingFirmware.data, id = existingFirmware.id, output = (0, _objectWithoutProperties3.default)(existingFirmware, ['data', 'id']);
+                return _context8.abrupt('return', this.ok(output));
+
+              case 13:
               case 'end':
                 return _context8.stop();
             }
@@ -287,23 +519,112 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee8, this);
       }));
 
-      function setFirmwareVersion(_x6, _x7, _x8) {
-        return _ref8.apply(this, arguments);
+      function getSingleFirmware(_x8, _x9) {
+        return _ref9.apply(this, arguments);
       }
 
-      return setFirmwareVersion;
+      return getSingleFirmware;
     }()
   }, {
-    key: 'removeDeviceFromProduct',
+    key: 'addFirmware',
     value: function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIdOrSlug, deviceID) {
+      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIDOrSlug, body) {
+        var missingFields, product, parser, moduleInfo, firmwarePlatformID, _moduleInfo$suffixInf, productId, productVersion, firmware, data, id, output;
+
         return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
             switch (_context9.prev = _context9.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
-
-              case 1:
+                missingFields = ['binary', 'description', 'title'].filter(function (key) {
+                  return !body[key];
+                });
+
+                if (!missingFields.length) {
+                  _context9.next = 3;
+                  break;
+                }
+
+                return _context9.abrupt('return', this.bad('Missing fields: ' + missingFields.join(', ')));
+
+              case 3:
+                _context9.next = 5;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
+
+              case 5:
+                product = _context9.sent;
+
+                if (product) {
+                  _context9.next = 8;
+                  break;
+                }
+
+                return _context9.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 8:
+                parser = new _binaryVersionReader.HalModuleParser();
+                _context9.next = 11;
+                return new _promise2.default(function (resolve, reject) {
+                  return parser.parseBuffer({ fileBuffer: body.binary.buffer }).then(resolve, reject);
+                });
+
+              case 11:
+                moduleInfo = _context9.sent;
+
+                if (!(moduleInfo.crc.ok !== 1)) {
+                  _context9.next = 14;
+                  break;
+                }
+
+                return _context9.abrupt('return', this.bad('Invalid CRC. Try recompiling the firmware'));
+
+              case 14:
+                firmwarePlatformID = moduleInfo.prefixInfo.platformID;
+
+                if (!(firmwarePlatformID !== product.platform_id)) {
+                  _context9.next = 17;
+                  break;
+                }
+
+                return _context9.abrupt('return', this.bad('Firmware had incorrect platform ID ' + firmwarePlatformID + '. Expected ' + product.platform_id));
+
+              case 17:
+                _moduleInfo$suffixInf = moduleInfo.suffixInfo, productId = _moduleInfo$suffixInf.productId, productVersion = _moduleInfo$suffixInf.productVersion;
+
+                if (!(productId !== parseInt(product.product_id, 10))) {
+                  _context9.next = 20;
+                  break;
+                }
+
+                return _context9.abrupt('return', this.bad('Firmware had incorrect product ID ' + productId + '. Expected ' + product.product_id));
+
+              case 20:
+                if (!(productVersion !== parseInt(body.version, 10))) {
+                  _context9.next = 22;
+                  break;
+                }
+
+                return _context9.abrupt('return', this.bad('Firmware had incorrect product version ' + productVersion + '. Expected ' + body.version));
+
+              case 22:
+                _context9.next = 24;
+                return this._productFirmwareRepository.create({
+                  current: body.current,
+                  data: body.binary.buffer,
+                  description: body.description,
+                  device_count: 0,
+                  name: body.binary.originalname,
+                  product_id: product.product_id,
+                  size: body.binary.size,
+                  title: body.title,
+                  version: body.version
+                });
+
+              case 24:
+                firmware = _context9.sent;
+                data = firmware.data, id = firmware.id, output = (0, _objectWithoutProperties3.default)(firmware, ['data', 'id']);
+                return _context9.abrupt('return', this.ok(output));
+
+              case 27:
               case 'end':
                 return _context9.stop();
             }
@@ -311,23 +632,69 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee9, this);
       }));
 
-      function removeDeviceFromProduct(_x9, _x10) {
-        return _ref9.apply(this, arguments);
+      function addFirmware(_x10, _x11) {
+        return _ref10.apply(this, arguments);
       }
 
-      return removeDeviceFromProduct;
+      return addFirmware;
     }()
   }, {
-    key: 'getConfig',
+    key: 'updateFirmware',
     value: function () {
-      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(productIdOrSlug) {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(productIDOrSlug, version, body) {
+        var _body, current, description, title, product, firmwareList, existingFirmware, firmware, data, id, output;
+
         return _regenerator2.default.wrap(function _callee10$(_context10) {
           while (1) {
             switch (_context10.prev = _context10.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _body = body, current = _body.current, description = _body.description, title = _body.title;
 
-              case 1:
+                body = {
+                  current: current,
+                  description: description,
+                  title: title
+                };
+                _context10.next = 4;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
+
+              case 4:
+                product = _context10.sent;
+
+                if (product) {
+                  _context10.next = 7;
+                  break;
+                }
+
+                return _context10.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 7:
+                _context10.next = 9;
+                return this._productFirmwareRepository.getAllByProductID(product.product_id);
+
+              case 9:
+                firmwareList = _context10.sent;
+                existingFirmware = firmwareList.find(function (firmware) {
+                  return firmware.version === version;
+                });
+
+                if (existingFirmware) {
+                  _context10.next = 13;
+                  break;
+                }
+
+                return _context10.abrupt('return', this.bad('Firmware version ' + version + ' does not exist'));
+
+              case 13:
+                _context10.next = 15;
+                return this._productFirmwareRepository.updateByID(existingFirmware.id, (0, _extends3.default)({}, existingFirmware, body));
+
+              case 15:
+                firmware = _context10.sent;
+                data = firmware.data, id = firmware.id, output = (0, _objectWithoutProperties3.default)(firmware, ['data', 'id']);
+                return _context10.abrupt('return', this.ok(output));
+
+              case 18:
               case 'end':
                 return _context10.stop();
             }
@@ -335,23 +702,59 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee10, this);
       }));
 
-      function getConfig(_x11) {
-        return _ref10.apply(this, arguments);
+      function updateFirmware(_x12, _x13, _x14) {
+        return _ref11.apply(this, arguments);
       }
 
-      return getConfig;
+      return updateFirmware;
     }()
   }, {
-    key: 'getEvents',
+    key: 'deleteFirmware',
     value: function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(productIdOrSlug, eventName) {
+      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(productIDOrSlug, version) {
+        var product, firmwareList, existingFirmware;
         return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
             switch (_context11.prev = _context11.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context11.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context11.sent;
+
+                if (product) {
+                  _context11.next = 5;
+                  break;
+                }
+
+                return _context11.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 5:
+                _context11.next = 7;
+                return this._productFirmwareRepository.getAllByProductID(product.product_id);
+
+              case 7:
+                firmwareList = _context11.sent;
+                existingFirmware = firmwareList.find(function (firmware) {
+                  return firmware.version === version;
+                });
+
+                if (existingFirmware) {
+                  _context11.next = 11;
+                  break;
+                }
+
+                return _context11.abrupt('return', this.bad('Firmware version ' + version + ' does not exist'));
+
+              case 11:
+                _context11.next = 13;
+                return this._productFirmwareRepository.deleteByID(existingFirmware.id);
+
+              case 13:
+                return _context11.abrupt('return', this.ok());
+
+              case 14:
               case 'end':
                 return _context11.stop();
             }
@@ -359,21 +762,21 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee11, this);
       }));
 
-      function getEvents(_x12, _x13) {
-        return _ref11.apply(this, arguments);
+      function deleteFirmware(_x15, _x16) {
+        return _ref12.apply(this, arguments);
       }
 
-      return getEvents;
+      return deleteFirmware;
     }()
   }, {
-    key: 'removeTeamMember',
+    key: 'getDevices',
     value: function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug, username) {
+      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug) {
         return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
             switch (_context12.prev = _context12.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                throw new _HttpError2.default('Not implemented');
 
               case 1:
               case 'end':
@@ -383,14 +786,119 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee12, this);
       }));
 
-      function removeTeamMember(_x14, _x15) {
-        return _ref12.apply(this, arguments);
+      function getDevices(_x17) {
+        return _ref13.apply(this, arguments);
+      }
+
+      return getDevices;
+    }()
+  }, {
+    key: 'setFirmwareVersion',
+    value: function () {
+      var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(productIdOrSlug, deviceID, body) {
+        return _regenerator2.default.wrap(function _callee13$(_context13) {
+          while (1) {
+            switch (_context13.prev = _context13.next) {
+              case 0:
+                throw new _HttpError2.default('Not implemented');
+
+              case 1:
+              case 'end':
+                return _context13.stop();
+            }
+          }
+        }, _callee13, this);
+      }));
+
+      function setFirmwareVersion(_x18, _x19, _x20) {
+        return _ref14.apply(this, arguments);
+      }
+
+      return setFirmwareVersion;
+    }()
+  }, {
+    key: 'removeDeviceFromProduct',
+    value: function () {
+      var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14(productIdOrSlug, deviceID) {
+        return _regenerator2.default.wrap(function _callee14$(_context14) {
+          while (1) {
+            switch (_context14.prev = _context14.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context14.stop();
+            }
+          }
+        }, _callee14, this);
+      }));
+
+      function removeDeviceFromProduct(_x21, _x22) {
+        return _ref15.apply(this, arguments);
+      }
+
+      return removeDeviceFromProduct;
+    }()
+  }, {
+    key: 'getEvents',
+    value: function () {
+      var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(productIdOrSlug, eventName) {
+        return _regenerator2.default.wrap(function _callee15$(_context15) {
+          while (1) {
+            switch (_context15.prev = _context15.next) {
+              case 0:
+                throw new _HttpError2.default('Not implemented');
+
+              case 1:
+              case 'end':
+                return _context15.stop();
+            }
+          }
+        }, _callee15, this);
+      }));
+
+      function getEvents(_x23, _x24) {
+        return _ref16.apply(this, arguments);
+      }
+
+      return getEvents;
+    }()
+  }, {
+    key: 'removeTeamMember',
+    value: function () {
+      var _ref17 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16(productIdOrSlug, username) {
+        return _regenerator2.default.wrap(function _callee16$(_context16) {
+          while (1) {
+            switch (_context16.prev = _context16.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context16.stop();
+            }
+          }
+        }, _callee16, this);
+      }));
+
+      function removeTeamMember(_x25, _x26) {
+        return _ref17.apply(this, arguments);
       }
 
       return removeTeamMember;
     }()
+  }, {
+    key: '_formatProduct',
+    value: function _formatProduct(product) {
+      var product_id = product.product_id,
+          output = (0, _objectWithoutProperties3.default)(product, ['product_id']);
+
+      output.id = product_id;
+      return output;
+    }
   }]);
   return ProductsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'generateClaimCode', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'generateClaimCode'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec23, _dec24], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProduct', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteProduct', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleFirmware', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addFirmware', [_dec17, _dec18, _dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateFirmware', [_dec20, _dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteFirmware', [_dec22, _dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec24, _dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec26, _dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec28, _dec29], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec30, _dec31], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec32, _dec33], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
 exports.default = ProductsController;
 /* eslint-enable */
\ No newline at end of file
diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 61081a1e..dd5aaaef 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -86,6 +86,22 @@ var _DeviceKeyDatabaseRepository = require('./repository/DeviceKeyDatabaseReposi
 
 var _DeviceKeyDatabaseRepository2 = _interopRequireDefault(_DeviceKeyDatabaseRepository);
 
+var _OrganizationDatabaseRepository = require('./repository/OrganizationDatabaseRepository');
+
+var _OrganizationDatabaseRepository2 = _interopRequireDefault(_OrganizationDatabaseRepository);
+
+var _ProductDatabaseRepository = require('./repository/ProductDatabaseRepository');
+
+var _ProductDatabaseRepository2 = _interopRequireDefault(_ProductDatabaseRepository);
+
+var _ProductConfigDatabaseRepository = require('./repository/ProductConfigDatabaseRepository');
+
+var _ProductConfigDatabaseRepository2 = _interopRequireDefault(_ProductConfigDatabaseRepository);
+
+var _ProductFirmwareDatabaseRepository = require('./repository/ProductFirmwareDatabaseRepository');
+
+var _ProductFirmwareDatabaseRepository2 = _interopRequireDefault(_ProductFirmwareDatabaseRepository);
+
 var _UserDatabaseRepository = require('./repository/UserDatabaseRepository');
 
 var _UserDatabaseRepository2 = _interopRequireDefault(_UserDatabaseRepository);
@@ -125,11 +141,11 @@ exports.default = function (container, newSettings) {
     };
   }, ['OAuthModel']);
 
-  container.bindClass('OAuthModel', _OAuthModel2.default, ['UserRepository']);
+  container.bindClass('OAuthModel', _OAuthModel2.default, ['IUserRepository']);
 
   container.bindClass('OAuthServer', _expressOauthServer2.default, ['OAUTH_SETTINGS']);
 
-  container.bindClass('Database', _NeDb2.default, ['DATABASE_PATH']);
+  container.bindClass('IDatabase', _NeDb2.default, ['DATABASE_PATH']);
 
   // lib
   container.bindClass('WebhookLogger', _WebhookLogger2.default, []);
@@ -138,22 +154,27 @@ exports.default = function (container, newSettings) {
   container.bindClass('DeviceClaimsController', _DeviceClaimsController2.default, ['DeviceManager', 'ClaimCodeManager']);
   container.bindClass('DevicesController', _DevicesController2.default, ['DeviceManager']);
   container.bindClass('EventsController', _EventsController2.default, ['EventManager']);
-  container.bindClass('PermissionManager', _PermissionManager2.default, ['DeviceAttributeRepository', 'UserRepository', 'WebhookRepository', 'OAuthServer']);
+  container.bindClass('PermissionManager', _PermissionManager2.default, ['IDeviceAttributeRepository', 'IOrganizationRepository', 'IUserRepository', 'IWebhookRepository', 'OAuthServer']);
   container.bindClass('OauthClientsController', _OauthClientsController2.default, []);
-  container.bindClass('ProductsController', _ProductsController2.default, []);
+  container.bindClass('ProductsController', _ProductsController2.default, ['IOrganizationRepository', 'IProductRepository', 'IProductConfigRepository', 'IProductFirmwareRepository']);
   container.bindClass('ProvisioningController', _ProvisioningController2.default, ['DeviceManager']);
-  container.bindClass('UsersController', _UsersController2.default, ['UserRepository']);
+  container.bindClass('UsersController', _UsersController2.default, ['IUserRepository']);
   container.bindClass('WebhooksController', _WebhooksController2.default, ['WebhookManager']);
 
   // managers
-  container.bindClass('DeviceManager', _DeviceManager2.default, ['DeviceAttributeRepository', 'DeviceFirmwareRepository', 'DeviceKeyRepository', 'PermissionManager', 'EventPublisher']);
+  container.bindClass('DeviceManager', _DeviceManager2.default, ['IDeviceAttributeRepository', 'IDeviceFirmwareRepository', 'IDeviceKeyRepository', 'PermissionManager', 'EventPublisher']);
   container.bindClass('EventManager', _EventManager2.default, ['EventPublisher']);
-  container.bindClass('WebhookManager', _WebhookManager2.default, ['EventPublisher', 'PermissionManager', 'WebhookLogger', 'WebhookRepository']);
+  container.bindClass('WebhookManager', _WebhookManager2.default, ['EventPublisher', 'PermissionManager', 'WebhookLogger', 'IWebhookRepository']);
 
   // Repositories
-  container.bindClass('DeviceAttributeRepository', _DeviceAttributeDatabaseRepository2.default, ['Database']);
-  container.bindClass('DeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']);
-  container.bindClass('DeviceKeyRepository', _DeviceKeyDatabaseRepository2.default, ['Database']);
-  container.bindClass('UserRepository', _UserDatabaseRepository2.default, ['Database']);
-  container.bindClass('WebhookRepository', _WebhookDatabaseRepository2.default, ['Database']);
+  container.bindClass('IDeviceAttributeRepository', _DeviceAttributeDatabaseRepository2.default, ['IDatabase']);
+  container.bindClass('IDeviceFirmwareRepository', _DeviceFirmwareFileRepository2.default, ['FIRMWARE_DIRECTORY']);
+  container.bindClass('IDeviceKeyRepository', _DeviceKeyDatabaseRepository2.default, ['IDatabase']);
+  container.bindClass('IOrganizationRepository', _OrganizationDatabaseRepository2.default, ['IDatabase']);
+  container.bindClass('IProductRepository', _ProductDatabaseRepository2.default, ['IDatabase']);
+  container.bindClass('IProductConfigRepository', _ProductConfigDatabaseRepository2.default, ['IDatabase']);
+  container.bindClass('IProductFirmwareRepository', _ProductFirmwareDatabaseRepository2.default, ['IDatabase']);
+
+  container.bindClass('IUserRepository', _UserDatabaseRepository2.default, ['IDatabase']);
+  container.bindClass('IWebhookRepository', _WebhookDatabaseRepository2.default, ['IDatabase']);
 };
\ No newline at end of file
diff --git a/dist/lib/WebhookLogger.js b/dist/lib/WebhookLogger.js
index 6adfad1c..0cba4f65 100644
--- a/dist/lib/WebhookLogger.js
+++ b/dist/lib/WebhookLogger.js
@@ -4,10 +4,6 @@ Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _extends2 = require('babel-runtime/helpers/extends');
-
-var _extends3 = _interopRequireDefault(_extends2);
-
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
@@ -37,7 +33,7 @@ var WebhookLogger = function () {
       }
 
       this._lastLog = args;
-      logger.info((0, _extends3.default)({}, args), 'webhook');
+      logger.info([].concat(args), 'webhook');
     }
   }]);
   return WebhookLogger;
diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index dce8aca3..43aac579 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -42,7 +42,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 var logger = _logger2.default.createModuleLogger(module);
 
-var PermissionManager = function PermissionManager(deviceAttributeRepository, userRepository, webhookRepository, oauthServer) {
+var PermissionManager = function PermissionManager(deviceAttributeRepository, organizationRepository, userRepository, webhookRepository, oauthServer) {
   var _this = this;
 
   (0, _classCallCheck3.default)(this, PermissionManager);
@@ -155,7 +155,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
             return _this._userRepository.createWithCredentials({
               password: _settings2.default.DEFAULT_ADMIN_PASSWORD,
               username: _settings2.default.DEFAULT_ADMIN_USERNAME
-            }, 'administrator');
+            });
 
           case 3:
             _context4.next = 5;
@@ -230,7 +230,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
     }, _callee5, _this);
   }));
   this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
-    var defaultAdminUser;
+    var defaultAdminUser, organizations;
     return _regenerator2.default.wrap(function _callee6$(_context6) {
       while (1) {
         switch (_context6.prev = _context6.next) {
@@ -247,7 +247,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
             }
 
             logger.info({ token: defaultAdminUser.accessTokens[0].accessToken }, 'Default Admin token');
-            _context6.next = 9;
+            _context6.next = 12;
             break;
 
           case 7:
@@ -255,6 +255,31 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
             return _this._createDefaultAdminUser();
 
           case 9:
+            _context6.next = 11;
+            return _this._userRepository.getByUsername(_settings2.default.DEFAULT_ADMIN_USERNAME);
+
+          case 11:
+            defaultAdminUser = _context6.sent;
+
+          case 12:
+            _context6.next = 14;
+            return _this._organizationRepository.getAll();
+
+          case 14:
+            organizations = _context6.sent;
+
+            if (!(!organizations.length && defaultAdminUser)) {
+              _context6.next = 18;
+              break;
+            }
+
+            _context6.next = 18;
+            return _this._organizationRepository.create({
+              name: 'DEFAULT ORG',
+              user_ids: [defaultAdminUser.id]
+            });
+
+          case 18:
           case 'end':
             return _context6.stop();
         }
@@ -262,6 +287,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, us
     }, _callee6, _this);
   }));
 
+  this._organizationRepository = organizationRepository;
   this._userRepository = userRepository;
   this._repositoriesByEntityName.set('deviceAttributes', deviceAttributeRepository);
   this._repositoriesByEntityName.set('webhook', webhookRepository);
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index 0752cb44..8f38fab5 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -95,8 +95,8 @@ var _initialiseProps = function _initialiseProps() {
   this._database = null;
   this._statusEventEmitter = new _events2.default();
 
-  this.insertOne = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collectionName, entity) {
+  this.count = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collectionName, query) {
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
@@ -104,19 +104,19 @@ var _initialiseProps = function _initialiseProps() {
               _context3.next = 2;
               return _this3.__runForCollection(collectionName, function () {
                 var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collection) {
-                  var insertResult;
                   return _regenerator2.default.wrap(function _callee2$(_context2) {
                     while (1) {
                       switch (_context2.prev = _context2.next) {
                         case 0:
                           _context2.next = 2;
-                          return collection.insertOne(entity);
+                          return collection.count(_this3.__translateQuery(query), {
+                            timeout: false
+                          });
 
                         case 2:
-                          insertResult = _context2.sent;
-                          return _context2.abrupt('return', _this3.__translateResultItem(insertResult.ops[0]));
+                          return _context2.abrupt('return', _context2.sent);
 
-                        case 4:
+                        case 3:
                         case 'end':
                           return _context2.stop();
                       }
@@ -145,8 +145,8 @@ var _initialiseProps = function _initialiseProps() {
     };
   }();
 
-  this.find = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collectionName, query) {
+  this.insertOne = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collectionName, entity) {
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
@@ -154,17 +154,17 @@ var _initialiseProps = function _initialiseProps() {
               _context5.next = 2;
               return _this3.__runForCollection(collectionName, function () {
                 var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collection) {
-                  var resultItems;
+                  var insertResult;
                   return _regenerator2.default.wrap(function _callee4$(_context4) {
                     while (1) {
                       switch (_context4.prev = _context4.next) {
                         case 0:
                           _context4.next = 2;
-                          return collection.find(_this3.__translateQuery(query), { timeout: false }).toArray();
+                          return collection.insertOne(entity);
 
                         case 2:
-                          resultItems = _context4.sent;
-                          return _context4.abrupt('return', resultItems.map(_this3.__translateResultItem));
+                          insertResult = _context4.sent;
+                          return _context4.abrupt('return', _this3.__translateResultItem(insertResult.ops[0]));
 
                         case 4:
                         case 'end':
@@ -195,8 +195,8 @@ var _initialiseProps = function _initialiseProps() {
     };
   }();
 
-  this.findAndModify = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collectionName, query, updateQuery) {
+  this.find = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collectionName, query) {
       return _regenerator2.default.wrap(function _callee7$(_context7) {
         while (1) {
           switch (_context7.prev = _context7.next) {
@@ -204,17 +204,17 @@ var _initialiseProps = function _initialiseProps() {
               _context7.next = 2;
               return _this3.__runForCollection(collectionName, function () {
                 var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collection) {
-                  var modifyResult;
+                  var resultItems;
                   return _regenerator2.default.wrap(function _callee6$(_context6) {
                     while (1) {
                       switch (_context6.prev = _context6.next) {
                         case 0:
                           _context6.next = 2;
-                          return collection.findAndModify(_this3.__translateQuery(query), null, _this3.__translateQuery(updateQuery), { new: true, upsert: true });
+                          return collection.find(_this3.__translateQuery(query), { timeout: false }).toArray();
 
                         case 2:
-                          modifyResult = _context6.sent;
-                          return _context6.abrupt('return', _this3.__translateResultItem(modifyResult.value));
+                          resultItems = _context6.sent;
+                          return _context6.abrupt('return', resultItems.map(_this3.__translateResultItem));
 
                         case 4:
                         case 'end':
@@ -224,7 +224,7 @@ var _initialiseProps = function _initialiseProps() {
                   }, _callee6, _this3);
                 }));
 
-                return function (_x11) {
+                return function (_x10) {
                   return _ref7.apply(this, arguments);
                 };
               }());
@@ -240,13 +240,13 @@ var _initialiseProps = function _initialiseProps() {
       }, _callee7, _this3);
     }));
 
-    return function (_x8, _x9, _x10) {
+    return function (_x8, _x9) {
       return _ref6.apply(this, arguments);
     };
   }();
 
-  this.findOne = function () {
-    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collectionName, query) {
+  this.findAndModify = function () {
+    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collectionName, query, updateQuery) {
       return _regenerator2.default.wrap(function _callee9$(_context9) {
         while (1) {
           switch (_context9.prev = _context9.next) {
@@ -254,17 +254,17 @@ var _initialiseProps = function _initialiseProps() {
               _context9.next = 2;
               return _this3.__runForCollection(collectionName, function () {
                 var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collection) {
-                  var resultItem;
+                  var modifyResult;
                   return _regenerator2.default.wrap(function _callee8$(_context8) {
                     while (1) {
                       switch (_context8.prev = _context8.next) {
                         case 0:
                           _context8.next = 2;
-                          return collection.findOne(_this3.__translateQuery(query));
+                          return collection.findAndModify(_this3.__translateQuery(query), null, _this3.__translateQuery(updateQuery), { new: true, upsert: true });
 
                         case 2:
-                          resultItem = _context8.sent;
-                          return _context8.abrupt('return', _this3.__translateResultItem(resultItem));
+                          modifyResult = _context8.sent;
+                          return _context8.abrupt('return', _this3.__translateResultItem(modifyResult.value));
 
                         case 4:
                         case 'end':
@@ -290,12 +290,12 @@ var _initialiseProps = function _initialiseProps() {
       }, _callee9, _this3);
     }));
 
-    return function (_x12, _x13) {
+    return function (_x11, _x12, _x13) {
       return _ref8.apply(this, arguments);
     };
   }();
 
-  this.remove = function () {
+  this.findOne = function () {
     var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, query) {
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
@@ -304,17 +304,19 @@ var _initialiseProps = function _initialiseProps() {
               _context11.next = 2;
               return _this3.__runForCollection(collectionName, function () {
                 var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collection) {
+                  var resultItem;
                   return _regenerator2.default.wrap(function _callee10$(_context10) {
                     while (1) {
                       switch (_context10.prev = _context10.next) {
                         case 0:
                           _context10.next = 2;
-                          return collection.remove(_this3.__translateQuery(query));
+                          return collection.findOne(_this3.__translateQuery(query));
 
                         case 2:
-                          return _context10.abrupt('return', _context10.sent);
+                          resultItem = _context10.sent;
+                          return _context10.abrupt('return', _this3.__translateResultItem(resultItem));
 
-                        case 3:
+                        case 4:
                         case 'end':
                           return _context10.stop();
                       }
@@ -343,53 +345,101 @@ var _initialiseProps = function _initialiseProps() {
     };
   }();
 
+  this.remove = function () {
+    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(collectionName, query) {
+      return _regenerator2.default.wrap(function _callee13$(_context13) {
+        while (1) {
+          switch (_context13.prev = _context13.next) {
+            case 0:
+              _context13.next = 2;
+              return _this3.__runForCollection(collectionName, function () {
+                var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(collection) {
+                  return _regenerator2.default.wrap(function _callee12$(_context12) {
+                    while (1) {
+                      switch (_context12.prev = _context12.next) {
+                        case 0:
+                          _context12.next = 2;
+                          return collection.remove(_this3.__translateQuery(query));
+
+                        case 2:
+                          return _context12.abrupt('return', _context12.sent);
+
+                        case 3:
+                        case 'end':
+                          return _context12.stop();
+                      }
+                    }
+                  }, _callee12, _this3);
+                }));
+
+                return function (_x20) {
+                  return _ref13.apply(this, arguments);
+                };
+              }());
+
+            case 2:
+              return _context13.abrupt('return', _context13.sent);
+
+            case 3:
+            case 'end':
+              return _context13.stop();
+          }
+        }
+      }, _callee13, _this3);
+    }));
+
+    return function (_x18, _x19) {
+      return _ref12.apply(this, arguments);
+    };
+  }();
+
   this.__runForCollection = function () {
-    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(collectionName, callback) {
-      return _regenerator2.default.wrap(function _callee12$(_context12) {
+    var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14(collectionName, callback) {
+      return _regenerator2.default.wrap(function _callee14$(_context14) {
         while (1) {
-          switch (_context12.prev = _context12.next) {
+          switch (_context14.prev = _context14.next) {
             case 0:
-              _context12.next = 2;
+              _context14.next = 2;
               return _this3._isDbReady();
 
             case 2:
               if (_this3._database) {
-                _context12.next = 4;
+                _context14.next = 4;
                 break;
               }
 
               throw new Error('database is not initialized');
 
             case 4:
-              return _context12.abrupt('return', callback(_this3._database.collection(collectionName)).catch(function (error) {
+              return _context14.abrupt('return', callback(_this3._database.collection(collectionName)).catch(function (error) {
                 return logger.error({ err: error }, 'Run for Collection');
               }));
 
             case 5:
             case 'end':
-              return _context12.stop();
+              return _context14.stop();
           }
         }
-      }, _callee12, _this3);
+      }, _callee14, _this3);
     }));
 
-    return function (_x18, _x19) {
-      return _ref12.apply(this, arguments);
+    return function (_x21, _x22) {
+      return _ref14.apply(this, arguments);
     };
   }();
 
   this._init = function () {
-    var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(url, options) {
+    var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(url, options) {
       var database;
-      return _regenerator2.default.wrap(function _callee13$(_context13) {
+      return _regenerator2.default.wrap(function _callee15$(_context15) {
         while (1) {
-          switch (_context13.prev = _context13.next) {
+          switch (_context15.prev = _context15.next) {
             case 0:
-              _context13.next = 2;
+              _context15.next = 2;
               return _mongodb.MongoClient.connect(url, options);
 
             case 2:
-              database = _context13.sent;
+              database = _context15.sent;
 
 
               database.on('error', function (error) {
@@ -409,31 +459,31 @@ var _initialiseProps = function _initialiseProps() {
 
             case 8:
             case 'end':
-              return _context13.stop();
+              return _context15.stop();
           }
         }
-      }, _callee13, _this3);
+      }, _callee15, _this3);
     }));
 
-    return function (_x20, _x21) {
-      return _ref13.apply(this, arguments);
+    return function (_x23, _x24) {
+      return _ref15.apply(this, arguments);
     };
   }();
 
-  this._isDbReady = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14() {
-    return _regenerator2.default.wrap(function _callee14$(_context14) {
+  this._isDbReady = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16() {
+    return _regenerator2.default.wrap(function _callee16$(_context16) {
       while (1) {
-        switch (_context14.prev = _context14.next) {
+        switch (_context16.prev = _context16.next) {
           case 0:
             if (!_this3._database) {
-              _context14.next = 2;
+              _context16.next = 2;
               break;
             }
 
-            return _context14.abrupt('return', _promise2.default.resolve());
+            return _context16.abrupt('return', _promise2.default.resolve());
 
           case 2:
-            return _context14.abrupt('return', new _promise2.default(function (resolve) {
+            return _context16.abrupt('return', new _promise2.default(function (resolve) {
               _this3._statusEventEmitter.once(DB_READY_EVENT, function () {
                 return resolve();
               });
@@ -441,10 +491,10 @@ var _initialiseProps = function _initialiseProps() {
 
           case 3:
           case 'end':
-            return _context14.stop();
+            return _context16.stop();
         }
       }
-    }, _callee14, _this3);
+    }, _callee16, _this3);
   }));
 };
 
diff --git a/dist/repository/NeDb.js b/dist/repository/NeDb.js
index 3143c968..8d76a2cc 100644
--- a/dist/repository/NeDb.js
+++ b/dist/repository/NeDb.js
@@ -70,8 +70,8 @@ var NeDb = function (_BaseMongoDb) {
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (NeDb.__proto__ || (0, _getPrototypeOf2.default)(NeDb)).call(this));
 
-    _this.insertOne = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, entity) {
+    _this.count = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, query) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
@@ -79,19 +79,17 @@ var NeDb = function (_BaseMongoDb) {
                 _context2.next = 2;
                 return _this.__runForCollection(collectionName, function () {
                   var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
-                    var insertResult;
                     return _regenerator2.default.wrap(function _callee$(_context) {
                       while (1) {
                         switch (_context.prev = _context.next) {
                           case 0:
                             _context.next = 2;
-                            return (0, _promisify.promisify)(collection, 'insert', entity);
+                            return (0, _promisify.promisify)(collection, 'find', query);
 
                           case 2:
-                            insertResult = _context.sent;
-                            return _context.abrupt('return', _this.__translateResultItem(insertResult));
+                            return _context.abrupt('return', _context.sent);
 
-                          case 4:
+                          case 3:
                           case 'end':
                             return _context.stop();
                         }
@@ -105,9 +103,19 @@ var NeDb = function (_BaseMongoDb) {
                 }());
 
               case 2:
-                return _context2.abrupt('return', _context2.sent);
+                _context2.t0 = _context2.sent;
 
-              case 3:
+                if (_context2.t0) {
+                  _context2.next = 5;
+                  break;
+                }
+
+                _context2.t0 = 0;
+
+              case 5:
+                return _context2.abrupt('return', _context2.t0);
+
+              case 6:
               case 'end':
                 return _context2.stop();
             }
@@ -120,8 +128,8 @@ var NeDb = function (_BaseMongoDb) {
       };
     }();
 
-    _this.find = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, query) {
+    _this.insertOne = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, entity) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -129,17 +137,17 @@ var NeDb = function (_BaseMongoDb) {
                 _context4.next = 2;
                 return _this.__runForCollection(collectionName, function () {
                   var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
-                    var resultItems;
+                    var insertResult;
                     return _regenerator2.default.wrap(function _callee3$(_context3) {
                       while (1) {
                         switch (_context3.prev = _context3.next) {
                           case 0:
                             _context3.next = 2;
-                            return (0, _promisify.promisify)(collection, 'find', query);
+                            return (0, _promisify.promisify)(collection, 'insert', entity);
 
                           case 2:
-                            resultItems = _context3.sent;
-                            return _context3.abrupt('return', resultItems.map(_this.__translateResultItem));
+                            insertResult = _context3.sent;
+                            return _context3.abrupt('return', _this.__translateResultItem(insertResult));
 
                           case 4:
                           case 'end':
@@ -170,8 +178,8 @@ var NeDb = function (_BaseMongoDb) {
       };
     }();
 
-    _this.findAndModify = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query, updateQuery) {
+    _this.find = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
@@ -179,27 +187,19 @@ var NeDb = function (_BaseMongoDb) {
                 _context6.next = 2;
                 return _this.__runForCollection(collectionName, function () {
                   var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
-                    var _ref7, _ref8, count, resultItem;
-
+                    var resultItems;
                     return _regenerator2.default.wrap(function _callee5$(_context5) {
                       while (1) {
                         switch (_context5.prev = _context5.next) {
                           case 0:
                             _context5.next = 2;
-                            return (0, _promisify.promisify)(collection, 'update', query, updateQuery, {
-                              returnUpdatedDocs: true,
-                              upsert: true
-                            });
+                            return (0, _promisify.promisify)(collection, 'find', query);
 
                           case 2:
-                            _ref7 = _context5.sent;
-                            _ref8 = (0, _slicedToArray3.default)(_ref7, 2);
-                            count = _ref8[0];
-                            // eslint-disable-line no-unused-vars
-                            resultItem = _ref8[1];
-                            return _context5.abrupt('return', _this.__translateResultItem(resultItem));
+                            resultItems = _context5.sent;
+                            return _context5.abrupt('return', resultItems.map(_this.__translateResultItem));
 
-                          case 7:
+                          case 4:
                           case 'end':
                             return _context5.stop();
                         }
@@ -207,7 +207,7 @@ var NeDb = function (_BaseMongoDb) {
                     }, _callee5, _this2);
                   }));
 
-                  return function (_x10) {
+                  return function (_x9) {
                     return _ref6.apply(this, arguments);
                   };
                 }());
@@ -223,33 +223,41 @@ var NeDb = function (_BaseMongoDb) {
         }, _callee6, _this2);
       }));
 
-      return function (_x7, _x8, _x9) {
+      return function (_x7, _x8) {
         return _ref5.apply(this, arguments);
       };
     }();
 
-    _this.findOne = function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query) {
+    _this.findAndModify = function () {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query, updateQuery) {
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
             switch (_context8.prev = _context8.next) {
               case 0:
                 _context8.next = 2;
                 return _this.__runForCollection(collectionName, function () {
-                  var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
-                    var resultItem;
+                  var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
+                    var _ref9, _ref10, count, resultItem;
+
                     return _regenerator2.default.wrap(function _callee7$(_context7) {
                       while (1) {
                         switch (_context7.prev = _context7.next) {
                           case 0:
                             _context7.next = 2;
-                            return (0, _promisify.promisify)(collection, 'findOne', query);
+                            return (0, _promisify.promisify)(collection, 'update', query, updateQuery, {
+                              returnUpdatedDocs: true,
+                              upsert: true
+                            });
 
                           case 2:
-                            resultItem = _context7.sent;
+                            _ref9 = _context7.sent;
+                            _ref10 = (0, _slicedToArray3.default)(_ref9, 2);
+                            count = _ref10[0];
+                            // eslint-disable-line no-unused-vars
+                            resultItem = _ref10[1];
                             return _context7.abrupt('return', _this.__translateResultItem(resultItem));
 
-                          case 4:
+                          case 7:
                           case 'end':
                             return _context7.stop();
                         }
@@ -258,7 +266,7 @@ var NeDb = function (_BaseMongoDb) {
                   }));
 
                   return function (_x13) {
-                    return _ref10.apply(this, arguments);
+                    return _ref8.apply(this, arguments);
                   };
                 }());
 
@@ -273,12 +281,12 @@ var NeDb = function (_BaseMongoDb) {
         }, _callee8, _this2);
       }));
 
-      return function (_x11, _x12) {
-        return _ref9.apply(this, arguments);
+      return function (_x10, _x11, _x12) {
+        return _ref7.apply(this, arguments);
       };
     }();
 
-    _this.remove = function () {
+    _this.findOne = function () {
       var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
         return _regenerator2.default.wrap(function _callee10$(_context10) {
           while (1) {
@@ -287,17 +295,19 @@ var NeDb = function (_BaseMongoDb) {
                 _context10.next = 2;
                 return _this.__runForCollection(collectionName, function () {
                   var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
+                    var resultItem;
                     return _regenerator2.default.wrap(function _callee9$(_context9) {
                       while (1) {
                         switch (_context9.prev = _context9.next) {
                           case 0:
                             _context9.next = 2;
-                            return (0, _promisify.promisify)(collection, 'remove', query);
+                            return (0, _promisify.promisify)(collection, 'findOne', query);
 
                           case 2:
-                            return _context9.abrupt('return', _context9.sent);
+                            resultItem = _context9.sent;
+                            return _context9.abrupt('return', _this.__translateResultItem(resultItem));
 
-                          case 3:
+                          case 4:
                           case 'end':
                             return _context9.stop();
                         }
@@ -326,20 +336,47 @@ var NeDb = function (_BaseMongoDb) {
       };
     }();
 
-    _this.__runForCollection = function () {
-      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, callback) {
-        return _regenerator2.default.wrap(function _callee11$(_context11) {
+    _this.remove = function () {
+      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(collectionName, query) {
+        return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
-            switch (_context11.prev = _context11.next) {
+            switch (_context12.prev = _context12.next) {
               case 0:
-                return _context11.abrupt('return', callback(_this._database[collectionName]));
+                _context12.next = 2;
+                return _this.__runForCollection(collectionName, function () {
+                  var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collection) {
+                    return _regenerator2.default.wrap(function _callee11$(_context11) {
+                      while (1) {
+                        switch (_context11.prev = _context11.next) {
+                          case 0:
+                            _context11.next = 2;
+                            return (0, _promisify.promisify)(collection, 'remove', query);
 
-              case 1:
+                          case 2:
+                            return _context11.abrupt('return', _context11.sent);
+
+                          case 3:
+                          case 'end':
+                            return _context11.stop();
+                        }
+                      }
+                    }, _callee11, _this2);
+                  }));
+
+                  return function (_x19) {
+                    return _ref14.apply(this, arguments);
+                  };
+                }());
+
+              case 2:
+                return _context12.abrupt('return', _context12.sent);
+
+              case 3:
               case 'end':
-                return _context11.stop();
+                return _context12.stop();
             }
           }
-        }, _callee11, _this2);
+        }, _callee12, _this2);
       }));
 
       return function (_x17, _x18) {
@@ -347,6 +384,27 @@ var NeDb = function (_BaseMongoDb) {
       };
     }();
 
+    _this.__runForCollection = function () {
+      var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(collectionName, callback) {
+        return _regenerator2.default.wrap(function _callee13$(_context13) {
+          while (1) {
+            switch (_context13.prev = _context13.next) {
+              case 0:
+                return _context13.abrupt('return', callback(_this._database[collectionName]));
+
+              case 1:
+              case 'end':
+                return _context13.stop();
+            }
+          }
+        }, _callee13, _this2);
+      }));
+
+      return function (_x20, _x21) {
+        return _ref15.apply(this, arguments);
+      };
+    }();
+
     if (!_fs2.default.existsSync(path)) {
       _mkdirp2.default.sync(path);
     }
diff --git a/dist/repository/OrganizationDatabaseRepository.js b/dist/repository/OrganizationDatabaseRepository.js
new file mode 100644
index 00000000..6ca2da46
--- /dev/null
+++ b/dist/repository/OrganizationDatabaseRepository.js
@@ -0,0 +1,184 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var OrganizationDatabaseRepository = function OrganizationDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, OrganizationDatabaseRepository);
+  this._collectionName = _collectionNames2.default.ORGANIZATIONS;
+
+  this.create = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+
+            case 2:
+              return _context.abrupt('return', _context.sent);
+
+            case 3:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.deleteByID = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this._database.remove(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x2) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.getAll = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              // TODO - this should probably just query the organization
+              query = userID ? { ownerID: userID } : {};
+              _context3.next = 3;
+              return _this._database.find(_this._collectionName, query);
+
+            case 3:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 4:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function () {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.getByUserID = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this._database.find(_this._collectionName, {
+                user_ids: userID
+              });
+
+            case 2:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 3:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x4) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.getByID = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              _context5.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context5.abrupt('return', _context5.sent);
+
+            case 3:
+            case 'end':
+              return _context5.stop();
+          }
+        }
+      }, _callee5, _this);
+    }));
+
+    return function (_x5) {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+    return _regenerator2.default.wrap(function _callee6$(_context6) {
+      while (1) {
+        switch (_context6.prev = _context6.next) {
+          case 0:
+            throw new Error('The method is not implemented');
+
+          case 1:
+          case 'end':
+            return _context6.stop();
+        }
+      }
+    }, _callee6, _this);
+  }));
+
+  this._database = database;
+};
+
+exports.default = OrganizationDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/ProductConfigDatabaseRepository.js b/dist/repository/ProductConfigDatabaseRepository.js
new file mode 100644
index 00000000..4247fcd1
--- /dev/null
+++ b/dist/repository/ProductConfigDatabaseRepository.js
@@ -0,0 +1,184 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var ProductConfigDatabaseRepository = function ProductConfigDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, ProductConfigDatabaseRepository);
+  this._collectionName = _collectionNames2.default.PRODUCT_CONFIGS;
+
+  this.create = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+
+            case 2:
+              return _context.abrupt('return', _context.sent);
+
+            case 3:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.deleteByID = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this._database.remove(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x2) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.getAll = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              // TODO - this should probably just query the organization
+              query = userID ? { ownerID: userID } : {};
+              _context3.next = 3;
+              return _this._database.find(_this._collectionName, query);
+
+            case 3:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 4:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function () {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.getByProductID = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this._database.findOne(_this._collectionName, {
+                product_id: productID
+              });
+
+            case 2:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 3:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x4) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.getByID = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              _context5.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context5.abrupt('return', _context5.sent);
+
+            case 3:
+            case 'end':
+              return _context5.stop();
+          }
+        }
+      }, _callee5, _this);
+    }));
+
+    return function (_x5) {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+    return _regenerator2.default.wrap(function _callee6$(_context6) {
+      while (1) {
+        switch (_context6.prev = _context6.next) {
+          case 0:
+            throw new Error('The method is not implemented');
+
+          case 1:
+          case 'end':
+            return _context6.stop();
+        }
+      }
+    }, _callee6, _this);
+  }));
+
+  this._database = database;
+};
+
+exports.default = ProductConfigDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
new file mode 100644
index 00000000..0cba9de0
--- /dev/null
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -0,0 +1,261 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var ProductDatabaseRepository = function ProductDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, ProductDatabaseRepository);
+  this._collectionName = _collectionNames2.default.PRODUCTS;
+
+  this.create = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.t0 = _this._database;
+              _context.t1 = _this._collectionName;
+              _context.t2 = _extends3.default;
+              _context.t3 = {};
+              _context.next = 6;
+              return _this._formatProduct(model);
+
+            case 6:
+              _context.t4 = _context.sent;
+              _context.t5 = new Date();
+              _context.next = 10;
+              return _this._database.count(_this._collectionName);
+
+            case 10:
+              _context.t6 = _context.sent;
+              _context.t7 = _context.t6 + 1;
+              _context.t8 = {
+                created_at: _context.t5,
+                product_id: _context.t7
+              };
+              _context.t9 = (0, _context.t2)(_context.t3, _context.t4, _context.t8);
+              _context.next = 16;
+              return _context.t0.insertOne.call(_context.t0, _context.t1, _context.t9);
+
+            case 16:
+              return _context.abrupt('return', _context.sent);
+
+            case 17:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.deleteByID = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this._database.remove(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x2) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.getAll = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              // TODO - this should probably just query the organization
+              query = userID ? { ownerID: userID } : {};
+              _context3.next = 3;
+              return _this._database.find(_this._collectionName, query);
+
+            case 3:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 4:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function () {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.getByID = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 3:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x4) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.getByIDOrSlug = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIDOrSlug) {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              _context5.next = 2;
+              return _this._database.findOne(_this._collectionName, {
+                $or: [{ product_id: productIDOrSlug }, { slug: productIDOrSlug }]
+              });
+
+            case 2:
+              return _context5.abrupt('return', _context5.sent);
+
+            case 3:
+            case 'end':
+              return _context5.stop();
+          }
+        }
+      }, _callee5, _this);
+    }));
+
+    return function (_x5) {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.updateByID = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productID, product) {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
+        while (1) {
+          switch (_context6.prev = _context6.next) {
+            case 0:
+              _context6.next = 2;
+              return _this._database.findAndModify(_this._collectionName, { _id: productID }, { $set: (0, _extends3.default)({}, product) });
+
+            case 2:
+              return _context6.abrupt('return', _context6.sent);
+
+            case 3:
+            case 'end':
+              return _context6.stop();
+          }
+        }
+      }, _callee6, _this);
+    }));
+
+    return function (_x6, _x7) {
+      return _ref6.apply(this, arguments);
+    };
+  }();
+
+  this._formatProduct = function () {
+    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(product) {
+      var slug, existingProduct;
+      return _regenerator2.default.wrap(function _callee7$(_context7) {
+        while (1) {
+          switch (_context7.prev = _context7.next) {
+            case 0:
+              slug = (product.name.trim() + ' ' + product.hardware_version.trim()).toLowerCase().replace(/\s+/g, '-') // Replace spaces with -
+              .replace(/[^\w-]+/g, '') // Remove all non-word chars
+              .replace(/--+/g, '-') // Replace multiple - with single -
+              .replace(/^-+/, '') // Trim - from start of text
+              .replace(/-+$/, ''); // Trim - from end of text
+
+              _context7.next = 3;
+              return _this._database.findOne(_this._collectionName, {
+                slug: slug
+              });
+
+            case 3:
+              existingProduct = _context7.sent;
+
+              if (!(existingProduct && existingProduct.id !== product.id)) {
+                _context7.next = 6;
+                break;
+              }
+
+              throw new Error('Product name or version already in use');
+
+            case 6:
+              return _context7.abrupt('return', (0, _extends3.default)({}, product, {
+                slug: slug
+              }));
+
+            case 7:
+            case 'end':
+              return _context7.stop();
+          }
+        }
+      }, _callee7, _this);
+    }));
+
+    return function (_x8) {
+      return _ref7.apply(this, arguments);
+    };
+  }();
+
+  this._database = database;
+};
+
+exports.default = ProductDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/ProductFirmwareDatabaseRepository.js b/dist/repository/ProductFirmwareDatabaseRepository.js
new file mode 100644
index 00000000..807470f5
--- /dev/null
+++ b/dist/repository/ProductFirmwareDatabaseRepository.js
@@ -0,0 +1,194 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var ProductFirmwareDatabaseRepository = function ProductFirmwareDatabaseRepository(database) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, ProductFirmwareDatabaseRepository);
+  this._collectionName = _collectionNames2.default.PRODUCT_FIRMWARE;
+
+  this.create = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
+                updated_at: new Date()
+              }));
+
+            case 2:
+              return _context.abrupt('return', _context.sent);
+
+            case 3:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function (_x) {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this.deleteByID = function () {
+    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      return _regenerator2.default.wrap(function _callee2$(_context2) {
+        while (1) {
+          switch (_context2.prev = _context2.next) {
+            case 0:
+              _context2.next = 2;
+              return _this._database.remove(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context2.abrupt('return', _context2.sent);
+
+            case 3:
+            case 'end':
+              return _context2.stop();
+          }
+        }
+      }, _callee2, _this);
+    }));
+
+    return function (_x2) {
+      return _ref2.apply(this, arguments);
+    };
+  }();
+
+  this.getAll = function () {
+    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var query;
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
+        while (1) {
+          switch (_context3.prev = _context3.next) {
+            case 0:
+              // TODO - this should probably just query the organization
+              query = userID ? { ownerID: userID } : {};
+              _context3.next = 3;
+              return _this._database.find(_this._collectionName, query);
+
+            case 3:
+              return _context3.abrupt('return', _context3.sent);
+
+            case 4:
+            case 'end':
+              return _context3.stop();
+          }
+        }
+      }, _callee3, _this);
+    }));
+
+    return function () {
+      return _ref3.apply(this, arguments);
+    };
+  }();
+
+  this.getAllByProductID = function () {
+    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
+      return _regenerator2.default.wrap(function _callee4$(_context4) {
+        while (1) {
+          switch (_context4.prev = _context4.next) {
+            case 0:
+              _context4.next = 2;
+              return _this._database.find(_this._collectionName, { product_id: productID });
+
+            case 2:
+              return _context4.abrupt('return', _context4.sent);
+
+            case 3:
+            case 'end':
+              return _context4.stop();
+          }
+        }
+      }, _callee4, _this);
+    }));
+
+    return function (_x4) {
+      return _ref4.apply(this, arguments);
+    };
+  }();
+
+  this.getByID = function () {
+    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
+        while (1) {
+          switch (_context5.prev = _context5.next) {
+            case 0:
+              _context5.next = 2;
+              return _this._database.findOne(_this._collectionName, { _id: id });
+
+            case 2:
+              return _context5.abrupt('return', _context5.sent);
+
+            case 3:
+            case 'end':
+              return _context5.stop();
+          }
+        }
+      }, _callee5, _this);
+    }));
+
+    return function (_x5) {
+      return _ref5.apply(this, arguments);
+    };
+  }();
+
+  this.updateByID = function () {
+    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productFirmwareID, productFirmware) {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
+        while (1) {
+          switch (_context6.prev = _context6.next) {
+            case 0:
+              _context6.next = 2;
+              return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, { updated_at: new Date() }) });
+
+            case 2:
+              return _context6.abrupt('return', _context6.sent);
+
+            case 3:
+            case 'end':
+              return _context6.stop();
+          }
+        }
+      }, _callee6, _this);
+    }));
+
+    return function (_x6, _x7) {
+      return _ref6.apply(this, arguments);
+    };
+  }();
+
+  this._database = database;
+};
+
+exports.default = ProductFirmwareDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/collectionNames.js b/dist/repository/collectionNames.js
index b3c155e2..1ac6376d 100644
--- a/dist/repository/collectionNames.js
+++ b/dist/repository/collectionNames.js
@@ -8,6 +8,10 @@ Object.defineProperty(exports, "__esModule", {
 var COLLECTION_NAMES = {
   DEVICE_ATTRIBUTES: 'deviceAttributes',
   DEVICE_KEYS: 'deviceKeys',
+  ORGANIZATIONS: 'organizations',
+  PRODUCT_CONFIGS: 'productConfigs',
+  PRODUCT_FIRMWARE: 'productFirmware',
+  PRODUCTS: 'products',
   USERS: 'users',
   WEBHOOKS: 'webhooks'
 };
diff --git a/examples/Products.md b/examples/Products.md
new file mode 100644
index 00000000..e365ce3b
--- /dev/null
+++ b/examples/Products.md
@@ -0,0 +1,330 @@
+# Products API
+
+The public cloud supports a products API through the admin console. Many of
+these endpoints are undocumented and really require the UI to be user friendly.
+In order to support different hardware/firmware versions, we have added a
+subset of the products API which by default will create a single organization
+that you can have multiple products under. We haven't implemented the
+permissions system that comes with organizations so you can't rely on this to
+handle account permissions.
+
+## Using the API
+In order to user this API, you will need to call the endpoints using an admin
+access token.  We use this model because the admin account is the only account
+which gets added to the organization. We currently haven't implemented any
+organization endpoints which would allow you to add more users to the
+organization. This adds a small amount of security in that you will only be
+able to upload new firmware or edit products if you have the admin's access
+token.
+
+## Products Initial Setup
+1. Grab the admin access token. This is logged to the console when the server
+starts up.
+2. Call the [Product Post Endpoint](#product-create) to create your first product.
+3. Call the [Firmware Post Endpoint](#firmware-create) to add a new firmware.  You must set `current` to true in order to activate the firmware for your fleet.
+
+## API
+
+  ##### Product List
+  Example cURL:
+  ```
+  curl -X GET \
+    http://172.20.10.6:8080/v1/products/ \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache'
+  ```
+  Sample Response:
+  ```
+  {
+      "products": [
+          {
+              "description": "Test description",
+              "hardware_version": "v1.0.1",
+              "name": "Test Device 2",
+              "platform_id": 6,
+              "type": "Consumer",
+              "organization": "rIoYeEmVmEzPPzgT",
+              "slug": "test-device-2-v101",
+              "created_at": "2017-07-21T01:33:23.875Z",
+              "id": "7R04hBg30JAiDcVs",
+              "config_id": "wLqfYW6Hf9MCYWEH"
+          },
+          {
+              "description": "Test description",
+              "hardware_version": "v1.0.0",
+              "name": "Test Device",
+              "platform_id": 6,
+              "type": "Consumer",
+              "organization": "rIoYeEmVmEzPPzgT",
+              "slug": "test-device-v100",
+              "created_at": "2017-07-21T01:33:02.555Z",
+              "id": "sYhdTrUyIzywIyl3",
+              "config_id": "qZ9AjcZbOtBDtAtA"
+          }
+      ]
+  }
+  ```
+  ##### Product Get By ID or Slug
+  Example cURL:
+  ```
+  curl -X GET \
+    http://172.20.8.40:8080/v1/products/test-device-2-v101 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+  }'
+  ```
+  Sample Response:
+  ```
+  {
+      "product": [
+          {
+              "description": "Test description",
+              "hardware_version": "v1.0.1",
+              "name": "Test Device 2",
+              "platform_id": 6,
+              "type": "Consumer",
+              "organization": "rIoYeEmVmEzPPzgT",
+              "slug": "test-device-2-v101",
+              "created_at": "2017-07-21T01:33:23.875Z",
+              "id": "7R04hBg30JAiDcVs",
+              "config_id": "wLqfYW6Hf9MCYWEH"
+          }
+      ]
+  }
+  ```
+  ##### Product Create
+  `Platform IDs`:
+  * 0 - Core
+  * 6 - Photon
+  * 8 - P1
+  * 10 - Electron
+  * 103- Bluz
+
+  `Product Type`:
+  * Consumer
+  * Hobbyist
+  * Industrial
+
+  Example cURL:
+  ```
+  curl -X POST \
+    http://172.20.8.40:8080/v1/products \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+    -d '{
+  	"product": {
+  		"description": "Test description",
+  		"hardware_version": "v1.0.0",
+  		"name": "Test Device",
+  		"platform_id": 6,
+  		"type": "Consumer"
+  	}
+  }'
+  ```
+  Sample Response:
+  ```
+  {
+      "product": [
+          {
+              "description": "Test description",
+              "hardware_version": "v1.0.0",
+              "name": "Test Device",
+              "platform_id": 6,
+              "type": "Consumer",
+              "organization": "rIoYeEmVmEzPPzgT",
+              "slug": "test-device-v100",
+              "created_at": "2017-07-20T20:48:43.442Z",
+              "id": 1,
+              "config_id": "IxjRmTWEH1nsxxOz"
+          }
+      ]
+  }
+  ```  
+  ##### Product Update
+  Example cURL:
+  ```
+  curl -X PUT \
+    http://172.20.8.40:8080/v1/products \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+    -d '{
+  	"product": {
+          "description": "Test description",
+          "hardware_version": "v1.0.1",
+          "name": "Test Device 2",
+          "platform_id": 6,
+          "type": "Consumer",
+          "organization": "rIoYeEmVmEzPPzgT",
+          "slug": "test-device-2-v101",
+          "created_at": "2017-07-21T01:33:23.875Z",
+          "id": 1,
+          "config_id": "wLqfYW6Hf9MCYWEH"
+      }
+  }'
+  ```
+  Sample Response:
+  ```
+  {
+      "product": [
+          {
+              "description": "Test description",
+              "hardware_version": "v1.0.1",
+              "name": "Test Device 2",
+              "platform_id": 6,
+              "type": "Consumer",
+              "organization": "rIoYeEmVmEzPPzgT",
+              "slug": "test-device-2-v101",
+              "created_at": "2017-07-21T01:33:23.875Z",
+              "id": 1,
+              "config_id": "wLqfYW6Hf9MCYWEH"
+          }
+      ]
+  }
+  ```
+  ##### Product Delete
+  Example cURL:
+  ```
+  curl -X DELETE \
+    http://172.20.8.40:8080/v1/products/test-device-2-v101 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+  ```
+  ##### Product Config Get
+  Example cURL:
+  ```
+  curl -X GET \
+    http://172.20.8.40:8080/v1/products/test-device-v100/config \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+    -H 'postman-token: 325462d2-a6e3-4b6c-d7b7-cf0c7fe255ff'
+  ```
+  Sample Response:
+  ```
+  {
+      "product_configuration": {
+          "org_id": "rIoYeEmVmEzPPzgT",
+          "product_id": "sYhdTrUyIzywIyl3",
+          "id": "qZ9AjcZbOtBDtAtA"
+      }
+  }
+  ```
+  ##### Firmware List
+  Example cURL:
+  ```
+  curl -X GET \
+    http://172.20.8.40:8080/v1/products/test-device-v100/firmware/ \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+  ```
+  Sample Response:
+  ```
+  [
+      {
+          "current": "false",
+          "description": "Test Description\n",
+          "device_count": 0,
+          "name": "photon_0.5.3_firmware_1500742294177.bin",
+          "product_id": "1",
+          "size": 36088,
+          "title": "Test Title",
+          "version": "2",
+          "updated_at": "2017-07-22T16:55:48.705Z",
+          "id": "SJVuRZQxBGyembNH"
+      }
+  ]
+  ```
+  ##### Firmware Get
+  Example cURL:
+  ```
+  curl -X GET \
+    http://172.20.8.40:8080/v1/products/test-device-v100/firmware/2 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+  ```
+  Sample Response:
+  ```
+  {
+      "current": "false",
+      "description": "Test Description\n",
+      "device_count": 0,
+      "name": "photon_0.5.3_firmware_1500742294177.bin",
+      "product_id": "1",
+      "size": 36088,
+      "title": "Test Title",
+      "version": "2",
+      "updated_at": "2017-07-22T16:55:48.705Z"
+  }
+  ```
+  ##### Firmware Create
+  Example cURL:
+  ```
+  curl -X POST \
+    http://172.20.8.40:8080/v1/products/test-device-v100/firmware \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
+    -F version=2 \
+    -F current=false \
+    -F 'binary=@C:\photon_tinker.bin' \
+    -F 'title=Test Title' \
+    -F 'description=Test Description
+  '
+  ```
+  Sample Response:
+  ```
+  {
+      "current": "false",
+      "description": "Test Description\n",
+      "device_count": 0,
+      "name": "photon_tinker.bin",
+      "product_id": 1,
+      "size": 3952,
+      "title": "Test Title",
+      "updated_at": "2017-07-22T14:48:27.120Z",
+      "version": "2",
+  }
+  ```
+  ##### Firmware Update
+  Example cURL:
+  ```
+  curl -X PUT \
+    http://172.20.8.40:8080/v1/products/test-device-v100/firmware/2 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+    -d '{
+      "current": "true",
+      "description": "Test Description asdfasdfasdfas\n",
+      "title": "Test Title"
+  }'
+  ```
+  Sample Response:
+  ```
+  {
+      "current": "true",
+      "description": "Test Description asdfasdfasdfas\n",
+      "device_count": 0,
+      "name": "photon_0.5.3_firmware_1500742294177.bin",
+      "product_id": "1",
+      "size": 36088,
+      "title": "Test Title",
+      "version": "2",
+      "updated_at": "2017-07-22T17:14:43.978Z"
+  }
+  ```
+  ##### Firmware Delete
+  Example cURL:
+  ```
+  curl -X DELETE \
+    http://172.20.8.40:8080/v1/products/test-device-v100/firmware/2 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json'
+  ```
diff --git a/examples/azure-mongodb/main.js b/examples/azure-mongodb/main.js
index 59b70342..fa194c31 100644
--- a/examples/azure-mongodb/main.js
+++ b/examples/azure-mongodb/main.js
@@ -37,7 +37,7 @@ defaultBindings(container, settings);
 // you have to override database bindings to use MongoDB instead of neDB:
 container.bindValue('DATABASE_URL', settings.DB_CONFIG.URL);
 container.bindValue('DATABASE_OPTIONS', settings.DB_CONFIG.OPTIONS);
-container.bindClass('Database', MongoDb, ['DATABASE_URL', 'DATABASE_OPTIONS']);
+container.bindClass('IDatabase', MongoDb, ['DATABASE_URL', 'DATABASE_OPTIONS']);
 
 function promisify(
   call: (serviceCallback: (error: StorageError, result: T) => void) => void,
diff --git a/package.json b/package.json
index 1b24af70..70429eb6 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
     "babel-cli": "^6.22.2",
     "babel-runtime": "^6.22.0",
     "basic-auth-parser": "0.0.2",
-    "binary-version-reader": "^0.5.0",
+    "binary-version-reader": "^0.5.1",
     "body-parser": "^1.15.2",
     "bunyan": "^1.8.10",
     "bunyan-middleware": "^0.8.0",
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 7c846549..b1a6b61c 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -37,7 +37,7 @@ const injectUserMiddleware = (container: Container): Middleware => (
     const user = token && token.user;
     // eslint-disable-next-line no-param-reassign
     (request: any).user = user;
-    container.constitute('UserRepository').setCurrentUser(user);
+    container.constitute('IUserRepository').setCurrentUser(user);
   }
   next();
 };
@@ -135,6 +135,19 @@ export default (
           } = request.body;
 
           try {
+            (allowedUploads || [])
+              .forEach(
+                ({ maxCount, name }: { maxCount: number, name: string }) => {
+                  if (!name || !request.files) {
+                    return;
+                  }
+                  const file = (request.files: any)[name];
+                  if (!file) {
+                    return;
+                  }
+                  body[name] = maxCount === 1 ? file[0] : file;
+                },
+              );
             const functionResult = mappedFunction.call(
               controllerInstance,
               ...values,
@@ -146,7 +159,10 @@ export default (
                 ? await Promise.race([
                     functionResult,
                     new Promise(
-                      (resolve: () => void, reject: () => void): number =>
+                      (
+                        resolve: () => void,
+                        reject: (error: Error) => void,
+                      ): number =>
                         setTimeout(
                           (): void => reject(new Error('timeout')),
                           settings.API_TIMEOUT,
@@ -162,6 +178,7 @@ export default (
               response.status(functionResult.status).json(functionResult.data);
             }
           } catch (error) {
+            console.log(error);
             const httpError = new HttpError(error);
             response.status(httpError.status).json({
               error: httpError.message,
@@ -177,18 +194,16 @@ export default (
     response.sendStatus(404);
   });
 
-  (app: any).use(
-    (
-      error: Error,
-      request: $Request,
-      response: $Response,
-      // eslint-disable-next-line no-unused-vars
-      next: NextFunction,
-    ) => {
-      response.status(400).json({
-        error: error.code ? error.code : error,
-        ok: false,
-      });
-    },
-  );
+  (app: any).use((
+    error: Error,
+    request: $Request,
+    response: $Response,
+    // eslint-disable-next-line no-unused-vars
+    next: NextFunction,
+  ) => {
+    response.status(400).json({
+      error: error.code ? error.code : error,
+      ok: false,
+    });
+  });
 };
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index ccc40b53..cb8410b6 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -1,6 +1,7 @@
 // @flow
 
 import type DeviceManager from '../managers/DeviceManager';
+import type { File } from 'express';
 import type { Device } from '../types';
 import type { DeviceAPIType } from '../lib/deviceToAPI';
 
@@ -123,8 +124,9 @@ class DevicesController extends Controller {
     deviceID: string,
     postBody: {
       app_id?: string,
-      name?: string,
+      file?: File,
       file_type?: 'binary',
+      name?: string,
       signal?: '1' | '0',
     },
   ): Promise<*> {
@@ -164,16 +166,12 @@ class DevicesController extends Controller {
     }
 
     // 4 flash device with custom application
-    if (this.request.files && !(this.request.files: any).file) {
+    const file = postBody.file;
+    if (!file) {
       throw new Error('Firmware file not provided');
     }
 
-    const file = this.request.files && (this.request.files: any).file[0];
-
-    if (
-      file &&
-      (file.originalname === 'binary' || file.originalname.endsWith('.bin'))
-    ) {
+    if (file.originalname === 'binary' || file.originalname.endsWith('.bin')) {
       const flashResult = await this._deviceManager.flashBinary(deviceID, file);
 
       return this.ok({ id: deviceID, status: flashResult.status });
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 5bd0d0eb..727cdb81 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -1,89 +1,357 @@
 // @flow
 /* eslint-disable */
 
-import type DeviceManager from '../managers/DeviceManager';
+import type { File } from 'express';
+import type {
+  IOrganizationRepository,
+  IProductConfigRepository,
+  IProductFirmwareRepository,
+  IProductRepository,
+  Product,
+  ProductFirmware,
+} from '../types';
 
 import Controller from './Controller';
+import allowUpload from '../decorators/allowUpload';
+import { HalModuleParser } from 'binary-version-reader';
 import httpVerb from '../decorators/httpVerb';
 import route from '../decorators/route';
 import HttpError from '../lib/HttpError';
 
+type ProductFirmwareUpload = {
+  current: boolean,
+  description: string,
+  binary: File,
+  title: string,
+  version: number,
+};
+
 class ProductsController extends Controller {
-  _deviceManager: DeviceManager;
+  _organizationRepository: IOrganizationRepository;
+  _productConfigRepository: IProductConfigRepository;
+  _productFirmwareRepository: IProductFirmwareRepository;
+  _productRepository: IProductRepository;
 
-  constructor(deviceManager: DeviceManager) {
+  constructor(
+    organizationRepository: IOrganizationRepository,
+    productRepository: IProductRepository,
+    productConfigRepository: IProductConfigRepository,
+    productFirmwareRepository: IProductFirmwareRepository,
+  ) {
     super();
 
-    this._deviceManager = deviceManager;
+    this._organizationRepository = organizationRepository;
+    this._productConfigRepository = productConfigRepository;
+    this._productFirmwareRepository = productFirmwareRepository;
+    this._productRepository = productRepository;
   }
 
   @httpVerb('get')
   @route('/v1/products')
   async getProducts(): Promise<*> {
-    throw new HttpError('not supported in the current server version');
+    const products = await this._productRepository.getAll();
+    return this.ok({ products: products.map(this._formatProduct) });
   }
 
   @httpVerb('post')
   @route('/v1/products')
-  async createProduct(): Promise<*> {
-    throw new HttpError('not supported in the current server version');
+  async createProduct(model: { product: $Shape }): Promise<*> {
+    if (!model.product) {
+      return this.bad('You must provide a product');
+    }
+
+    const missingFields = [
+      'description',
+      'hardware_version',
+      'name',
+      'platform_id',
+      'type',
+    ].filter(key => !model.product[key]);
+    if (missingFields.length) {
+      return this.bad(`Missing fields: ${missingFields.join(', ')}`);
+    }
+
+    const organizations = await this._organizationRepository.getByUserID(
+      this.user.id,
+    );
+    if (!organizations.length) {
+      return this.bad("You don't have access to any organizations");
+    }
+
+    const organizationID = organizations[0].id;
+    model.product.organization = organizationID;
+    const product = await this._productRepository.create(model.product);
+    const config = await this._productConfigRepository.create({
+      org_id: organizationID,
+      product_id: product.id,
+    });
+    product.config_id = config.id;
+    await this._productRepository.updateByID(product.id, product);
+    // For some reason the spark API returns it in an array.
+    return this.ok({ product: [this._formatProduct(product)] });
   }
 
   @httpVerb('get')
-  @route('/v1/products/:productIdOrSlug')
-  async getProduct(productIdOrSlug: string): Promise<*> {
-    throw new HttpError('Not implemented');
+  @route('/v1/products/:productIDOrSlug')
+  async getProduct(productIDOrSlug: string): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad('Product does not exist', 404);
+    }
+
+    return this.ok({ product: [this._formatProduct(product)] });
   }
 
-  @httpVerb('post')
-  @route('/v1/products/:productIdOrSlug/device_claims')
-  async generateClaimCode(productIdOrSlug: string): Promise<*> {
-    throw new HttpError('not supported in the current server version');
+  @httpVerb('put')
+  @route('/v1/products/:productIDOrSlug')
+  async updateProduct(
+    productIDOrSlug: string,
+    model: { product: Product },
+  ): Promise<*> {
+    if (!model.product) {
+      return this.bad('You must provide a product');
+    }
+
+    const missingFields = [
+      'config_id',
+      'description',
+      'hardware_version',
+      'id',
+      'name',
+      'organization',
+      'platform_id',
+      'type',
+    ].filter(key => !model.product[key]);
+    if (missingFields.length) {
+      return this.bad(`Missing fields: ${missingFields.join(', ')}`);
+    }
+
+    let product = await this._productRepository.getByIDOrSlug(productIDOrSlug);
+    if (!product) {
+      return this.bad(`Product ${productIDOrSlug} doesn't exist`);
+    }
+
+    product = await this._productRepository.updateByID(product.id, {
+      ...product,
+      ...model.product,
+    });
+
+    // For some reason the spark API returns it in an array.
+    return this.ok({ product: [this._formatProduct(product)] });
+  }
+
+  @httpVerb('delete')
+  @route('/v1/products/:productIDOrSlug')
+  async deleteProduct(productIDOrSlug: string): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad('Product does not exist', 404);
+    }
+
+    await this._productRepository.deleteByID(product.id);
+
+    return this.ok();
   }
 
   @httpVerb('get')
-  @route('/v1/products/:productIdOrSlug/firmware')
-  async getFirmware(productIdOrSlug: string): Promise<*> {
-    throw new HttpError('Not implemented');
+  @route('/v1/products/:productIDOrSlug/config')
+  async getConfig(productIDOrSlug: string): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad('Product does not exist', 404);
+    }
+
+    const config = await this._productConfigRepository.getByProductID(
+      product.id,
+    );
+
+    return this.ok({ product_configuration: config });
+  }
+
+  @httpVerb('get')
+  @route('/v1/products/:productIDOrSlug/firmware')
+  async getFirmware(productIDOrSlug: string): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad('Product does not exist', 404);
+    }
+
+    const firmwares = await this._productFirmwareRepository.getAllByProductID(
+      product.product_id,
+    );
+
+    return this.ok(firmwares.map(({ data, ...firmware }) => firmware));
+  }
+
+  @httpVerb('get')
+  @route('/v1/products/:productIDOrSlug/firmware/:version')
+  async getSingleFirmware(
+    productIDOrSlug: string,
+    version: number,
+  ): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+    const firmwareList = await this._productFirmwareRepository.getAllByProductID(
+      product.product_id,
+    );
+
+    const existingFirmware = firmwareList.find(
+      firmware => firmware.version === version,
+    );
+    if (!existingFirmware) {
+      return this.bad(`Firmware version ${version} does not exist`);
+    }
+
+    const { data, id, ...output } = existingFirmware;
+    return this.ok(output);
   }
 
-  // {version: number, name: 'current', binary: File, title: string, description: string}
   @httpVerb('post')
-  @route('/v1/products/:productIdOrSlug/firmware')
-  async getFirmware(productIdOrSlug: string): Promise<*> {
-    /*
-    {
-    "updated_at": "2017-01-23T05:55:11.592Z",
-    "uploaded_on": "2017-01-23T05:55:11.592Z",
-    "uploaded_by": {
-      "__v": 3,
-      "_id": "aaa",
-      "access_token": "asdf",
-      "access_token_expires_at": "2015-05-14T02:46:54.216Z",
-      "created_at": "2014-12-05T14:17:32.000Z",
-      "updated_at": "2017-01-14T15:45:26.877Z",
-      "username": "foo@gmail.com",
-      "tos": {
-        "accepted": true
+  @route('/v1/products/:productIDOrSlug/firmware')
+  @allowUpload('binary', 1)
+  async addFirmware(
+    productIDOrSlug: string,
+    body: ProductFirmwareUpload,
+  ): Promise<*> {
+    const missingFields = ['binary', 'description', 'title'].filter(
+      key => !body[key],
+    );
+    if (missingFields.length) {
+      return this.bad(`Missing fields: ${missingFields.join(', ')}`);
+    }
+
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+
+    const parser = new HalModuleParser();
+    const moduleInfo = await new Promise((resolve, reject) =>
+      parser
+        .parseBuffer({ fileBuffer: body.binary.buffer })
+        .then(resolve, reject),
+    );
+
+    if (moduleInfo.crc.ok !== 1) {
+      return this.bad('Invalid CRC. Try recompiling the firmware');
+    }
+
+    const firmwarePlatformID = moduleInfo.prefixInfo.platformID;
+    if (firmwarePlatformID !== product.platform_id) {
+      return this.bad(
+        `Firmware had incorrect platform ID ${firmwarePlatformID}. Expected ` +
+          product.platform_id,
+      );
+    }
+
+    const { productId, productVersion } = moduleInfo.suffixInfo;
+    if (productId !== parseInt(product.product_id, 10)) {
+      return this.bad(
+        `Firmware had incorrect product ID ${productId}. Expected ` +
+          product.product_id,
+      );
+    }
+
+    if (productVersion !== parseInt(body.version, 10)) {
+      return this.bad(
+        `Firmware had incorrect product version ${productVersion}. Expected ` +
+          body.version,
+      );
+    }
+
+    const firmware = await this._productFirmwareRepository.create({
+      current: body.current,
+      data: body.binary.buffer,
+      description: body.description,
+      device_count: 0,
+      name: body.binary.originalname,
+      product_id: product.product_id,
+      size: body.binary.size,
+      title: body.title,
+      version: body.version,
+    });
+    const { data, id, ...output } = firmware;
+    return this.ok(output);
+  }
+
+  @httpVerb('put')
+  @route('/v1/products/:productIDOrSlug/firmware/:version')
+  async updateFirmware(
+    productIDOrSlug: string,
+    version: number,
+    body: $Shape,
+  ): Promise<*> {
+    const { current, description, title } = body;
+    body = {
+      current,
+      description,
+      title,
+    };
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+    const firmwareList = await this._productFirmwareRepository.getAllByProductID(
+      product.product_id,
+    );
+
+    const existingFirmware = firmwareList.find(
+      firmware => firmware.version === version,
+    );
+    if (!existingFirmware) {
+      return this.bad(`Firmware version ${version} does not exist`);
+    }
+
+    const firmware = await this._productFirmwareRepository.updateByID(
+      existingFirmware.id,
+      {
+        ...existingFirmware,
+        ...body,
       },
-      "subscription_ids": [
-        3632,
-        3633,
-        7312
-      ]
-    },
-    "version": 1,
-    "product_id": 647,
-    "size": 40648,
-    "name": "p1_firmware_1485150795661.bin",
-    "title": "Test",
-    "description": "test",
-    "_id": "asdf",
-    "current": false,
-    "device_count": 0
-  }
-  */
-    throw new HttpError('Not implemented');
+    );
+    const { data, id, ...output } = firmware;
+    return this.ok(output);
+  }
+
+  @httpVerb('delete')
+  @route('/v1/products/:productIDOrSlug/firmware/:version')
+  async deleteFirmware(productIDOrSlug: string, version: number): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+    const firmwareList = await this._productFirmwareRepository.getAllByProductID(
+      product.product_id,
+    );
+
+    const existingFirmware = firmwareList.find(
+      firmware => firmware.version === version,
+    );
+    if (!existingFirmware) {
+      return this.bad(`Firmware version ${version} does not exist`);
+    }
+
+    await this._productFirmwareRepository.deleteByID(existingFirmware.id);
+
+    return this.ok();
   }
 
   @httpVerb('get')
@@ -118,24 +386,6 @@ class ProductsController extends Controller {
     throw new HttpError('not supported in the current server version');
   }
 
-  @httpVerb('get')
-  @route('/v1/products/:productIdOrSlug/config')
-  async getConfig(productIdOrSlug: string): Promise<*> {
-    /*
-    {
-      "product_configuration": [
-        {
-          "org_id": "57a0a70786ddb6f9501032d6",
-          "__v": 0,
-          "id": "57a0a71086ddb6f9501032d8",
-          "product_id": 647
-        }
-      ]
-    }
-    */
-    throw new HttpError('Not implemented');
-  }
-
   @httpVerb('get')
   @route('/v1/products/:productIdOrSlug/events/:eventPrefix?*')
   async getEvents(productIdOrSlug: string, eventName: string): Promise<*> {
@@ -150,6 +400,12 @@ class ProductsController extends Controller {
   ): Promise<*> {
     throw new HttpError('not supported in the current server version');
   }
+
+  _formatProduct(product: Product): $Shape {
+    const { product_id, ...output } = product;
+    output.id = product_id;
+    return output;
+  }
 }
 
 export default ProductsController;
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index a70f9a69..992b00d7 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -23,6 +23,10 @@ import DeviceFirmwareFileRepository from './repository/DeviceFirmwareFileReposit
 import NeDb from './repository/NeDb';
 import DeviceAttributeDatabaseRepository from './repository/DeviceAttributeDatabaseRepository';
 import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepository';
+import OrganizationDatabaseRepository from './repository/OrganizationDatabaseRepository';
+import ProductDatabaseRepository from './repository/ProductDatabaseRepository';
+import ProductConfigDatabaseRepository from './repository/ProductConfigDatabaseRepository';
+import ProductFirmwareDatabaseRepository from './repository/ProductFirmwareDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
 import settings from './settings';
@@ -54,11 +58,11 @@ export default (container: Container, newSettings: Settings) => {
     ['OAuthModel'],
   );
 
-  container.bindClass('OAuthModel', OAuthModel, ['UserRepository']);
+  container.bindClass('OAuthModel', OAuthModel, ['IUserRepository']);
 
   container.bindClass('OAuthServer', OAuthServer, ['OAUTH_SETTINGS']);
 
-  container.bindClass('Database', NeDb, ['DATABASE_PATH']);
+  container.bindClass('IDatabase', NeDb, ['DATABASE_PATH']);
 
   // lib
   container.bindClass('WebhookLogger', WebhookLogger, []);
@@ -73,26 +77,32 @@ export default (container: Container, newSettings: Settings) => {
   ]);
   container.bindClass('EventsController', EventsController, ['EventManager']);
   container.bindClass('PermissionManager', PermissionManager, [
-    'DeviceAttributeRepository',
-    'UserRepository',
-    'WebhookRepository',
+    'IDeviceAttributeRepository',
+    'IOrganizationRepository',
+    'IUserRepository',
+    'IWebhookRepository',
     'OAuthServer',
   ]);
   container.bindClass('OauthClientsController', OauthClientsController, []);
-  container.bindClass('ProductsController', ProductsController, []);
+  container.bindClass('ProductsController', ProductsController, [
+    'IOrganizationRepository',
+    'IProductRepository',
+    'IProductConfigRepository',
+    'IProductFirmwareRepository',
+  ]);
   container.bindClass('ProvisioningController', ProvisioningController, [
     'DeviceManager',
   ]);
-  container.bindClass('UsersController', UsersController, ['UserRepository']);
+  container.bindClass('UsersController', UsersController, ['IUserRepository']);
   container.bindClass('WebhooksController', WebhooksController, [
     'WebhookManager',
   ]);
 
   // managers
   container.bindClass('DeviceManager', DeviceManager, [
-    'DeviceAttributeRepository',
-    'DeviceFirmwareRepository',
-    'DeviceKeyRepository',
+    'IDeviceAttributeRepository',
+    'IDeviceFirmwareRepository',
+    'IDeviceKeyRepository',
     'PermissionManager',
     'EventPublisher',
   ]);
@@ -101,25 +111,44 @@ export default (container: Container, newSettings: Settings) => {
     'EventPublisher',
     'PermissionManager',
     'WebhookLogger',
-    'WebhookRepository',
+    'IWebhookRepository',
   ]);
 
   // Repositories
   container.bindClass(
-    'DeviceAttributeRepository',
+    'IDeviceAttributeRepository',
     DeviceAttributeDatabaseRepository,
-    ['Database'],
+    ['IDatabase'],
   );
   container.bindClass(
-    'DeviceFirmwareRepository',
+    'IDeviceFirmwareRepository',
     DeviceFirmwareFileRepository,
     ['FIRMWARE_DIRECTORY'],
   );
-  container.bindClass('DeviceKeyRepository', DeviceKeyDatabaseRepository, [
-    'Database',
+  container.bindClass('IDeviceKeyRepository', DeviceKeyDatabaseRepository, [
+    'IDatabase',
   ]);
-  container.bindClass('UserRepository', UserDatabaseRepository, ['Database']);
-  container.bindClass('WebhookRepository', WebhookDatabaseRepository, [
-    'Database',
+  container.bindClass(
+    'IOrganizationRepository',
+    OrganizationDatabaseRepository,
+    ['IDatabase'],
+  );
+  container.bindClass('IProductRepository', ProductDatabaseRepository, [
+    'IDatabase',
+  ]);
+  container.bindClass(
+    'IProductConfigRepository',
+    ProductConfigDatabaseRepository,
+    ['IDatabase'],
+  );
+  container.bindClass(
+    'IProductFirmwareRepository',
+    ProductFirmwareDatabaseRepository,
+    ['IDatabase'],
+  );
+
+  container.bindClass('IUserRepository', UserDatabaseRepository, ['IDatabase']);
+  container.bindClass('IWebhookRepository', WebhookDatabaseRepository, [
+    'IDatabase',
   ]);
 };
diff --git a/src/lib/WebhookLogger.js b/src/lib/WebhookLogger.js
index 677eaf74..b7af1257 100644
--- a/src/lib/WebhookLogger.js
+++ b/src/lib/WebhookLogger.js
@@ -9,7 +9,7 @@ class WebhookLogger implements IWebhookLogger {
 
   log(...args: Array) {
     this._lastLog = args;
-    logger.info({ ...args }, 'webhook');
+    logger.info([...args], 'webhook');
   }
 }
 
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index 48f3cabb..ad382c6a 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -2,6 +2,7 @@
 
 import type {
   IDeviceAttributeRepository,
+  IOrganizationRepository,
   IUserRepository,
   IWebhookRepository,
   ProtectedEntityName,
@@ -15,16 +16,19 @@ import Logger from '../lib/logger';
 const logger = Logger.createModuleLogger(module);
 
 class PermissionManager {
+  _organizationRepository: IOrganizationRepository;
   _userRepository: IUserRepository;
   _repositoriesByEntityName: Map = new Map();
   _oauthServer: Object;
 
   constructor(
     deviceAttributeRepository: IDeviceAttributeRepository,
+    organizationRepository: IOrganizationRepository,
     userRepository: IUserRepository,
     webhookRepository: IWebhookRepository,
     oauthServer: Object,
   ) {
+    this._organizationRepository = organizationRepository;
     this._userRepository = userRepository;
     this._repositoriesByEntityName.set(
       'deviceAttributes',
@@ -70,13 +74,10 @@ class PermissionManager {
 
   _createDefaultAdminUser = async (): Promise => {
     try {
-      await this._userRepository.createWithCredentials(
-        {
-          password: settings.DEFAULT_ADMIN_PASSWORD,
-          username: settings.DEFAULT_ADMIN_USERNAME,
-        },
-        'administrator',
-      );
+      await this._userRepository.createWithCredentials({
+        password: settings.DEFAULT_ADMIN_PASSWORD,
+        username: settings.DEFAULT_ADMIN_USERNAME,
+      });
 
       const token = await this._generateAdminToken();
 
@@ -122,7 +123,7 @@ class PermissionManager {
   };
 
   _init = async (): Promise => {
-    const defaultAdminUser = await this._userRepository.getByUsername(
+    let defaultAdminUser = await this._userRepository.getByUsername(
       settings.DEFAULT_ADMIN_USERNAME,
     );
     if (defaultAdminUser) {
@@ -132,6 +133,18 @@ class PermissionManager {
       );
     } else {
       await this._createDefaultAdminUser();
+      defaultAdminUser = await this._userRepository.getByUsername(
+        settings.DEFAULT_ADMIN_USERNAME,
+      );
+    }
+
+    // Set up the organization
+    const organizations = await this._organizationRepository.getAll();
+    if (!organizations.length && defaultAdminUser) {
+      await this._organizationRepository.create({
+        name: 'DEFAULT ORG',
+        user_ids: [defaultAdminUser.id],
+      });
     }
   };
 }
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index afc8207a..df2829c2 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -20,6 +20,15 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
     (async (): Promise => await this._init(url, options))();
   }
 
+  count = async (collectionName: string, query: Object): Promise =>
+    await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise<*> =>
+        await collection.count(this.__translateQuery(query), {
+          timeout: false,
+        }),
+    );
+
   insertOne = async (collectionName: string, entity: Object): Promise<*> =>
     await this.__runForCollection(
       collectionName,
diff --git a/src/repository/NeDb.js b/src/repository/NeDb.js
index 4ceedf34..f4f9033f 100644
--- a/src/repository/NeDb.js
+++ b/src/repository/NeDb.js
@@ -31,6 +31,13 @@ class NeDb extends BaseMongoDb implements IBaseDatabase {
     });
   }
 
+  count = async (collectionName: string, query: Object): Promise =>
+    (await this.__runForCollection(
+      collectionName,
+      async (collection: Object): Promise =>
+        await promisify(collection, 'find', query),
+    )) || 0;
+
   insertOne = async (collectionName: string, entity: Object): Promise<*> =>
     await this.__runForCollection(
       collectionName,
diff --git a/src/repository/OrganizationDatabaseRepository.js b/src/repository/OrganizationDatabaseRepository.js
new file mode 100644
index 00000000..c81e64e6
--- /dev/null
+++ b/src/repository/OrganizationDatabaseRepository.js
@@ -0,0 +1,47 @@
+// @flow
+
+import type { CollectionName } from './collectionNames';
+import type {
+  IBaseDatabase,
+  IOrganizationRepository,
+  Organization,
+} from '../types';
+
+import COLLECTION_NAMES from './collectionNames';
+
+class OrganizationDatabaseRepository implements IOrganizationRepository {
+  _database: IBaseDatabase;
+  _collectionName: CollectionName = COLLECTION_NAMES.ORGANIZATIONS;
+
+  constructor(database: IBaseDatabase) {
+    this._database = database;
+  }
+
+  create = async (model: $Shape): Promise =>
+    await this._database.insertOne(this._collectionName, {
+      ...model,
+    });
+
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
+
+  getAll = async (userID: ?string = null): Promise> => {
+    // TODO - this should probably just query the organization
+    const query = userID ? { ownerID: userID } : {};
+    return await this._database.find(this._collectionName, query);
+  };
+
+  getByUserID = async (userID: string): Promise> =>
+    await this._database.find(this._collectionName, {
+      user_ids: userID,
+    });
+
+  getByID = async (id: string): Promise =>
+    await this._database.findOne(this._collectionName, { _id: id });
+
+  updateByID = async (): Promise => {
+    throw new Error('The method is not implemented');
+  };
+}
+
+export default OrganizationDatabaseRepository;
diff --git a/src/repository/ProductConfigDatabaseRepository.js b/src/repository/ProductConfigDatabaseRepository.js
new file mode 100644
index 00000000..6e31aafe
--- /dev/null
+++ b/src/repository/ProductConfigDatabaseRepository.js
@@ -0,0 +1,47 @@
+// @flow
+
+import type { CollectionName } from './collectionNames';
+import type {
+  IBaseDatabase,
+  IProductConfigRepository,
+  ProductConfig,
+} from '../types';
+
+import COLLECTION_NAMES from './collectionNames';
+
+class ProductConfigDatabaseRepository implements IProductConfigRepository {
+  _database: IBaseDatabase;
+  _collectionName: CollectionName = COLLECTION_NAMES.PRODUCT_CONFIGS;
+
+  constructor(database: IBaseDatabase) {
+    this._database = database;
+  }
+
+  create = async (model: $Shape): Promise =>
+    await this._database.insertOne(this._collectionName, {
+      ...model,
+    });
+
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
+
+  getAll = async (userID: ?string = null): Promise> => {
+    // TODO - this should probably just query the organization
+    const query = userID ? { ownerID: userID } : {};
+    return await this._database.find(this._collectionName, query);
+  };
+
+  getByProductID = async (productID: string): Promise =>
+    await this._database.findOne(this._collectionName, {
+      product_id: productID,
+    });
+
+  getByID = async (id: string): Promise =>
+    await this._database.findOne(this._collectionName, { _id: id });
+
+  updateByID = async (): Promise => {
+    throw new Error('The method is not implemented');
+  };
+}
+
+export default ProductConfigDatabaseRepository;
diff --git a/src/repository/ProductDatabaseRepository.js b/src/repository/ProductDatabaseRepository.js
new file mode 100644
index 00000000..5c4a0195
--- /dev/null
+++ b/src/repository/ProductDatabaseRepository.js
@@ -0,0 +1,73 @@
+// @flow
+
+import type { CollectionName } from './collectionNames';
+import type { IBaseDatabase, IProductRepository, Product } from '../types';
+
+import COLLECTION_NAMES from './collectionNames';
+
+class ProductDatabaseRepository implements IProductRepository {
+  _database: IBaseDatabase;
+  _collectionName: CollectionName = COLLECTION_NAMES.PRODUCTS;
+
+  constructor(database: IBaseDatabase) {
+    this._database = database;
+  }
+
+  create = async (model: $Shape): Promise =>
+    await this._database.insertOne(this._collectionName, {
+      ...(await this._formatProduct(model)),
+      created_at: new Date(),
+      product_id: (await this._database.count(this._collectionName)) + 1,
+    });
+
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
+
+  getAll = async (userID: ?string = null): Promise> => {
+    // TODO - this should probably just query the organization
+    const query = userID ? { ownerID: userID } : {};
+    return await this._database.find(this._collectionName, query);
+  };
+
+  getByID = async (id: string): Promise =>
+    await this._database.findOne(this._collectionName, { _id: id });
+
+  getByIDOrSlug = async (productIDOrSlug: string): Promise =>
+    await this._database.findOne(this._collectionName, {
+      $or: [{ product_id: productIDOrSlug }, { slug: productIDOrSlug }],
+    });
+
+  updateByID = async (productID: string, product: Product): Promise =>
+    await this._database.findAndModify(
+      this._collectionName,
+      { _id: productID },
+      { $set: { ...product } },
+    );
+
+  _formatProduct = async (
+    product: $Shape,
+  ): Promise<$Shape> => {
+    const slug = `${product.name.trim()} ${product.hardware_version.trim()}`
+      .toLowerCase()
+      .replace(/\s+/g, '-') // Replace spaces with -
+      .replace(/[^\w-]+/g, '') // Remove all non-word chars
+      .replace(/--+/g, '-') // Replace multiple - with single -
+      .replace(/^-+/, '') // Trim - from start of text
+      .replace(/-+$/, ''); // Trim - from end of text
+
+    const existingProduct = await this._database.findOne(this._collectionName, {
+      slug,
+    });
+
+    if (existingProduct && existingProduct.id !== product.id) {
+      throw new Error('Product name or version already in use');
+    }
+
+    return {
+      ...product,
+      slug,
+    };
+  };
+}
+
+export default ProductDatabaseRepository;
diff --git a/src/repository/ProductFirmwareDatabaseRepository.js b/src/repository/ProductFirmwareDatabaseRepository.js
new file mode 100644
index 00000000..fe66fa67
--- /dev/null
+++ b/src/repository/ProductFirmwareDatabaseRepository.js
@@ -0,0 +1,54 @@
+// @flow
+
+import type { CollectionName } from './collectionNames';
+import type {
+  IBaseDatabase,
+  IProductFirmwareRepository,
+  ProductFirmware,
+} from '../types';
+
+import COLLECTION_NAMES from './collectionNames';
+
+class ProductFirmwareDatabaseRepository implements IProductFirmwareRepository {
+  _database: IBaseDatabase;
+  _collectionName: CollectionName = COLLECTION_NAMES.PRODUCT_FIRMWARE;
+
+  constructor(database: IBaseDatabase) {
+    this._database = database;
+  }
+
+  create = async (model: $Shape): Promise =>
+    await this._database.insertOne(this._collectionName, {
+      ...model,
+      updated_at: new Date(),
+    });
+
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
+
+  getAll = async (userID: ?string = null): Promise> => {
+    // TODO - this should probably just query the organization
+    const query = userID ? { ownerID: userID } : {};
+    return await this._database.find(this._collectionName, query);
+  };
+
+  getAllByProductID = async (
+    productID: string,
+  ): Promise> =>
+    await this._database.find(this._collectionName, { product_id: productID });
+
+  getByID = async (id: string): Promise =>
+    await this._database.findOne(this._collectionName, { _id: id });
+
+  updateByID = async (
+    productFirmwareID: string,
+    productFirmware: ProductFirmware,
+  ): Promise =>
+    await this._database.findAndModify(
+      this._collectionName,
+      { _id: productFirmwareID },
+      { $set: { ...productFirmware, updated_at: new Date() } },
+    );
+}
+
+export default ProductFirmwareDatabaseRepository;
diff --git a/src/repository/collectionNames.js b/src/repository/collectionNames.js
index 48567561..0006a098 100644
--- a/src/repository/collectionNames.js
+++ b/src/repository/collectionNames.js
@@ -3,12 +3,20 @@
 export type CollectionName =
   | 'deviceAttributes'
   | 'deviceKeys'
+  | 'organizations'
+  | 'products'
+  | 'productConfigs'
+  | 'productFirmware'
   | 'users'
   | 'webhooks';
 
 const COLLECTION_NAMES: { [key: string]: CollectionName } = {
   DEVICE_ATTRIBUTES: 'deviceAttributes',
   DEVICE_KEYS: 'deviceKeys',
+  ORGANIZATIONS: 'organizations',
+  PRODUCT_CONFIGS: 'productConfigs',
+  PRODUCT_FIRMWARE: 'productFirmware',
+  PRODUCTS: 'products',
   USERS: 'users',
   WEBHOOKS: 'webhooks',
 };
diff --git a/src/types.js b/src/types.js
index ef16c6d4..c7abc0ee 100644
--- a/src/types.js
+++ b/src/types.js
@@ -180,21 +180,55 @@ export type RequestOptions = {
   url: string,
 };
 
-export type Product = {
+export type PlatformType =
+  | 0 // Core
+  | 6 // Photon
+  | 8 // P1
+  | 10 // Electron
+  | 103; // Bluz
+
+export type Product = {|
   config_id: string,
   description: string,
   hardware_version: string,
-  id: string,
+  id: string, // This should always be swapped out with product_id when sent to the client
+  latest_firmware_version: number,
   name: string,
   organization: string,
-  product_id: number,
-  requires_activation_codes: boolean,
+  platform_id: PlatformType,
+  product_id: string,
   slug: string,
   type: 'Consumer' | 'Hobbyist' | 'Industrial',
-};
+|};
+
+export type ProductFirmware = {|
+  current: boolean,
+  data: Buffer,
+  description: string,
+  device_count: number,
+  id: string,
+  name: string,
+  product_id: string,
+  size: number,
+  title: string,
+  updated_at: Date,
+  version: number,
+|};
+
+export type Organization = {|
+  id: string,
+  name: string,
+  user_ids: Array,
+|};
+
+export type ProductConfig = {|
+  id: string,
+  org_id: string,
+  product_id: string,
+|};
 
 export interface IBaseRepository {
-  create(model: TModel | $Shape): Promise,
+  create(model: $Shape): Promise,
   deleteByID(id: string): Promise,
   getAll(): Promise>,
   getByID(id: string): Promise,
@@ -203,6 +237,24 @@ export interface IBaseRepository {
 
 export interface IWebhookRepository extends IBaseRepository {}
 
+export interface IProductRepository extends IBaseRepository {
+  getByIDOrSlug(productIDOrSlug: string): Promise,
+}
+
+export interface IProductConfigRepository
+  extends IBaseRepository {
+  getByProductID(productID: string): Promise,
+}
+
+export interface IOrganizationRepository extends IBaseRepository {
+  getByUserID(userID: string): Promise>,
+}
+
+export interface IProductFirmwareRepository
+  extends IBaseRepository {
+  getAllByProductID(productID: string): Promise>,
+}
+
 export interface IDeviceAttributeRepository
   extends IBaseRepository {}
 
@@ -226,6 +278,7 @@ export interface IDeviceFirmwareRepository {
 }
 
 export interface IBaseDatabase {
+  count(collectionName: string, ...args: Array): Promise,
   find(collectionName: string, ...args: Array): Promise<*>,
   findAndModify(collectionName: string, ...args: Array): Promise<*>,
   findOne(collectionName: string, ...args: Array): Promise<*>,
diff --git a/test/DeviceClaimsController.test.js b/test/DeviceClaimsController.test.js
index 1bcbc213..070952fa 100644
--- a/test/DeviceClaimsController.test.js
+++ b/test/DeviceClaimsController.test.js
@@ -38,7 +38,7 @@ test.before(async () => {
     .send(USER_CREDENTIALS);
 
   testUser = await container
-    .constitute('UserRepository')
+    .constitute('IUserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
@@ -84,7 +84,9 @@ test("should return claimCode, and user's devices ids", async t => {
 });
 
 test.after.always(async (): Promise => {
-  await container.constitute('UserRepository').deleteByID(testUser.id);
-  await container.constitute('DeviceAttributeRepository').deleteByID(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteByID(DEVICE_ID);
+  await container.constitute('IUserRepository').deleteByID(testUser.id);
+  await container
+    .constitute('IDeviceAttributeRepository')
+    .deleteByID(DEVICE_ID);
+  await container.constitute('IDeviceKeyRepository').deleteByID(DEVICE_ID);
 });
diff --git a/test/DevicesController.test.js b/test/DevicesController.test.js
index fbca0154..7ab5aeea 100644
--- a/test/DevicesController.test.js
+++ b/test/DevicesController.test.js
@@ -101,7 +101,7 @@ test.before(async () => {
     .send(USER_CREDENTIALS);
 
   testUser = await container
-    .constitute('UserRepository')
+    .constitute('IUserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
@@ -260,7 +260,7 @@ test.serial(
   'should throw an error if device belongs to somebody else',
   async t => {
     const deviceAttributesStub = sinon
-      .stub(container.constitute('DeviceAttributeRepository'), 'getByID')
+      .stub(container.constitute('IDeviceAttributeRepository'), 'getByID')
       .returns({ ownerID: TestData.getID() });
 
     const claimDeviceResponse = await request(app)
@@ -375,7 +375,7 @@ test.serial(
     const knownAppBuffer = new Buffer(knownAppName);
 
     const deviceFirmwareStub = sinon
-      .stub(container.constitute('DeviceFirmwareRepository'), 'getByName')
+      .stub(container.constitute('IDeviceFirmwareRepository'), 'getByName')
       .returns(knownAppBuffer);
 
     const flashKnownAppResponse = await request(app)
@@ -457,17 +457,17 @@ test.serial(
 
 test.after.always(async (): Promise => {
   await TestData.deleteCustomFirmwareBinary(customFirmwareFilePath);
-  await container.constitute('UserRepository').deleteByID(testUser.id);
+  await container.constitute('IUserRepository').deleteByID(testUser.id);
   await container
-    .constitute('DeviceAttributeRepository')
+    .constitute('IDeviceAttributeRepository')
     .deleteByID(CONNECTED_DEVICE_ID);
   await container
-    .constitute('DeviceKeyRepository')
+    .constitute('IDeviceKeyRepository')
     .deleteByID(CONNECTED_DEVICE_ID);
   await container
-    .constitute('DeviceAttributeRepository')
+    .constitute('IDeviceAttributeRepository')
     .deleteByID(DISCONNECTED_DEVICE_ID);
   await container
-    .constitute('DeviceKeyRepository')
+    .constitute('IDeviceKeyRepository')
     .deleteByID(DISCONNECTED_DEVICE_ID);
 });
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index 124b6366..0513ac6e 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -39,7 +39,7 @@ test.before(async () => {
     .send(USER_CREDENTIALS);
 
   testUser = await container
-    .constitute('UserRepository')
+    .constitute('IUserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
@@ -91,7 +91,9 @@ test('should throw an error if public key is not provided', async t => {
 });
 
 test.after.always(async (): Promise => {
-  await container.constitute('UserRepository').deleteByID(testUser.id);
-  await container.constitute('DeviceAttributeRepository').deleteByID(DEVICE_ID);
-  await container.constitute('DeviceKeyRepository').deleteByID(DEVICE_ID);
+  await container.constitute('IUserRepository').deleteByID(testUser.id);
+  await container
+    .constitute('IDeviceAttributeRepository')
+    .deleteByID(DEVICE_ID);
+  await container.constitute('IDeviceKeyRepository').deleteByID(DEVICE_ID);
 });
diff --git a/test/UsersController.test.js b/test/UsersController.test.js
index c59361bf..06d7125d 100644
--- a/test/UsersController.test.js
+++ b/test/UsersController.test.js
@@ -18,7 +18,7 @@ test.serial('should create new user', async t => {
   const response = await request(app).post('/v1/users').send(USER_CREDENTIALS);
 
   user = await container
-    .constitute('UserRepository')
+    .constitute('IUserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   t.is(response.status, 200);
@@ -82,5 +82,5 @@ test.serial('should delete the access token for the user', async t => {
 });
 
 test.after.always(async (): Promise => {
-  await container.constitute('UserRepository').deleteByID(user.id);
+  await container.constitute('IUserRepository').deleteByID(user.id);
 });
diff --git a/test/WebhooksController.test.js b/test/WebhooksController.test.js
index 1bf859e5..cec2e5fe 100644
--- a/test/WebhooksController.test.js
+++ b/test/WebhooksController.test.js
@@ -26,7 +26,7 @@ test.before(async () => {
     .send(USER_CREDENTIALS);
 
   testUser = await container
-    .constitute('UserRepository')
+    .constitute('IUserRepository')
     .getByUsername(USER_CREDENTIALS.username);
 
   const tokenResponse = await request(app)
@@ -145,6 +145,6 @@ test.serial('should delete webhook', async t => {
 });
 
 test.after.always(async (): Promise => {
-  await container.constitute('WebhookRepository').deleteByID(testWebhook.id);
-  await container.constitute('UserRepository').deleteByID(testUser.id);
+  await container.constitute('IWebhookRepository').deleteByID(testWebhook.id);
+  await container.constitute('IUserRepository').deleteByID(testUser.id);
 });
diff --git a/test/setup/settings.js b/test/setup/settings.js
index fc6ff16c..dbf2a67c 100644
--- a/test/setup/settings.js
+++ b/test/setup/settings.js
@@ -15,6 +15,7 @@ export default {
     __dirname,
     '../__test_data__/firmware',
   ),
+  LOG_LEVEL: (process.env.LOG_LEVEL: any) || 'info',
   SERVER_KEY_FILENAME: 'default_key.pem',
   SERVER_KEYS_DIRECTORY: path.join(__dirname, '../__test_data__'),
   USERS_DIRECTORY: path.join(__dirname, '../__test_data__/users'),

From 35df618f81744c442e3e76ca09d60a6ba7e448a5 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 23 Jul 2017 21:33:39 +0200
Subject: [PATCH 470/504] fix adminUser creation

---
 dist/managers/PermissionManager.js |  2 +-
 src/managers/PermissionManager.js  | 11 +++++++----
 src/types.js                       |  5 ++++-
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index 43aac579..4d3d9895 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -155,7 +155,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
             return _this._userRepository.createWithCredentials({
               password: _settings2.default.DEFAULT_ADMIN_PASSWORD,
               username: _settings2.default.DEFAULT_ADMIN_USERNAME
-            });
+            }, 'administrator');
 
           case 3:
             _context4.next = 5;
diff --git a/src/managers/PermissionManager.js b/src/managers/PermissionManager.js
index ad382c6a..e7878a89 100644
--- a/src/managers/PermissionManager.js
+++ b/src/managers/PermissionManager.js
@@ -74,10 +74,13 @@ class PermissionManager {
 
   _createDefaultAdminUser = async (): Promise => {
     try {
-      await this._userRepository.createWithCredentials({
-        password: settings.DEFAULT_ADMIN_PASSWORD,
-        username: settings.DEFAULT_ADMIN_USERNAME,
-      });
+      await this._userRepository.createWithCredentials(
+        {
+          password: settings.DEFAULT_ADMIN_PASSWORD,
+          username: settings.DEFAULT_ADMIN_USERNAME,
+        },
+        'administrator',
+      );
 
       const token = await this._generateAdminToken();
 
diff --git a/src/types.js b/src/types.js
index c7abc0ee..604cb0b4 100644
--- a/src/types.js
+++ b/src/types.js
@@ -262,7 +262,10 @@ export interface IDeviceKeyRepository
   extends IBaseRepository {}
 
 export interface IUserRepository extends IBaseRepository {
-  createWithCredentials(credentials: UserCredentials): Promise,
+  createWithCredentials(
+    credentials: UserCredentials,
+    userRole: ?UserRole,
+  ): Promise,
   deleteAccessToken(userID: string, accessToken: string): Promise,
   getByAccessToken(accessToken: string): Promise,
   getByUsername(username: string): Promise,

From 673ab73f9c761b9422d5fc1fc49bbfabd7b838c5 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Sun, 23 Jul 2017 19:39:31 -0700
Subject: [PATCH 471/504] Finished API for managing firmware/devices/products.

Now we need to actually add code for flashing the correct firmware when the device connects.
---
 dist/controllers/ProductsController.js        | 539 ++++++++++++-
 dist/defaultBindings.js                       |   7 +-
 dist/managers/DeviceManager.js                |  35 +-
 dist/repository/BaseRepository.js             |  65 ++
 .../DeviceAttributeDatabaseRepository.js      | 327 ++++----
 .../repository/DeviceKeyDatabaseRepository.js | 234 +++---
 dist/repository/MongoDb.js                    |  39 +-
 dist/repository/NeDb.js                       |  24 +-
 .../OrganizationDatabaseRepository.js         | 290 +++----
 .../ProductConfigDatabaseRepository.js        | 290 +++----
 dist/repository/ProductDatabaseRepository.js  | 470 ++++++------
 .../ProductDeviceDatabaseRepository.js        | 264 +++++++
 .../ProductFirmwareDatabaseRepository.js      | 338 +++++----
 dist/repository/UserDatabaseRepository.js     | 710 +++++++++---------
 dist/repository/UserFileRepository.js         | 257 ++++---
 dist/repository/WebhookDatabaseRepository.js  | 240 +++---
 dist/repository/WebhookFileRepository.js      | 115 +--
 dist/repository/collectionNames.js            |   1 +
 examples/Products.md                          | 108 +++
 package.json                                  |   1 +
 src/controllers/ProductsController.js         | 325 +++++++-
 src/defaultBindings.js                        |   8 +
 src/managers/DeviceManager.js                 |  20 +-
 src/repository/BaseRepository.js              |  22 +
 .../DeviceAttributeDatabaseRepository.js      |  16 +-
 src/repository/DeviceKeyDatabaseRepository.js |   5 +-
 src/repository/MongoDb.js                     |  18 +-
 src/repository/NeDb.js                        |  12 +-
 .../OrganizationDatabaseRepository.js         |   5 +-
 .../ProductConfigDatabaseRepository.js        |   5 +-
 src/repository/ProductDatabaseRepository.js   |   5 +-
 .../ProductDeviceDatabaseRepository.js        |  73 ++
 .../ProductFirmwareDatabaseRepository.js      |   5 +-
 src/repository/UserDatabaseRepository.js      |   4 +-
 src/repository/UserFileRepository.js          |   2 +
 src/repository/WebhookDatabaseRepository.js   |   5 +-
 src/repository/WebhookFileRepository.js       |   2 +
 src/repository/collectionNames.js             |   2 +
 src/types.js                                  |  31 +-
 39 files changed, 3308 insertions(+), 1611 deletions(-)
 create mode 100644 dist/repository/BaseRepository.js
 create mode 100644 dist/repository/ProductDeviceDatabaseRepository.js
 create mode 100644 src/repository/BaseRepository.js
 create mode 100644 src/repository/ProductDeviceDatabaseRepository.js

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index f8391b79..696fb76a 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -8,6 +8,10 @@ var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-pr
 
 var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
 
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
 var _promise = require('babel-runtime/core-js/promise');
 
 var _promise2 = _interopRequireDefault(_promise);
@@ -48,7 +52,7 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _dec25, _dec26, _dec27, _dec28, _dec29, _dec30, _dec31, _dec32, _dec33, _desc, _value, _class;
+var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _dec23, _dec24, _dec25, _dec26, _dec27, _dec28, _dec29, _dec30, _dec31, _dec32, _dec33, _dec34, _dec35, _dec36, _dec37, _dec38, _desc, _value, _class;
 /* eslint-disable */
 
 var _Controller2 = require('./Controller');
@@ -61,10 +65,18 @@ var _allowUpload2 = _interopRequireDefault(_allowUpload);
 
 var _binaryVersionReader = require('binary-version-reader');
 
+var _csv = require('csv');
+
+var _csv2 = _interopRequireDefault(_csv);
+
 var _httpVerb = require('../decorators/httpVerb');
 
 var _httpVerb2 = _interopRequireDefault(_httpVerb);
 
+var _nullthrows2 = require('nullthrows');
+
+var _nullthrows3 = _interopRequireDefault(_nullthrows2);
+
 var _route = require('../decorators/route');
 
 var _route2 = _interopRequireDefault(_route);
@@ -104,16 +116,18 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
   return desc;
 }
 
-var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec7 = (0, _httpVerb2.default)('put'), _dec8 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec9 = (0, _httpVerb2.default)('delete'), _dec10 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec11 = (0, _httpVerb2.default)('get'), _dec12 = (0, _route2.default)('/v1/products/:productIDOrSlug/config'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec15 = (0, _httpVerb2.default)('get'), _dec16 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec17 = (0, _httpVerb2.default)('post'), _dec18 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec19 = (0, _allowUpload2.default)('binary', 1), _dec20 = (0, _httpVerb2.default)('put'), _dec21 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec22 = (0, _httpVerb2.default)('delete'), _dec23 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec24 = (0, _httpVerb2.default)('get'), _dec25 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices'), _dec26 = (0, _httpVerb2.default)('put'), _dec27 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec28 = (0, _httpVerb2.default)('delete'), _dec29 = (0, _route2.default)('/v1/products/:productIdOrSlug/devices/:deviceID'), _dec30 = (0, _httpVerb2.default)('get'), _dec31 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec32 = (0, _httpVerb2.default)('delete'), _dec33 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) {
+var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec7 = (0, _httpVerb2.default)('put'), _dec8 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec9 = (0, _httpVerb2.default)('delete'), _dec10 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec11 = (0, _httpVerb2.default)('get'), _dec12 = (0, _route2.default)('/v1/products/:productIDOrSlug/config'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec15 = (0, _httpVerb2.default)('get'), _dec16 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec17 = (0, _httpVerb2.default)('post'), _dec18 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec19 = (0, _allowUpload2.default)('binary', 1), _dec20 = (0, _httpVerb2.default)('put'), _dec21 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec22 = (0, _httpVerb2.default)('delete'), _dec23 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec24 = (0, _httpVerb2.default)('get'), _dec25 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices'), _dec26 = (0, _httpVerb2.default)('get'), _dec27 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices/:deviceID'), _dec28 = (0, _httpVerb2.default)('post'), _dec29 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices'), _dec30 = (0, _allowUpload2.default)('file', 1), _dec31 = (0, _httpVerb2.default)('put'), _dec32 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices/:deviceID'), _dec33 = (0, _httpVerb2.default)('delete'), _dec34 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices/:deviceID'), _dec35 = (0, _httpVerb2.default)('get'), _dec36 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec37 = (0, _httpVerb2.default)('delete'), _dec38 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) {
   (0, _inherits3.default)(ProductsController, _Controller);
 
-  function ProductsController(organizationRepository, productRepository, productConfigRepository, productFirmwareRepository) {
+  function ProductsController(deviceAttributeRepository, organizationRepository, productRepository, productConfigRepository, productDeviceRepository, productFirmwareRepository) {
     (0, _classCallCheck3.default)(this, ProductsController);
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (ProductsController.__proto__ || (0, _getPrototypeOf2.default)(ProductsController)).call(this));
 
+    _this._deviceAttributeRepository = deviceAttributeRepository;
     _this._organizationRepository = organizationRepository;
     _this._productConfigRepository = productConfigRepository;
+    _this._productDeviceRepository = productDeviceRepository;
     _this._productFirmwareRepository = productFirmwareRepository;
     _this._productRepository = productRepository;
     return _this;
@@ -529,13 +543,13 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     key: 'addFirmware',
     value: function () {
       var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIDOrSlug, body) {
-        var missingFields, product, parser, moduleInfo, firmwarePlatformID, _moduleInfo$suffixInf, productId, productVersion, firmware, data, id, output;
+        var missingFields, product, parser, moduleInfo, firmwarePlatformID, _moduleInfo$suffixInf, productId, productVersion, version, firmwareList, maxExistingFirmwareVersion, firmware, data, id, output;
 
         return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
             switch (_context9.prev = _context9.next) {
               case 0:
-                missingFields = ['binary', 'description', 'title'].filter(function (key) {
+                missingFields = ['binary', 'description', 'title', 'version'].filter(function (key) {
                   return !body[key];
                 });
 
@@ -598,15 +612,34 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 return _context9.abrupt('return', this.bad('Firmware had incorrect product ID ' + productId + '. Expected ' + product.product_id));
 
               case 20:
-                if (!(productVersion !== parseInt(body.version, 10))) {
-                  _context9.next = 22;
+                version = parseInt(body.version, 10);
+
+                if (!(productVersion !== version)) {
+                  _context9.next = 23;
                   break;
                 }
 
                 return _context9.abrupt('return', this.bad('Firmware had incorrect product version ' + productVersion + '. Expected ' + body.version));
 
-              case 22:
-                _context9.next = 24;
+              case 23:
+                _context9.next = 25;
+                return this._productFirmwareRepository.getAllByProductID(product.product_id);
+
+              case 25:
+                firmwareList = _context9.sent;
+                maxExistingFirmwareVersion = Math.max.apply(Math, (0, _toConsumableArray3.default)(firmwareList.map(function (firmware) {
+                  return parseInt(firmware.version, 10);
+                })));
+
+                if (!(version <= maxExistingFirmwareVersion)) {
+                  _context9.next = 29;
+                  break;
+                }
+
+                return _context9.abrupt('return', this.bad('version must be greater than ' + maxExistingFirmwareVersion));
+
+              case 29:
+                _context9.next = 31;
                 return this._productFirmwareRepository.create({
                   current: body.current,
                   data: body.binary.buffer,
@@ -616,15 +649,15 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                   product_id: product.product_id,
                   size: body.binary.size,
                   title: body.title,
-                  version: body.version
+                  version: version
                 });
 
-              case 24:
+              case 31:
                 firmware = _context9.sent;
                 data = firmware.data, id = firmware.id, output = (0, _objectWithoutProperties3.default)(firmware, ['data', 'id']);
                 return _context9.abrupt('return', this.ok(output));
 
-              case 27:
+              case 34:
               case 'end':
                 return _context9.stop();
             }
@@ -771,14 +804,74 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getDevices',
     value: function () {
-      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIdOrSlug) {
+      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIDOrSlug, query) {
+        var product, page, _query$per_page, per_page, totalDevices, productDevices, deviceIDs, devices;
+
         return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
             switch (_context12.prev = _context12.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context12.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context12.sent;
+
+                if (product) {
+                  _context12.next = 5;
+                  break;
+                }
+
+                return _context12.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 5:
+
+                query.page = Math.max(1, query.page);
+                page = query.page, _query$per_page = query.per_page, per_page = _query$per_page === undefined ? 25 : _query$per_page;
+                _context12.next = 9;
+                return this._productDeviceRepository.count({
+                  productID: product.id
+                });
+
+              case 9:
+                totalDevices = _context12.sent;
+                _context12.next = 12;
+                return this._productDeviceRepository.getAllByProductID(product.id, page, per_page);
+
+              case 12:
+                productDevices = _context12.sent;
+                deviceIDs = productDevices.map(function (productDevice) {
+                  return productDevice.deviceID;
+                });
+                _context12.next = 16;
+                return this._deviceAttributeRepository.getManyFromIDs(deviceIDs, this.user.id);
+
+              case 16:
+                _context12.t0 = function (device) {
+                  var _nullthrows = (0, _nullthrows3.default)(productDevices.find(function (productDevice) {
+                    return productDevice.deviceID === device.deviceID;
+                  })),
+                      denied = _nullthrows.denied,
+                      development = _nullthrows.development,
+                      quarantined = _nullthrows.quarantined;
+
+                  return (0, _extends3.default)({}, device, {
+                    denied: denied,
+                    development: development,
+                    quarantined: quarantined
+                  });
+                };
+
+                devices = _context12.sent.map(_context12.t0);
+
+                console.log(totalDevices, per_page);
+                return _context12.abrupt('return', this.ok({
+                  accounts: [],
+                  devices: devices,
+                  meta: { total_pages: Math.ceil(totalDevices / per_page) }
+                }));
+
+              case 20:
               case 'end':
                 return _context12.stop();
             }
@@ -786,23 +879,71 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee12, this);
       }));
 
-      function getDevices(_x17) {
+      function getDevices(_x17, _x18) {
         return _ref13.apply(this, arguments);
       }
 
       return getDevices;
     }()
   }, {
-    key: 'setFirmwareVersion',
+    key: 'getSingleDevices',
     value: function () {
-      var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(productIdOrSlug, deviceID, body) {
+      var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(productIDOrSlug, deviceID) {
+        var product, deviceAttributes, productDevice, denied, development, quarantined;
         return _regenerator2.default.wrap(function _callee13$(_context13) {
           while (1) {
             switch (_context13.prev = _context13.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                _context13.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context13.sent;
+
+                if (product) {
+                  _context13.next = 5;
+                  break;
+                }
+
+                return _context13.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 5:
+                _context13.next = 7;
+                return this._deviceAttributeRepository.getByID(deviceID);
+
+              case 7:
+                deviceAttributes = _context13.sent;
+
+                if (deviceAttributes) {
+                  _context13.next = 10;
+                  break;
+                }
+
+                return _context13.abrupt('return', this.bad('Device ' + deviceID + ' doesn\'t exist.'));
+
+              case 10:
+                _context13.next = 12;
+                return this._productDeviceRepository.getManyFromDeviceIDs([deviceID]);
+
+              case 12:
+                productDevice = _context13.sent[0];
+
+                if (productDevice) {
+                  _context13.next = 15;
+                  break;
+                }
+
+                return _context13.abrupt('return', this.bad('Device ' + deviceID + ' hasn\'t been assigned to a product'));
+
+              case 15:
+                denied = productDevice.denied, development = productDevice.development, quarantined = productDevice.quarantined;
+                return _context13.abrupt('return', this.ok((0, _extends3.default)({}, deviceAttributes, {
+                  denied: denied,
+                  development: development,
+                  quarantined: quarantined
+                })));
+
+              case 17:
               case 'end':
                 return _context13.stop();
             }
@@ -810,23 +951,168 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee13, this);
       }));
 
-      function setFirmwareVersion(_x18, _x19, _x20) {
+      function getSingleDevices(_x19, _x20) {
         return _ref14.apply(this, arguments);
       }
 
-      return setFirmwareVersion;
+      return getSingleDevices;
     }()
   }, {
-    key: 'removeDeviceFromProduct',
+    key: 'addDevice',
     value: function () {
-      var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14(productIdOrSlug, deviceID) {
+      var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14(productIDOrSlug, body) {
+        var _this2 = this;
+
+        var product, ids, _file, originalname, records, deviceAttributes, incorrectPlatformDeviceIDs, deviceAttributeIDs, existingProductDeviceIDs, invalidDeviceIds, nonmemberDeviceIds, idsToCreate;
+
         return _regenerator2.default.wrap(function _callee14$(_context14) {
           while (1) {
             switch (_context14.prev = _context14.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                _context14.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context14.sent;
+
+                if (product) {
+                  _context14.next = 5;
+                  break;
+                }
+
+                return _context14.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 5:
+                ids = null;
+
+                if (!(body.import_method === 'many')) {
+                  _context14.next = 21;
+                  break;
+                }
+
+                _file = body.file;
+
+                if (_file) {
+                  _context14.next = 10;
+                  break;
+                }
+
+                return _context14.abrupt('return', this.bad('No file uploaded'));
+
+              case 10:
+                originalname = _file.originalname;
+
+                if (!(!originalname.endsWith('.txt') && !originalname.endsWith('.csv'))) {
+                  _context14.next = 13;
+                  break;
+                }
+
+                return _context14.abrupt('return', this.bad('File must be csv or txt file.'));
+
+              case 13:
+                records = _csv2.default.parse(_file.buffer.toString('utf8'));
+
+                if (records.length) {
+                  _context14.next = 16;
+                  break;
+                }
+
+                return _context14.abrupt('return', this.bad('File didn\'t have any ids'));
+
+              case 16:
+                if (!records.some(function (record) {
+                  return record.length !== 1;
+                })) {
+                  _context14.next = 18;
+                  break;
+                }
+
+                return _context14.abrupt('return', this.bad('File should only have a single column of device ids'));
+
+              case 18:
+
+                ids = [].concat.apply([], records);
+                _context14.next = 24;
+                break;
+
+              case 21:
+                if (body.id) {
+                  _context14.next = 23;
+                  break;
+                }
+
+                return _context14.abrupt('return', this.bad('You must pass an id for a device'));
+
+              case 23:
+
+                ids = [body.id];
+
+              case 24:
+                _context14.next = 26;
+                return this._deviceAttributeRepository.getManyFromIDs(ids, this.user.id);
+
+              case 26:
+                deviceAttributes = _context14.sent;
+                incorrectPlatformDeviceIDs = deviceAttributes.filter(function (deviceAttribute) {
+                  return deviceAttribute.particleProductId !== product.platform_id;
+                }).map(function (deviceAttribute) {
+                  return deviceAttribute.deviceID;
+                });
+                deviceAttributeIDs = deviceAttributes.map(function (deviceAttribute) {
+                  return deviceAttribute.deviceID;
+                });
+                _context14.next = 31;
+                return this._productDeviceRepository.getManyFromDeviceIDs(ids);
+
+              case 31:
+                _context14.t0 = function (productDevice) {
+                  return productDevice.deviceID;
+                };
+
+                existingProductDeviceIDs = _context14.sent.map(_context14.t0);
+                invalidDeviceIds = [].concat((0, _toConsumableArray3.default)(incorrectPlatformDeviceIDs), (0, _toConsumableArray3.default)(existingProductDeviceIDs));
+                nonmemberDeviceIds = ids.filter(function (id) {
+                  return !deviceAttributeIDs.includes(id);
+                });
+
+                if (!invalidDeviceIds.length) {
+                  _context14.next = 37;
+                  break;
+                }
+
+                return _context14.abrupt('return', {
+                  data: {
+                    updated: 0,
+                    nonmemberDeviceIds: nonmemberDeviceIds,
+                    invalidDeviceIds: invalidDeviceIds
+                  },
+                  status: 400
+                });
+
+              case 37:
+                idsToCreate = ids.filter(function (id) {
+                  return !invalidDeviceIds.includes(id) && !existingProductDeviceIDs.includes(id);
+                });
+                _context14.next = 40;
+                return _promise2.default.all(idsToCreate.map(function (id) {
+                  return _this2._productDeviceRepository.create({
+                    denied: false,
+                    development: false,
+                    deviceID: id,
+                    lockedFirmwareVersion: null,
+                    productID: product.id,
+                    quarantined: nonmemberDeviceIds.includes(id)
+                  });
+                }));
+
+              case 40:
+                return _context14.abrupt('return', this.ok({
+                  updated: idsToCreate.length,
+                  nonmemberDeviceIds: nonmemberDeviceIds,
+                  invalidDeviceIds: invalidDeviceIds
+                }));
+
+              case 41:
               case 'end':
                 return _context14.stop();
             }
@@ -834,23 +1120,101 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee14, this);
       }));
 
-      function removeDeviceFromProduct(_x21, _x22) {
+      function addDevice(_x21, _x22) {
         return _ref15.apply(this, arguments);
       }
 
-      return removeDeviceFromProduct;
+      return addDevice;
     }()
   }, {
-    key: 'getEvents',
+    key: 'updateDeviceProduct',
     value: function () {
-      var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(productIdOrSlug, eventName) {
+      var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(productIDOrSlug, deviceID, body) {
+        var desired_firmware_version, notes, product, deviceAttributes, productDevice, output, deviceFirmwares, parsedFirmware, updatedProductDevice;
         return _regenerator2.default.wrap(function _callee15$(_context15) {
           while (1) {
             switch (_context15.prev = _context15.next) {
               case 0:
-                throw new _HttpError2.default('Not implemented');
+                desired_firmware_version = body.desired_firmware_version, notes = body.notes;
+                _context15.next = 3;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 3:
+                product = _context15.sent;
+
+                if (product) {
+                  _context15.next = 6;
+                  break;
+                }
+
+                return _context15.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 6:
+                _context15.next = 8;
+                return this._deviceAttributeRepository.getByID(deviceID);
+
+              case 8:
+                deviceAttributes = _context15.sent;
+
+                if (deviceAttributes) {
+                  _context15.next = 11;
+                  break;
+                }
+
+                return _context15.abrupt('return', this.bad('Device ' + deviceID + ' doesn\'t exist.'));
+
+              case 11:
+                _context15.next = 13;
+                return this._productDeviceRepository.getManyFromDeviceIDs([deviceID]);
+
+              case 13:
+                productDevice = _context15.sent[0];
+                output = { id: productDevice.id, updated: new Date() };
+
+                if (!desired_firmware_version) {
+                  _context15.next = 25;
+                  break;
+                }
+
+                _context15.next = 18;
+                return this._productFirmwareRepository.getAllByProductID(product.product_id);
+
+              case 18:
+                deviceFirmwares = _context15.sent;
+
+                console.log(deviceFirmwares);
+
+                parsedFirmware = parseInt(desired_firmware_version, 10);
+
+                if (deviceFirmwares.find(function (firmware) {
+                  return firmware.version === parsedFirmware;
+                })) {
+                  _context15.next = 23;
+                  break;
+                }
+
+                return _context15.abrupt('return', this.bad('Firmware version ' + desired_firmware_version + ' does not exist'));
+
+              case 23:
+
+                productDevice.lockedFirmwareVersion = parsedFirmware;
+                output = (0, _extends3.default)({}, output, { desired_firmware_version: desired_firmware_version });
+
+              case 25:
+
+                if (notes !== undefined) {
+                  productDevice.notes = notes;
+                  output = (0, _extends3.default)({}, output, { notes: notes });
+                }
+
+                _context15.next = 28;
+                return this._productDeviceRepository.updateByID(productDevice.id, productDevice);
+
+              case 28:
+                updatedProductDevice = _context15.sent;
+                return _context15.abrupt('return', this.ok(output));
+
+              case 30:
               case 'end':
                 return _context15.stop();
             }
@@ -858,23 +1222,70 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee15, this);
       }));
 
-      function getEvents(_x23, _x24) {
+      function updateDeviceProduct(_x23, _x24, _x25) {
         return _ref16.apply(this, arguments);
       }
 
-      return getEvents;
+      return updateDeviceProduct;
     }()
   }, {
-    key: 'removeTeamMember',
+    key: 'removeDeviceFromProduct',
     value: function () {
-      var _ref17 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16(productIdOrSlug, username) {
+      var _ref17 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16(productIDOrSlug, deviceID) {
+        var product, deviceAttributes, productDevice;
         return _regenerator2.default.wrap(function _callee16$(_context16) {
           while (1) {
             switch (_context16.prev = _context16.next) {
               case 0:
-                throw new _HttpError2.default('not supported in the current server version');
+                _context16.next = 2;
+                return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 1:
+              case 2:
+                product = _context16.sent;
+
+                if (product) {
+                  _context16.next = 5;
+                  break;
+                }
+
+                return _context16.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
+
+              case 5:
+                _context16.next = 7;
+                return this._deviceAttributeRepository.getByID(deviceID);
+
+              case 7:
+                deviceAttributes = _context16.sent;
+
+                if (deviceAttributes) {
+                  _context16.next = 10;
+                  break;
+                }
+
+                return _context16.abrupt('return', this.bad('Device ' + deviceID + ' doesn\'t exist.'));
+
+              case 10:
+                _context16.next = 12;
+                return this._productDeviceRepository.getManyFromDeviceIDs([deviceID]);
+
+              case 12:
+                productDevice = _context16.sent[0];
+
+                if (productDevice) {
+                  _context16.next = 15;
+                  break;
+                }
+
+                return _context16.abrupt('return', this.bad('Device ' + deviceID + ' was not mapped to ' + productIDOrSlug));
+
+              case 15:
+                _context16.next = 17;
+                return this._productDeviceRepository.deleteByID(productDevice.id);
+
+              case 17:
+                return _context16.abrupt('return', this.ok());
+
+              case 18:
               case 'end':
                 return _context16.stop();
             }
@@ -882,10 +1293,58 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee16, this);
       }));
 
-      function removeTeamMember(_x25, _x26) {
+      function removeDeviceFromProduct(_x26, _x27) {
         return _ref17.apply(this, arguments);
       }
 
+      return removeDeviceFromProduct;
+    }()
+  }, {
+    key: 'getEvents',
+    value: function () {
+      var _ref18 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee17(productIdOrSlug, eventName) {
+        return _regenerator2.default.wrap(function _callee17$(_context17) {
+          while (1) {
+            switch (_context17.prev = _context17.next) {
+              case 0:
+                throw new _HttpError2.default('Not implemented');
+
+              case 1:
+              case 'end':
+                return _context17.stop();
+            }
+          }
+        }, _callee17, this);
+      }));
+
+      function getEvents(_x28, _x29) {
+        return _ref18.apply(this, arguments);
+      }
+
+      return getEvents;
+    }()
+  }, {
+    key: 'removeTeamMember',
+    value: function () {
+      var _ref19 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee18(productIdOrSlug, username) {
+        return _regenerator2.default.wrap(function _callee18$(_context18) {
+          while (1) {
+            switch (_context18.prev = _context18.next) {
+              case 0:
+                throw new _HttpError2.default('not supported in the current server version');
+
+              case 1:
+              case 'end':
+                return _context18.stop();
+            }
+          }
+        }, _callee18, this);
+      }));
+
+      function removeTeamMember(_x30, _x31) {
+        return _ref19.apply(this, arguments);
+      }
+
       return removeTeamMember;
     }()
   }, {
@@ -899,6 +1358,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     }
   }]);
   return ProductsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProduct', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteProduct', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleFirmware', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addFirmware', [_dec17, _dec18, _dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateFirmware', [_dec20, _dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteFirmware', [_dec22, _dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec24, _dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'setFirmwareVersion', [_dec26, _dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'setFirmwareVersion'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec28, _dec29], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec30, _dec31], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec32, _dec33], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProduct', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteProduct', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleFirmware', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addFirmware', [_dec17, _dec18, _dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateFirmware', [_dec20, _dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteFirmware', [_dec22, _dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec24, _dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleDevices', [_dec26, _dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addDevice', [_dec28, _dec29, _dec30], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDeviceProduct', [_dec31, _dec32], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDeviceProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec33, _dec34], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec35, _dec36], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec37, _dec38], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
 exports.default = ProductsController;
 /* eslint-enable */
\ No newline at end of file
diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index dd5aaaef..62aa42a5 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -98,6 +98,10 @@ var _ProductConfigDatabaseRepository = require('./repository/ProductConfigDataba
 
 var _ProductConfigDatabaseRepository2 = _interopRequireDefault(_ProductConfigDatabaseRepository);
 
+var _ProductDeviceDatabaseRepository = require('./repository/ProductDeviceDatabaseRepository');
+
+var _ProductDeviceDatabaseRepository2 = _interopRequireDefault(_ProductDeviceDatabaseRepository);
+
 var _ProductFirmwareDatabaseRepository = require('./repository/ProductFirmwareDatabaseRepository');
 
 var _ProductFirmwareDatabaseRepository2 = _interopRequireDefault(_ProductFirmwareDatabaseRepository);
@@ -156,7 +160,7 @@ exports.default = function (container, newSettings) {
   container.bindClass('EventsController', _EventsController2.default, ['EventManager']);
   container.bindClass('PermissionManager', _PermissionManager2.default, ['IDeviceAttributeRepository', 'IOrganizationRepository', 'IUserRepository', 'IWebhookRepository', 'OAuthServer']);
   container.bindClass('OauthClientsController', _OauthClientsController2.default, []);
-  container.bindClass('ProductsController', _ProductsController2.default, ['IOrganizationRepository', 'IProductRepository', 'IProductConfigRepository', 'IProductFirmwareRepository']);
+  container.bindClass('ProductsController', _ProductsController2.default, ['IDeviceAttributeRepository', 'IOrganizationRepository', 'IProductRepository', 'IProductConfigRepository', 'IProductDeviceRepository', 'IProductFirmwareRepository']);
   container.bindClass('ProvisioningController', _ProvisioningController2.default, ['DeviceManager']);
   container.bindClass('UsersController', _UsersController2.default, ['IUserRepository']);
   container.bindClass('WebhooksController', _WebhooksController2.default, ['WebhookManager']);
@@ -173,6 +177,7 @@ exports.default = function (container, newSettings) {
   container.bindClass('IOrganizationRepository', _OrganizationDatabaseRepository2.default, ['IDatabase']);
   container.bindClass('IProductRepository', _ProductDatabaseRepository2.default, ['IDatabase']);
   container.bindClass('IProductConfigRepository', _ProductConfigDatabaseRepository2.default, ['IDatabase']);
+  container.bindClass('IProductDeviceRepository', _ProductDeviceDatabaseRepository2.default, ['IDatabase']);
   container.bindClass('IProductFirmwareRepository', _ProductFirmwareDatabaseRepository2.default, ['IDatabase']);
 
   container.bindClass('IUserRepository', _UserDatabaseRepository2.default, ['IDatabase']);
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 4ebfd859..165d1dd7 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -63,45 +63,53 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               attributes = _context.sent;
 
               if (attributes) {
-                _context.next = 5;
+                _context.next = 7;
                 break;
               }
 
-              throw new _HttpError2.default('No device found', 404);
+              _context.next = 6;
+              return _this._deviceAttributeRepository.updateByID(deviceID, {
+                deviceID: deviceID,
+                ownerID: userID,
+                registrar: userID
+              });
 
-            case 5:
+            case 6:
+              return _context.abrupt('return', _context.sent);
+
+            case 7:
               if (!(attributes.ownerID && attributes.ownerID !== userID)) {
-                _context.next = 7;
+                _context.next = 9;
                 break;
               }
 
               throw new _HttpError2.default('The device belongs to someone else.');
 
-            case 7:
+            case 9:
               if (!(attributes.ownerID && attributes.ownerID === userID)) {
-                _context.next = 9;
+                _context.next = 11;
                 break;
               }
 
               throw new _HttpError2.default('The device is already claimed.');
 
-            case 9:
-              _context.next = 11;
+            case 11:
+              _context.next = 13;
               return _this._eventPublisher.publishAndListenForResponse({
                 context: { attributes: { ownerID: userID }, deviceID: deviceID },
                 name: _sparkProtocol.SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES
               });
 
-            case 11:
-              _context.next = 13;
+            case 13:
+              _context.next = 15;
               return _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: userID
               });
 
-            case 13:
+            case 15:
               return _context.abrupt('return', _context.sent);
 
-            case 14:
+            case 16:
             case 'end':
               return _context.stop();
           }
@@ -550,8 +558,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               _context11.next = 25;
               return _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: userID,
-                registrar: userID,
-                timestamp: new Date()
+                registrar: userID
               });
 
             case 25:
diff --git a/dist/repository/BaseRepository.js b/dist/repository/BaseRepository.js
new file mode 100644
index 00000000..b4c365b0
--- /dev/null
+++ b/dist/repository/BaseRepository.js
@@ -0,0 +1,65 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var BaseRepository = function BaseRepository(database, collectionName) {
+  var _this = this;
+
+  (0, _classCallCheck3.default)(this, BaseRepository);
+
+  this.count = function () {
+    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      var _database;
+
+      for (var _len = arguments.length, filters = Array(_len), _key = 0; _key < _len; _key++) {
+        filters[_key] = arguments[_key];
+      }
+
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              _context.next = 2;
+              return (_database = _this._database).count.apply(_database, [_this._collectionName].concat((0, _toConsumableArray3.default)(filters.length ? filters : [{}])));
+
+            case 2:
+              return _context.abrupt('return', _context.sent);
+
+            case 3:
+            case 'end':
+              return _context.stop();
+          }
+        }
+      }, _callee, _this);
+    }));
+
+    return function () {
+      return _ref.apply(this, arguments);
+    };
+  }();
+
+  this._database = database;
+  this._collectionName = collectionName;
+};
+
+exports.default = BaseRepository;
\ No newline at end of file
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 89b07ea9..c6978f53 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -24,172 +24,227 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
 var _collectionNames = require('./collectionNames');
 
 var _collectionNames2 = _interopRequireDefault(_collectionNames);
 
+var _BaseRepository2 = require('./BaseRepository');
+
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 // getByID, deleteByID and update uses model.deviceID as ID for querying
-var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseRepository(database, permissionManager) {
-  var _this = this;
-
-  (0, _classCallCheck3.default)(this, DeviceAttributeDatabaseRepository);
-  this._collectionName = _collectionNames2.default.DEVICE_ATTRIBUTES;
-  this.create = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
-    return _regenerator2.default.wrap(function _callee$(_context) {
-      while (1) {
-        switch (_context.prev = _context.next) {
-          case 0:
-            throw new Error('The method is not implemented');
-
-          case 1:
-          case 'end':
-            return _context.stop();
-        }
-      }
-    }, _callee, _this);
-  }));
+var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(DeviceAttributeDatabaseRepository, _BaseRepository);
 
-  this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { deviceID: deviceID });
-
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
+  function DeviceAttributeDatabaseRepository(database, permissionManager) {
+    var _this2 = this;
 
-            case 3:
-            case 'end':
-              return _context2.stop();
-          }
-        }
-      }, _callee2, _this);
-    }));
+    (0, _classCallCheck3.default)(this, DeviceAttributeDatabaseRepository);
 
-    return function (_x) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
+    var _this = (0, _possibleConstructorReturn3.default)(this, (DeviceAttributeDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(DeviceAttributeDatabaseRepository)).call(this, database, _collectionNames2.default.DEVICE_ATTRIBUTES));
 
-  this.getAll = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-      var query;
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
+    _this._collectionName = _collectionNames2.default.DEVICE_ATTRIBUTES;
+    _this.create = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
-          switch (_context3.prev = _context3.next) {
+          switch (_context.prev = _context.next) {
             case 0:
-              query = userID ? { ownerID: userID } : {};
-              _context3.next = 3;
-              return _this._database.find(_this._collectionName, query);
-
-            case 3:
-              _context3.t0 = _this._parseVariables;
-              return _context3.abrupt('return', _context3.sent.map(_context3.t0));
+              throw new Error('The method is not implemented');
 
-            case 5:
+            case 1:
             case 'end':
-              return _context3.stop();
+              return _context.stop();
           }
         }
-      }, _callee3, _this);
+      }, _callee, _this2);
     }));
 
-    return function () {
-      return _ref3.apply(this, arguments);
-    };
-  }();
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { deviceID: deviceID });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.getAll = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var query;
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                query = userID ? { ownerID: userID } : {};
+                _context3.next = 3;
+                return _this._database.find(_this._collectionName, query);
+
+              case 3:
+                _context3.t0 = _this._parseVariables;
+                return _context3.abrupt('return', _context3.sent.map(_context3.t0));
+
+              case 5:
+              case 'end':
+                return _context3.stop();
+            }
+          }
+        }, _callee3, _this2);
+      }));
+
+      return function () {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.t0 = _this;
+                _context4.next = 3;
+                return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
+
+              case 3:
+                _context4.t1 = _context4.sent;
+                return _context4.abrupt('return', _context4.t0._parseVariables.call(_context4.t0, _context4.t1));
+
+              case 5:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
+
+      return function (_x3) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
+
+    _this.getManyFromIDs = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceIDs, ownerID) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
+          while (1) {
+            switch (_context5.prev = _context5.next) {
+              case 0:
+                _context5.next = 2;
+                return _this._database.find(_this._collectionName, {
+                  deviceID: { $in: deviceIDs },
+                  ownerID: ownerID
+                });
+
+              case 2:
+                _context5.t0 = _this._parseVariables;
+                return _context5.abrupt('return', _context5.sent.map(_context5.t0));
+
+              case 4:
+              case 'end':
+                return _context5.stop();
+            }
+          }
+        }, _callee5, _this2);
+      }));
+
+      return function (_x4, _x5) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
+
+    _this.updateByID = function () {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID, _ref7) {
+        var variables = _ref7.variables,
+            props = (0, _objectWithoutProperties3.default)(_ref7, ['variables']);
+        var attributesToSave;
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                attributesToSave = (0, _extends3.default)({}, props, {
+                  variables: variables ? (0, _stringify2.default)(variables) : undefined
+                });
+                _context6.next = 3;
+                return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, attributesToSave, { timestamp: new Date() }) });
+
+              case 3:
+                return _context6.abrupt('return', _context6.sent);
+
+              case 4:
+              case 'end':
+                return _context6.stop();
+            }
+          }
+        }, _callee6, _this2);
+      }));
 
-  this.getByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
-        while (1) {
-          switch (_context4.prev = _context4.next) {
-            case 0:
-              _context4.t0 = _this;
-              _context4.next = 3;
-              return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
+      return function (_x6, _x7) {
+        return _ref6.apply(this, arguments);
+      };
+    }();
 
-            case 3:
-              _context4.t1 = _context4.sent;
-              return _context4.abrupt('return', _context4.t0._parseVariables.call(_context4.t0, _context4.t1));
+    _this._parseVariables = function (attributesFromDB) {
+      if (!attributesFromDB) {
+        return null;
+      }
 
-            case 5:
-            case 'end':
-              return _context4.stop();
-          }
-        }
-      }, _callee4, _this);
-    }));
+      var variables = attributesFromDB.variables;
 
-    return function (_x3) {
-      return _ref4.apply(this, arguments);
+      try {
+        return (0, _extends3.default)({}, attributesFromDB, {
+          variables: variables ? JSON.parse(variables) : undefined
+        });
+      } catch (ignore) {
+        return attributesFromDB;
+      }
     };
-  }();
-
-  this.updateByID = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, _ref6) {
-      var variables = _ref6.variables,
-          props = (0, _objectWithoutProperties3.default)(_ref6, ['variables']);
-      var attributesToSave;
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
-        while (1) {
-          switch (_context5.prev = _context5.next) {
-            case 0:
-              attributesToSave = (0, _extends3.default)({}, props, {
-                variables: variables ? (0, _stringify2.default)(variables) : undefined
-              });
-              _context5.next = 3;
-              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, attributesToSave, { timeStamp: new Date() }) });
 
-            case 3:
-              return _context5.abrupt('return', _context5.sent);
+    _this._database = database;
+    _this._permissionManager = permissionManager;
+    return _this;
+  }
 
-            case 4:
-            case 'end':
-              return _context5.stop();
-          }
-        }
-      }, _callee5, _this);
-    }));
+  // mongo and neDB don't support dots in variables names
+  // but some of the server users want to have dots in their device var names
+  // so we have to stringify them and parse back.
 
-    return function (_x4, _x5) {
-      return _ref5.apply(this, arguments);
-    };
-  }();
-
-  this._parseVariables = function (attributesFromDB) {
-    if (!attributesFromDB) {
-      return null;
-    }
-
-    var variables = attributesFromDB.variables;
-
-    try {
-      return (0, _extends3.default)({}, attributesFromDB, {
-        variables: variables ? JSON.parse(variables) : undefined
-      });
-    } catch (ignore) {
-      return attributesFromDB;
-    }
-  };
-
-  this._database = database;
-  this._permissionManager = permissionManager;
-}
-
-// mongo and neDB don't support dots in variables names
-// but some of the server users want to have dots in their device var names
-// so we have to stringify them and parse back.
-;
+
+  return DeviceAttributeDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = DeviceAttributeDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index 01751dfc..5747b222 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -16,141 +16,167 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
 var _collectionNames = require('./collectionNames');
 
 var _collectionNames2 = _interopRequireDefault(_collectionNames);
 
+var _BaseRepository2 = require('./BaseRepository');
+
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 // getByID, deleteByID and update uses model.deviceID as ID for querying
-var DeviceKeyDatabaseRepository = function DeviceKeyDatabaseRepository(database) {
-  var _this = this;
+var DeviceKeyDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(DeviceKeyDatabaseRepository, _BaseRepository);
 
-  (0, _classCallCheck3.default)(this, DeviceKeyDatabaseRepository);
-  this._collectionName = _collectionNames2.default.DEVICE_KEYS;
+  function DeviceKeyDatabaseRepository(database) {
+    var _this2 = this;
 
-  this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
-        while (1) {
-          switch (_context.prev = _context.next) {
-            case 0:
-              _context.next = 2;
-              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({
-                _id: model.deviceID
-              }, model));
-
-            case 2:
-              return _context.abrupt('return', _context.sent);
+    (0, _classCallCheck3.default)(this, DeviceKeyDatabaseRepository);
 
-            case 3:
-            case 'end':
-              return _context.stop();
-          }
-        }
-      }, _callee, _this);
-    }));
+    var _this = (0, _possibleConstructorReturn3.default)(this, (DeviceKeyDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(DeviceKeyDatabaseRepository)).call(this, database, _collectionNames2.default.DEVICE_KEYS));
 
-    return function (_x) {
-      return _ref.apply(this, arguments);
-    };
-  }();
+    _this._collectionName = _collectionNames2.default.DEVICE_KEYS;
 
-  this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { deviceID: deviceID });
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.next = 2;
+                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({
+                  _id: model.deviceID
+                }, model));
 
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
+              case 2:
+                return _context.abrupt('return', _context.sent);
 
-            case 3:
-            case 'end':
-              return _context2.stop();
+              case 3:
+              case 'end':
+                return _context.stop();
+            }
           }
-        }
-      }, _callee2, _this);
-    }));
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { deviceID: deviceID });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
 
-    return function (_x2) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
-
-  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-    return _regenerator2.default.wrap(function _callee3$(_context3) {
-      while (1) {
-        switch (_context3.prev = _context3.next) {
-          case 0:
-            throw new Error('The method is not implemented.');
-
-          case 1:
-          case 'end':
-            return _context3.stop();
-        }
-      }
-    }, _callee3, _this);
-  }));
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
 
-  this.getByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
+    _this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
-          switch (_context4.prev = _context4.next) {
+          switch (_context3.prev = _context3.next) {
             case 0:
-              _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
-
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
+              throw new Error('The method is not implemented.');
 
-            case 3:
+            case 1:
             case 'end':
-              return _context4.stop();
+              return _context3.stop();
           }
         }
-      }, _callee4, _this);
+      }, _callee3, _this2);
     }));
 
-    return function (_x3) {
-      return _ref4.apply(this, arguments);
-    };
-  }();
-
-  this.updateByID = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, props) {
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
-        while (1) {
-          switch (_context5.prev = _context5.next) {
-            case 0:
-              _context5.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, props) });
-
-            case 2:
-              return _context5.abrupt('return', _context5.sent);
-
-            case 3:
-            case 'end':
-              return _context5.stop();
+    _this.getByID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
           }
-        }
-      }, _callee5, _this);
-    }));
+        }, _callee4, _this2);
+      }));
+
+      return function (_x3) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
+
+    _this.updateByID = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, props) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
+          while (1) {
+            switch (_context5.prev = _context5.next) {
+              case 0:
+                _context5.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, props) });
+
+              case 2:
+                return _context5.abrupt('return', _context5.sent);
+
+              case 3:
+              case 'end':
+                return _context5.stop();
+            }
+          }
+        }, _callee5, _this2);
+      }));
+
+      return function (_x4, _x5) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
 
-    return function (_x4, _x5) {
-      return _ref5.apply(this, arguments);
-    };
-  }();
+    _this._database = database;
+    return _this;
+  }
 
-  this._database = database;
-};
+  return DeviceKeyDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = DeviceKeyDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index 8f38fab5..aa7ca2bf 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -8,6 +8,10 @@ var _promise = require('babel-runtime/core-js/promise');
 
 var _promise2 = _interopRequireDefault(_promise);
 
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -130,9 +134,19 @@ var _initialiseProps = function _initialiseProps() {
               }());
 
             case 2:
-              return _context3.abrupt('return', _context3.sent);
+              _context3.t0 = _context3.sent;
 
-            case 3:
+              if (_context3.t0) {
+                _context3.next = 5;
+                break;
+              }
+
+              _context3.t0 = 0;
+
+            case 5:
+              return _context3.abrupt('return', _context3.t0);
+
+            case 6:
             case 'end':
               return _context3.stop();
           }
@@ -204,19 +218,30 @@ var _initialiseProps = function _initialiseProps() {
               _context7.next = 2;
               return _this3.__runForCollection(collectionName, function () {
                 var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collection) {
-                  var resultItems;
+                  var page, _query$pageSize, pageSize, otherQuery, result, resultItems;
+
                   return _regenerator2.default.wrap(function _callee6$(_context6) {
                     while (1) {
                       switch (_context6.prev = _context6.next) {
                         case 0:
-                          _context6.next = 2;
-                          return collection.find(_this3.__translateQuery(query), { timeout: false }).toArray();
+                          page = query.page, _query$pageSize = query.pageSize, pageSize = _query$pageSize === undefined ? 25 : _query$pageSize, otherQuery = (0, _objectWithoutProperties3.default)(query, ['page', 'pageSize']);
+                          result = collection.find(_this3.__translateQuery(otherQuery), {
+                            timeout: false
+                          });
 
-                        case 2:
+
+                          if (page) {
+                            result = result.skip((page - 1) * pageSize).limit(pageSize);
+                          }
+
+                          _context6.next = 5;
+                          return result;
+
+                        case 5:
                           resultItems = _context6.sent;
                           return _context6.abrupt('return', resultItems.map(_this3.__translateResultItem));
 
-                        case 4:
+                        case 7:
                         case 'end':
                           return _context6.stop();
                       }
diff --git a/dist/repository/NeDb.js b/dist/repository/NeDb.js
index 8d76a2cc..8876ddfc 100644
--- a/dist/repository/NeDb.js
+++ b/dist/repository/NeDb.js
@@ -12,6 +12,10 @@ var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
 
 var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
 
+var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
+
+var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -84,7 +88,7 @@ var NeDb = function (_BaseMongoDb) {
                         switch (_context.prev = _context.next) {
                           case 0:
                             _context.next = 2;
-                            return (0, _promisify.promisify)(collection, 'find', query);
+                            return (0, _promisify.promisify)(collection, 'count', query);
 
                           case 2:
                             return _context.abrupt('return', _context.sent);
@@ -187,19 +191,27 @@ var NeDb = function (_BaseMongoDb) {
                 _context6.next = 2;
                 return _this.__runForCollection(collectionName, function () {
                   var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
-                    var resultItems;
+                    var page, _query$pageSize, pageSize, otherQuery, boundFunction, resultItems;
+
                     return _regenerator2.default.wrap(function _callee5$(_context5) {
                       while (1) {
                         switch (_context5.prev = _context5.next) {
                           case 0:
-                            _context5.next = 2;
-                            return (0, _promisify.promisify)(collection, 'find', query);
+                            page = query.page, _query$pageSize = query.pageSize, pageSize = _query$pageSize === undefined ? 25 : _query$pageSize, otherQuery = (0, _objectWithoutProperties3.default)(query, ['page', 'pageSize']);
+                            boundFunction = collection.find(otherQuery);
 
-                          case 2:
+                            if (page) {
+                              console.log(page, pageSize);
+                              boundFunction = boundFunction.skip((page - 1) * pageSize).limit(pageSize);
+                            }
+                            _context5.next = 5;
+                            return (0, _promisify.promisify)(boundFunction, 'exec');
+
+                          case 5:
                             resultItems = _context5.sent;
                             return _context5.abrupt('return', resultItems.map(_this.__translateResultItem));
 
-                          case 4:
+                          case 7:
                           case 'end':
                             return _context5.stop();
                         }
diff --git a/dist/repository/OrganizationDatabaseRepository.js b/dist/repository/OrganizationDatabaseRepository.js
index 6ca2da46..8686b2bd 100644
--- a/dist/repository/OrganizationDatabaseRepository.js
+++ b/dist/repository/OrganizationDatabaseRepository.js
@@ -16,169 +16,195 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
-var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
 
-var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
 
-var _collectionNames = require('./collectionNames');
-
-var _collectionNames2 = _interopRequireDefault(_collectionNames);
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
-var OrganizationDatabaseRepository = function OrganizationDatabaseRepository(database) {
-  var _this = this;
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
 
-  (0, _classCallCheck3.default)(this, OrganizationDatabaseRepository);
-  this._collectionName = _collectionNames2.default.ORGANIZATIONS;
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
 
-  this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
-        while (1) {
-          switch (_context.prev = _context.next) {
-            case 0:
-              _context.next = 2;
-              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+var _inherits2 = require('babel-runtime/helpers/inherits');
 
-            case 2:
-              return _context.abrupt('return', _context.sent);
+var _inherits3 = _interopRequireDefault(_inherits2);
 
-            case 3:
-            case 'end':
-              return _context.stop();
-          }
-        }
-      }, _callee, _this);
-    }));
+var _collectionNames = require('./collectionNames');
 
-    return function (_x) {
-      return _ref.apply(this, arguments);
-    };
-  }();
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
 
-  this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
+var _BaseRepository2 = require('./BaseRepository');
 
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
 
-            case 3:
-            case 'end':
-              return _context2.stop();
-          }
-        }
-      }, _callee2, _this);
-    }));
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-    return function (_x2) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
+var OrganizationDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(OrganizationDatabaseRepository, _BaseRepository);
 
-  this.getAll = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-      var query;
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
-        while (1) {
-          switch (_context3.prev = _context3.next) {
-            case 0:
-              // TODO - this should probably just query the organization
-              query = userID ? { ownerID: userID } : {};
-              _context3.next = 3;
-              return _this._database.find(_this._collectionName, query);
+  function OrganizationDatabaseRepository(database) {
+    var _this2 = this;
 
-            case 3:
-              return _context3.abrupt('return', _context3.sent);
+    (0, _classCallCheck3.default)(this, OrganizationDatabaseRepository);
 
-            case 4:
-            case 'end':
-              return _context3.stop();
-          }
-        }
-      }, _callee3, _this);
-    }));
+    var _this = (0, _possibleConstructorReturn3.default)(this, (OrganizationDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(OrganizationDatabaseRepository)).call(this, database, _collectionNames2.default.ORGANIZATIONS));
 
-    return function () {
-      return _ref3.apply(this, arguments);
-    };
-  }();
+    _this._collectionName = _collectionNames2.default.ORGANIZATIONS;
 
-  this.getByUserID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
-        while (1) {
-          switch (_context4.prev = _context4.next) {
-            case 0:
-              _context4.next = 2;
-              return _this._database.find(_this._collectionName, {
-                user_ids: userID
-              });
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.next = 2;
+                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
 
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
+              case 2:
+                return _context.abrupt('return', _context.sent);
 
-            case 3:
-            case 'end':
-              return _context4.stop();
+              case 3:
+              case 'end':
+                return _context.stop();
+            }
           }
-        }
-      }, _callee4, _this);
-    }));
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.getAll = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var query;
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                // TODO - this should probably just query the organization
+                query = userID ? { ownerID: userID } : {};
+                _context3.next = 3;
+                return _this._database.find(_this._collectionName, query);
+
+              case 3:
+                return _context3.abrupt('return', _context3.sent);
+
+              case 4:
+              case 'end':
+                return _context3.stop();
+            }
+          }
+        }, _callee3, _this2);
+      }));
+
+      return function () {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.getByUserID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.find(_this._collectionName, {
+                  user_ids: userID
+                });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
+          while (1) {
+            switch (_context5.prev = _context5.next) {
+              case 0:
+                _context5.next = 2;
+                return _this._database.findOne(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context5.abrupt('return', _context5.sent);
+
+              case 3:
+              case 'end':
+                return _context5.stop();
+            }
+          }
+        }, _callee5, _this2);
+      }));
 
-    return function (_x4) {
-      return _ref4.apply(this, arguments);
-    };
-  }();
+      return function (_x5) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
 
-  this.getByID = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
+    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
-          switch (_context5.prev = _context5.next) {
+          switch (_context6.prev = _context6.next) {
             case 0:
-              _context5.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id });
+              throw new Error('The method is not implemented');
 
-            case 2:
-              return _context5.abrupt('return', _context5.sent);
-
-            case 3:
+            case 1:
             case 'end':
-              return _context5.stop();
+              return _context6.stop();
           }
         }
-      }, _callee5, _this);
+      }, _callee6, _this2);
     }));
 
-    return function (_x5) {
-      return _ref5.apply(this, arguments);
-    };
-  }();
-
-  this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
-    return _regenerator2.default.wrap(function _callee6$(_context6) {
-      while (1) {
-        switch (_context6.prev = _context6.next) {
-          case 0:
-            throw new Error('The method is not implemented');
-
-          case 1:
-          case 'end':
-            return _context6.stop();
-        }
-      }
-    }, _callee6, _this);
-  }));
+    _this._database = database;
+    return _this;
+  }
 
-  this._database = database;
-};
+  return OrganizationDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = OrganizationDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/ProductConfigDatabaseRepository.js b/dist/repository/ProductConfigDatabaseRepository.js
index 4247fcd1..fb38681c 100644
--- a/dist/repository/ProductConfigDatabaseRepository.js
+++ b/dist/repository/ProductConfigDatabaseRepository.js
@@ -16,169 +16,195 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
-var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
 
-var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
 
-var _collectionNames = require('./collectionNames');
-
-var _collectionNames2 = _interopRequireDefault(_collectionNames);
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
-var ProductConfigDatabaseRepository = function ProductConfigDatabaseRepository(database) {
-  var _this = this;
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
 
-  (0, _classCallCheck3.default)(this, ProductConfigDatabaseRepository);
-  this._collectionName = _collectionNames2.default.PRODUCT_CONFIGS;
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
 
-  this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
-        while (1) {
-          switch (_context.prev = _context.next) {
-            case 0:
-              _context.next = 2;
-              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+var _inherits2 = require('babel-runtime/helpers/inherits');
 
-            case 2:
-              return _context.abrupt('return', _context.sent);
+var _inherits3 = _interopRequireDefault(_inherits2);
 
-            case 3:
-            case 'end':
-              return _context.stop();
-          }
-        }
-      }, _callee, _this);
-    }));
+var _collectionNames = require('./collectionNames');
 
-    return function (_x) {
-      return _ref.apply(this, arguments);
-    };
-  }();
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
 
-  this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
+var _BaseRepository2 = require('./BaseRepository');
 
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
 
-            case 3:
-            case 'end':
-              return _context2.stop();
-          }
-        }
-      }, _callee2, _this);
-    }));
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-    return function (_x2) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
+var ProductConfigDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(ProductConfigDatabaseRepository, _BaseRepository);
 
-  this.getAll = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-      var query;
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
-        while (1) {
-          switch (_context3.prev = _context3.next) {
-            case 0:
-              // TODO - this should probably just query the organization
-              query = userID ? { ownerID: userID } : {};
-              _context3.next = 3;
-              return _this._database.find(_this._collectionName, query);
+  function ProductConfigDatabaseRepository(database) {
+    var _this2 = this;
 
-            case 3:
-              return _context3.abrupt('return', _context3.sent);
+    (0, _classCallCheck3.default)(this, ProductConfigDatabaseRepository);
 
-            case 4:
-            case 'end':
-              return _context3.stop();
-          }
-        }
-      }, _callee3, _this);
-    }));
+    var _this = (0, _possibleConstructorReturn3.default)(this, (ProductConfigDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(ProductConfigDatabaseRepository)).call(this, database, _collectionNames2.default.PRODUCT_CONFIGS));
 
-    return function () {
-      return _ref3.apply(this, arguments);
-    };
-  }();
+    _this._collectionName = _collectionNames2.default.PRODUCT_CONFIGS;
 
-  this.getByProductID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
-        while (1) {
-          switch (_context4.prev = _context4.next) {
-            case 0:
-              _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, {
-                product_id: productID
-              });
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.next = 2;
+                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
 
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
+              case 2:
+                return _context.abrupt('return', _context.sent);
 
-            case 3:
-            case 'end':
-              return _context4.stop();
+              case 3:
+              case 'end':
+                return _context.stop();
+            }
           }
-        }
-      }, _callee4, _this);
-    }));
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.getAll = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var query;
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                // TODO - this should probably just query the organization
+                query = userID ? { ownerID: userID } : {};
+                _context3.next = 3;
+                return _this._database.find(_this._collectionName, query);
+
+              case 3:
+                return _context3.abrupt('return', _context3.sent);
+
+              case 4:
+              case 'end':
+                return _context3.stop();
+            }
+          }
+        }, _callee3, _this2);
+      }));
+
+      return function () {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.getByProductID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.findOne(_this._collectionName, {
+                  product_id: productID
+                });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
+          while (1) {
+            switch (_context5.prev = _context5.next) {
+              case 0:
+                _context5.next = 2;
+                return _this._database.findOne(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context5.abrupt('return', _context5.sent);
+
+              case 3:
+              case 'end':
+                return _context5.stop();
+            }
+          }
+        }, _callee5, _this2);
+      }));
 
-    return function (_x4) {
-      return _ref4.apply(this, arguments);
-    };
-  }();
+      return function (_x5) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
 
-  this.getByID = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
+    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+      return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
-          switch (_context5.prev = _context5.next) {
+          switch (_context6.prev = _context6.next) {
             case 0:
-              _context5.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id });
+              throw new Error('The method is not implemented');
 
-            case 2:
-              return _context5.abrupt('return', _context5.sent);
-
-            case 3:
+            case 1:
             case 'end':
-              return _context5.stop();
+              return _context6.stop();
           }
         }
-      }, _callee5, _this);
+      }, _callee6, _this2);
     }));
 
-    return function (_x5) {
-      return _ref5.apply(this, arguments);
-    };
-  }();
-
-  this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
-    return _regenerator2.default.wrap(function _callee6$(_context6) {
-      while (1) {
-        switch (_context6.prev = _context6.next) {
-          case 0:
-            throw new Error('The method is not implemented');
-
-          case 1:
-          case 'end':
-            return _context6.stop();
-        }
-      }
-    }, _callee6, _this);
-  }));
+    _this._database = database;
+    return _this;
+  }
 
-  this._database = database;
-};
+  return ProductConfigDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = ProductConfigDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
index 0cba9de0..c1d14725 100644
--- a/dist/repository/ProductDatabaseRepository.js
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -16,246 +16,272 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
 var _collectionNames = require('./collectionNames');
 
 var _collectionNames2 = _interopRequireDefault(_collectionNames);
 
+var _BaseRepository2 = require('./BaseRepository');
+
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var ProductDatabaseRepository = function ProductDatabaseRepository(database) {
-  var _this = this;
-
-  (0, _classCallCheck3.default)(this, ProductDatabaseRepository);
-  this._collectionName = _collectionNames2.default.PRODUCTS;
-
-  this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
-        while (1) {
-          switch (_context.prev = _context.next) {
-            case 0:
-              _context.t0 = _this._database;
-              _context.t1 = _this._collectionName;
-              _context.t2 = _extends3.default;
-              _context.t3 = {};
-              _context.next = 6;
-              return _this._formatProduct(model);
-
-            case 6:
-              _context.t4 = _context.sent;
-              _context.t5 = new Date();
-              _context.next = 10;
-              return _this._database.count(_this._collectionName);
-
-            case 10:
-              _context.t6 = _context.sent;
-              _context.t7 = _context.t6 + 1;
-              _context.t8 = {
-                created_at: _context.t5,
-                product_id: _context.t7
-              };
-              _context.t9 = (0, _context.t2)(_context.t3, _context.t4, _context.t8);
-              _context.next = 16;
-              return _context.t0.insertOne.call(_context.t0, _context.t1, _context.t9);
-
-            case 16:
-              return _context.abrupt('return', _context.sent);
-
-            case 17:
-            case 'end':
-              return _context.stop();
+var ProductDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(ProductDatabaseRepository, _BaseRepository);
+
+  function ProductDatabaseRepository(database) {
+    var _this2 = this;
+
+    (0, _classCallCheck3.default)(this, ProductDatabaseRepository);
+
+    var _this = (0, _possibleConstructorReturn3.default)(this, (ProductDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(ProductDatabaseRepository)).call(this, database, _collectionNames2.default.PRODUCTS));
+
+    _this._collectionName = _collectionNames2.default.PRODUCTS;
+
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.t0 = _this._database;
+                _context.t1 = _this._collectionName;
+                _context.t2 = _extends3.default;
+                _context.t3 = {};
+                _context.next = 6;
+                return _this._formatProduct(model);
+
+              case 6:
+                _context.t4 = _context.sent;
+                _context.t5 = new Date();
+                _context.next = 10;
+                return _this._database.count(_this._collectionName);
+
+              case 10:
+                _context.t6 = _context.sent;
+                _context.t7 = _context.t6 + 1;
+                _context.t8 = {
+                  created_at: _context.t5,
+                  product_id: _context.t7
+                };
+                _context.t9 = (0, _context.t2)(_context.t3, _context.t4, _context.t8);
+                _context.next = 16;
+                return _context.t0.insertOne.call(_context.t0, _context.t1, _context.t9);
+
+              case 16:
+                return _context.abrupt('return', _context.sent);
+
+              case 17:
+              case 'end':
+                return _context.stop();
+            }
           }
-        }
-      }, _callee, _this);
-    }));
-
-    return function (_x) {
-      return _ref.apply(this, arguments);
-    };
-  }();
-
-  this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
-
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
-
-            case 3:
-            case 'end':
-              return _context2.stop();
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
           }
-        }
-      }, _callee2, _this);
-    }));
-
-    return function (_x2) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
-
-  this.getAll = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-      var query;
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
-        while (1) {
-          switch (_context3.prev = _context3.next) {
-            case 0:
-              // TODO - this should probably just query the organization
-              query = userID ? { ownerID: userID } : {};
-              _context3.next = 3;
-              return _this._database.find(_this._collectionName, query);
-
-            case 3:
-              return _context3.abrupt('return', _context3.sent);
-
-            case 4:
-            case 'end':
-              return _context3.stop();
+        }, _callee2, _this2);
+      }));
+
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.getAll = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var query;
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                // TODO - this should probably just query the organization
+                query = userID ? { ownerID: userID } : {};
+                _context3.next = 3;
+                return _this._database.find(_this._collectionName, query);
+
+              case 3:
+                return _context3.abrupt('return', _context3.sent);
+
+              case 4:
+              case 'end':
+                return _context3.stop();
+            }
           }
-        }
-      }, _callee3, _this);
-    }));
-
-    return function () {
-      return _ref3.apply(this, arguments);
-    };
-  }();
-
-  this.getByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
-        while (1) {
-          switch (_context4.prev = _context4.next) {
-            case 0:
-              _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id });
-
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
-
-            case 3:
-            case 'end':
-              return _context4.stop();
+        }, _callee3, _this2);
+      }));
+
+      return function () {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.findOne(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
           }
-        }
-      }, _callee4, _this);
-    }));
-
-    return function (_x4) {
-      return _ref4.apply(this, arguments);
-    };
-  }();
-
-  this.getByIDOrSlug = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIDOrSlug) {
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
-        while (1) {
-          switch (_context5.prev = _context5.next) {
-            case 0:
-              _context5.next = 2;
-              return _this._database.findOne(_this._collectionName, {
-                $or: [{ product_id: productIDOrSlug }, { slug: productIDOrSlug }]
-              });
-
-            case 2:
-              return _context5.abrupt('return', _context5.sent);
-
-            case 3:
-            case 'end':
-              return _context5.stop();
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
+
+    _this.getByIDOrSlug = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIDOrSlug) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
+          while (1) {
+            switch (_context5.prev = _context5.next) {
+              case 0:
+                _context5.next = 2;
+                return _this._database.findOne(_this._collectionName, {
+                  $or: [{ product_id: productIDOrSlug }, { slug: productIDOrSlug }]
+                });
+
+              case 2:
+                return _context5.abrupt('return', _context5.sent);
+
+              case 3:
+              case 'end':
+                return _context5.stop();
+            }
           }
-        }
-      }, _callee5, _this);
-    }));
-
-    return function (_x5) {
-      return _ref5.apply(this, arguments);
-    };
-  }();
-
-  this.updateByID = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productID, product) {
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
-        while (1) {
-          switch (_context6.prev = _context6.next) {
-            case 0:
-              _context6.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: productID }, { $set: (0, _extends3.default)({}, product) });
-
-            case 2:
-              return _context6.abrupt('return', _context6.sent);
-
-            case 3:
-            case 'end':
-              return _context6.stop();
+        }, _callee5, _this2);
+      }));
+
+      return function (_x5) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
+
+    _this.updateByID = function () {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productID, product) {
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                _context6.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { _id: productID }, { $set: (0, _extends3.default)({}, product) });
+
+              case 2:
+                return _context6.abrupt('return', _context6.sent);
+
+              case 3:
+              case 'end':
+                return _context6.stop();
+            }
           }
-        }
-      }, _callee6, _this);
-    }));
-
-    return function (_x6, _x7) {
-      return _ref6.apply(this, arguments);
-    };
-  }();
-
-  this._formatProduct = function () {
-    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(product) {
-      var slug, existingProduct;
-      return _regenerator2.default.wrap(function _callee7$(_context7) {
-        while (1) {
-          switch (_context7.prev = _context7.next) {
-            case 0:
-              slug = (product.name.trim() + ' ' + product.hardware_version.trim()).toLowerCase().replace(/\s+/g, '-') // Replace spaces with -
-              .replace(/[^\w-]+/g, '') // Remove all non-word chars
-              .replace(/--+/g, '-') // Replace multiple - with single -
-              .replace(/^-+/, '') // Trim - from start of text
-              .replace(/-+$/, ''); // Trim - from end of text
-
-              _context7.next = 3;
-              return _this._database.findOne(_this._collectionName, {
-                slug: slug
-              });
-
-            case 3:
-              existingProduct = _context7.sent;
-
-              if (!(existingProduct && existingProduct.id !== product.id)) {
-                _context7.next = 6;
-                break;
-              }
-
-              throw new Error('Product name or version already in use');
-
-            case 6:
-              return _context7.abrupt('return', (0, _extends3.default)({}, product, {
-                slug: slug
-              }));
-
-            case 7:
-            case 'end':
-              return _context7.stop();
+        }, _callee6, _this2);
+      }));
+
+      return function (_x6, _x7) {
+        return _ref6.apply(this, arguments);
+      };
+    }();
+
+    _this._formatProduct = function () {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(product) {
+        var slug, existingProduct;
+        return _regenerator2.default.wrap(function _callee7$(_context7) {
+          while (1) {
+            switch (_context7.prev = _context7.next) {
+              case 0:
+                slug = (product.name.trim() + ' ' + product.hardware_version.trim()).toLowerCase().replace(/\s+/g, '-') // Replace spaces with -
+                .replace(/[^\w-]+/g, '') // Remove all non-word chars
+                .replace(/--+/g, '-') // Replace multiple - with single -
+                .replace(/^-+/, '') // Trim - from start of text
+                .replace(/-+$/, ''); // Trim - from end of text
+
+                _context7.next = 3;
+                return _this._database.findOne(_this._collectionName, {
+                  slug: slug
+                });
+
+              case 3:
+                existingProduct = _context7.sent;
+
+                if (!(existingProduct && existingProduct.id !== product.id)) {
+                  _context7.next = 6;
+                  break;
+                }
+
+                throw new Error('Product name or version already in use');
+
+              case 6:
+                return _context7.abrupt('return', (0, _extends3.default)({}, product, {
+                  slug: slug
+                }));
+
+              case 7:
+              case 'end':
+                return _context7.stop();
+            }
           }
-        }
-      }, _callee7, _this);
-    }));
+        }, _callee7, _this2);
+      }));
+
+      return function (_x8) {
+        return _ref7.apply(this, arguments);
+      };
+    }();
 
-    return function (_x8) {
-      return _ref7.apply(this, arguments);
-    };
-  }();
+    _this._database = database;
+    return _this;
+  }
 
-  this._database = database;
-};
+  return ProductDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = ProductDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/ProductDeviceDatabaseRepository.js b/dist/repository/ProductDeviceDatabaseRepository.js
new file mode 100644
index 00000000..fb60793a
--- /dev/null
+++ b/dist/repository/ProductDeviceDatabaseRepository.js
@@ -0,0 +1,264 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _regenerator = require('babel-runtime/regenerator');
+
+var _regenerator2 = _interopRequireDefault(_regenerator);
+
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
+var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
+
+var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
+
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _collectionNames = require('./collectionNames');
+
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
+
+var _BaseRepository2 = require('./BaseRepository');
+
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var ProductDeviceDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(ProductDeviceDatabaseRepository, _BaseRepository);
+
+  function ProductDeviceDatabaseRepository(database) {
+    var _this2 = this;
+
+    (0, _classCallCheck3.default)(this, ProductDeviceDatabaseRepository);
+
+    var _this = (0, _possibleConstructorReturn3.default)(this, (ProductDeviceDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(ProductDeviceDatabaseRepository)).call(this, database, _collectionNames2.default.PRODUCT_DEVICES));
+
+    _this._collectionName = _collectionNames2.default.PRODUCT_DEVICES;
+
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.next = 2;
+                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+
+              case 2:
+                return _context.abrupt('return', _context.sent);
+
+              case 3:
+              case 'end':
+                return _context.stop();
+            }
+          }
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.getAll = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var query;
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                // TODO - this should probably just query the organization
+                query = userID ? { ownerID: userID } : {};
+                _context3.next = 3;
+                return _this._database.find(_this._collectionName, query);
+
+              case 3:
+                return _context3.abrupt('return', _context3.sent);
+
+              case 4:
+              case 'end':
+                return _context3.stop();
+            }
+          }
+        }, _callee3, _this2);
+      }));
+
+      return function () {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.getAllByProductID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID, page, pageSize) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.find(_this._collectionName, {
+                  page: page,
+                  pageSize: pageSize,
+                  productID: productID
+                });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4, _x5, _x6) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
+          while (1) {
+            switch (_context5.prev = _context5.next) {
+              case 0:
+                _context5.next = 2;
+                return _this._database.findOne(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context5.abrupt('return', _context5.sent);
+
+              case 3:
+              case 'end':
+                return _context5.stop();
+            }
+          }
+        }, _callee5, _this2);
+      }));
+
+      return function (_x7) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
+
+    _this.getManyFromDeviceIDs = function () {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceIDs) {
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                _context6.next = 2;
+                return _this._database.find(_this._collectionName, {
+                  deviceID: { $in: deviceIDs }
+                });
+
+              case 2:
+                return _context6.abrupt('return', _context6.sent);
+
+              case 3:
+              case 'end':
+                return _context6.stop();
+            }
+          }
+        }, _callee6, _this2);
+      }));
+
+      return function (_x8) {
+        return _ref6.apply(this, arguments);
+      };
+    }();
+
+    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
+      return _regenerator2.default.wrap(function _callee7$(_context7) {
+        while (1) {
+          switch (_context7.prev = _context7.next) {
+            case 0:
+              throw new Error('The method is not implemented');
+
+            case 1:
+            case 'end':
+              return _context7.stop();
+          }
+        }
+      }, _callee7, _this2);
+    }));
+
+    _this.updateByID = function () {
+      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productDeviceID, productDevice) {
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
+          while (1) {
+            switch (_context8.prev = _context8.next) {
+              case 0:
+                _context8.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { _id: productDeviceID }, { $set: (0, _extends3.default)({}, productDevice) });
+
+              case 2:
+                return _context8.abrupt('return', _context8.sent);
+
+              case 3:
+              case 'end':
+                return _context8.stop();
+            }
+          }
+        }, _callee8, _this2);
+      }));
+
+      return function (_x9, _x10) {
+        return _ref8.apply(this, arguments);
+      };
+    }();
+
+    _this._database = database;
+    return _this;
+  }
+
+  return ProductDeviceDatabaseRepository;
+}(_BaseRepository3.default);
+
+exports.default = ProductDeviceDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/ProductFirmwareDatabaseRepository.js b/dist/repository/ProductFirmwareDatabaseRepository.js
index 807470f5..2d47f0ce 100644
--- a/dist/repository/ProductFirmwareDatabaseRepository.js
+++ b/dist/repository/ProductFirmwareDatabaseRepository.js
@@ -16,179 +16,205 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
 var _collectionNames = require('./collectionNames');
 
 var _collectionNames2 = _interopRequireDefault(_collectionNames);
 
+var _BaseRepository2 = require('./BaseRepository');
+
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var ProductFirmwareDatabaseRepository = function ProductFirmwareDatabaseRepository(database) {
-  var _this = this;
-
-  (0, _classCallCheck3.default)(this, ProductFirmwareDatabaseRepository);
-  this._collectionName = _collectionNames2.default.PRODUCT_FIRMWARE;
-
-  this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
-        while (1) {
-          switch (_context.prev = _context.next) {
-            case 0:
-              _context.next = 2;
-              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
-                updated_at: new Date()
-              }));
-
-            case 2:
-              return _context.abrupt('return', _context.sent);
-
-            case 3:
-            case 'end':
-              return _context.stop();
+var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(ProductFirmwareDatabaseRepository, _BaseRepository);
+
+  function ProductFirmwareDatabaseRepository(database) {
+    var _this2 = this;
+
+    (0, _classCallCheck3.default)(this, ProductFirmwareDatabaseRepository);
+
+    var _this = (0, _possibleConstructorReturn3.default)(this, (ProductFirmwareDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(ProductFirmwareDatabaseRepository)).call(this, database, _collectionNames2.default.PRODUCT_FIRMWARE));
+
+    _this._collectionName = _collectionNames2.default.PRODUCT_FIRMWARE;
+
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.next = 2;
+                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
+                  updated_at: new Date()
+                }));
+
+              case 2:
+                return _context.abrupt('return', _context.sent);
+
+              case 3:
+              case 'end':
+                return _context.stop();
+            }
           }
-        }
-      }, _callee, _this);
-    }));
-
-    return function (_x) {
-      return _ref.apply(this, arguments);
-    };
-  }();
-
-  this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
-
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
-
-            case 3:
-            case 'end':
-              return _context2.stop();
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
           }
-        }
-      }, _callee2, _this);
-    }));
-
-    return function (_x2) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
-
-  this.getAll = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-      var query;
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
-        while (1) {
-          switch (_context3.prev = _context3.next) {
-            case 0:
-              // TODO - this should probably just query the organization
-              query = userID ? { ownerID: userID } : {};
-              _context3.next = 3;
-              return _this._database.find(_this._collectionName, query);
-
-            case 3:
-              return _context3.abrupt('return', _context3.sent);
-
-            case 4:
-            case 'end':
-              return _context3.stop();
+        }, _callee2, _this2);
+      }));
+
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.getAll = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var query;
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                // TODO - this should probably just query the organization
+                query = userID ? { ownerID: userID } : {};
+                _context3.next = 3;
+                return _this._database.find(_this._collectionName, query);
+
+              case 3:
+                return _context3.abrupt('return', _context3.sent);
+
+              case 4:
+              case 'end':
+                return _context3.stop();
+            }
           }
-        }
-      }, _callee3, _this);
-    }));
-
-    return function () {
-      return _ref3.apply(this, arguments);
-    };
-  }();
-
-  this.getAllByProductID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
-        while (1) {
-          switch (_context4.prev = _context4.next) {
-            case 0:
-              _context4.next = 2;
-              return _this._database.find(_this._collectionName, { product_id: productID });
-
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
-
-            case 3:
-            case 'end':
-              return _context4.stop();
+        }, _callee3, _this2);
+      }));
+
+      return function () {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.getAllByProductID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.find(_this._collectionName, { product_id: productID });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
           }
-        }
-      }, _callee4, _this);
-    }));
-
-    return function (_x4) {
-      return _ref4.apply(this, arguments);
-    };
-  }();
-
-  this.getByID = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
-      return _regenerator2.default.wrap(function _callee5$(_context5) {
-        while (1) {
-          switch (_context5.prev = _context5.next) {
-            case 0:
-              _context5.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id });
-
-            case 2:
-              return _context5.abrupt('return', _context5.sent);
-
-            case 3:
-            case 'end':
-              return _context5.stop();
+        }, _callee4, _this2);
+      }));
+
+      return function (_x4) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
+          while (1) {
+            switch (_context5.prev = _context5.next) {
+              case 0:
+                _context5.next = 2;
+                return _this._database.findOne(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context5.abrupt('return', _context5.sent);
+
+              case 3:
+              case 'end':
+                return _context5.stop();
+            }
           }
-        }
-      }, _callee5, _this);
-    }));
-
-    return function (_x5) {
-      return _ref5.apply(this, arguments);
-    };
-  }();
-
-  this.updateByID = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productFirmwareID, productFirmware) {
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
-        while (1) {
-          switch (_context6.prev = _context6.next) {
-            case 0:
-              _context6.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, { updated_at: new Date() }) });
-
-            case 2:
-              return _context6.abrupt('return', _context6.sent);
-
-            case 3:
-            case 'end':
-              return _context6.stop();
+        }, _callee5, _this2);
+      }));
+
+      return function (_x5) {
+        return _ref5.apply(this, arguments);
+      };
+    }();
+
+    _this.updateByID = function () {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productFirmwareID, productFirmware) {
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                _context6.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, { updated_at: new Date() }) });
+
+              case 2:
+                return _context6.abrupt('return', _context6.sent);
+
+              case 3:
+              case 'end':
+                return _context6.stop();
+            }
           }
-        }
-      }, _callee6, _this);
-    }));
+        }, _callee6, _this2);
+      }));
+
+      return function (_x6, _x7) {
+        return _ref6.apply(this, arguments);
+      };
+    }();
 
-    return function (_x6, _x7) {
-      return _ref6.apply(this, arguments);
-    };
-  }();
+    _this._database = database;
+    return _this;
+  }
 
-  this._database = database;
-};
+  return ProductFirmwareDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = ProductFirmwareDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 754f178d..0ec04631 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -16,10 +16,26 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
+
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
+
+var _inherits2 = require('babel-runtime/helpers/inherits');
+
+var _inherits3 = _interopRequireDefault(_inherits2);
+
+var _BaseRepository2 = require('./BaseRepository');
+
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
+
 var _collectionNames = require('./collectionNames');
 
 var _collectionNames2 = _interopRequireDefault(_collectionNames);
@@ -34,386 +50,396 @@ var _HttpError2 = _interopRequireDefault(_HttpError);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var UserDatabaseRepository = function UserDatabaseRepository(database) {
-  var _this = this;
+var UserDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(UserDatabaseRepository, _BaseRepository);
 
-  (0, _classCallCheck3.default)(this, UserDatabaseRepository);
-  this._collectionName = _collectionNames2.default.USERS;
+  function UserDatabaseRepository(database) {
+    var _this2 = this;
 
-  this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(user) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
-        while (1) {
-          switch (_context.prev = _context.next) {
-            case 0:
-              _context.next = 2;
-              return _this._database.insertOne(_this._collectionName, user);
+    (0, _classCallCheck3.default)(this, UserDatabaseRepository);
 
-            case 2:
-              return _context.abrupt('return', _context.sent);
+    var _this = (0, _possibleConstructorReturn3.default)(this, (UserDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(UserDatabaseRepository)).call(this, database, _collectionNames2.default.USERS));
 
-            case 3:
-            case 'end':
-              return _context.stop();
-          }
-        }
-      }, _callee, _this);
-    }));
+    _this._collectionName = _collectionNames2.default.USERS;
 
-    return function (_x) {
-      return _ref.apply(this, arguments);
-    };
-  }();
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(user) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.next = 2;
+                return _this._database.insertOne(_this._collectionName, user);
 
-  this.createWithCredentials = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
-      var userRole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
-      var username, password, salt, passwordHash, modelToSave;
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              username = userCredentials.username, password = userCredentials.password;
-              _context2.next = 3;
-              return _PasswordHasher2.default.generateSalt();
-
-            case 3:
-              salt = _context2.sent;
-              _context2.next = 6;
-              return _PasswordHasher2.default.hash(password, salt);
-
-            case 6:
-              passwordHash = _context2.sent;
-              modelToSave = {
-                accessTokens: [],
-                created_at: new Date(),
-                passwordHash: passwordHash,
-                role: userRole,
-                salt: salt,
-                username: username
-              };
-              _context2.next = 10;
-              return _this._database.insertOne(_this._collectionName, modelToSave);
-
-            case 10:
-              return _context2.abrupt('return', _context2.sent);
-
-            case 11:
-            case 'end':
-              return _context2.stop();
-          }
-        }
-      }, _callee2, _this);
-    }));
-
-    return function (_x2) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
-
-  this.deleteAccessToken = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID, accessToken) {
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
-        while (1) {
-          switch (_context3.prev = _context3.next) {
-            case 0:
-              _context3.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $pull: { accessTokens: { accessToken: accessToken } } });
+              case 2:
+                return _context.abrupt('return', _context.sent);
 
-            case 2:
-              return _context3.abrupt('return', _context3.sent);
-
-            case 3:
-            case 'end':
-              return _context3.stop();
+              case 3:
+              case 'end':
+                return _context.stop();
+            }
           }
-        }
-      }, _callee3, _this);
-    }));
-
-    return function (_x4, _x5) {
-      return _ref3.apply(this, arguments);
-    };
-  }();
-
-  this.deleteByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
-        while (1) {
-          switch (_context4.prev = _context4.next) {
-            case 0:
-              _context4.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
-
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
-
-            case 3:
-            case 'end':
-              return _context4.stop();
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.createWithCredentials = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
+        var userRole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+        var username, password, salt, passwordHash, modelToSave;
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                username = userCredentials.username, password = userCredentials.password;
+                _context2.next = 3;
+                return _PasswordHasher2.default.generateSalt();
+
+              case 3:
+                salt = _context2.sent;
+                _context2.next = 6;
+                return _PasswordHasher2.default.hash(password, salt);
+
+              case 6:
+                passwordHash = _context2.sent;
+                modelToSave = {
+                  accessTokens: [],
+                  created_at: new Date(),
+                  passwordHash: passwordHash,
+                  role: userRole,
+                  salt: salt,
+                  username: username
+                };
+                _context2.next = 10;
+                return _this._database.insertOne(_this._collectionName, modelToSave);
+
+              case 10:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 11:
+              case 'end':
+                return _context2.stop();
+            }
           }
-        }
-      }, _callee4, _this);
-    }));
-
-    return function (_x6) {
-      return _ref4.apply(this, arguments);
-    };
-  }();
-
-  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
-    return _regenerator2.default.wrap(function _callee5$(_context5) {
-      while (1) {
-        switch (_context5.prev = _context5.next) {
-          case 0:
-            throw new Error('The method is not implemented');
-
-          case 1:
-          case 'end':
-            return _context5.stop();
-        }
-      }
-    }, _callee5, _this);
-  }));
-
-  this.getByAccessToken = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(accessToken) {
-      var user;
-      return _regenerator2.default.wrap(function _callee6$(_context6) {
-        while (1) {
-          switch (_context6.prev = _context6.next) {
-            case 0:
-              _context6.next = 2;
-              return _this._database.findOne(_this._collectionName, {
-                accessTokens: { $elemMatch: { accessToken: accessToken } }
-              });
-
-            case 2:
-              user = _context6.sent;
-
-              if (user) {
-                _context6.next = 7;
-                break;
-              }
-
-              _context6.next = 6;
-              return _this._database.findOne(_this._collectionName, {
-                'accessTokens.accessToken': accessToken
-              });
-
-            case 6:
-              user = _context6.sent;
-
-            case 7:
-              return _context6.abrupt('return', user);
-
-            case 8:
-            case 'end':
-              return _context6.stop();
+        }, _callee2, _this2);
+      }));
+
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteAccessToken = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID, accessToken) {
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                _context3.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $pull: { accessTokens: { accessToken: accessToken } } });
+
+              case 2:
+                return _context3.abrupt('return', _context3.sent);
+
+              case 3:
+              case 'end':
+                return _context3.stop();
+            }
           }
-        }
-      }, _callee6, _this);
-    }));
+        }, _callee3, _this2);
+      }));
+
+      return function (_x4, _x5) {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.remove(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
 
-    return function (_x7) {
-      return _ref6.apply(this, arguments);
-    };
-  }();
+      return function (_x6) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
 
-  this.getByID = function () {
-    var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
-      return _regenerator2.default.wrap(function _callee7$(_context7) {
+    _this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
-          switch (_context7.prev = _context7.next) {
+          switch (_context5.prev = _context5.next) {
             case 0:
               throw new Error('The method is not implemented');
 
             case 1:
             case 'end':
-              return _context7.stop();
+              return _context5.stop();
           }
         }
-      }, _callee7, _this);
+      }, _callee5, _this2);
     }));
 
-    return function (_x8) {
-      return _ref7.apply(this, arguments);
-    };
-  }();
-
-  this.getByUsername = function () {
-    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(username) {
-      return _regenerator2.default.wrap(function _callee8$(_context8) {
-        while (1) {
-          switch (_context8.prev = _context8.next) {
-            case 0:
-              _context8.next = 2;
-              return _this._database.findOne(_this._collectionName, { username: username });
-
-            case 2:
-              return _context8.abrupt('return', _context8.sent);
-
-            case 3:
-            case 'end':
-              return _context8.stop();
+    _this.getByAccessToken = function () {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(accessToken) {
+        var user;
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
+          while (1) {
+            switch (_context6.prev = _context6.next) {
+              case 0:
+                _context6.next = 2;
+                return _this._database.findOne(_this._collectionName, {
+                  accessTokens: { $elemMatch: { accessToken: accessToken } }
+                });
+
+              case 2:
+                user = _context6.sent;
+
+                if (user) {
+                  _context6.next = 7;
+                  break;
+                }
+
+                _context6.next = 6;
+                return _this._database.findOne(_this._collectionName, {
+                  'accessTokens.accessToken': accessToken
+                });
+
+              case 6:
+                user = _context6.sent;
+
+              case 7:
+                return _context6.abrupt('return', user);
+
+              case 8:
+              case 'end':
+                return _context6.stop();
+            }
           }
-        }
-      }, _callee8, _this);
-    }));
-
-    return function (_x9) {
-      return _ref8.apply(this, arguments);
-    };
-  }();
-
-  this.getCurrentUser = function () {
-    return _this._currentUser;
-  };
-
-  this.isUserNameInUse = function () {
-    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(username) {
-      return _regenerator2.default.wrap(function _callee9$(_context9) {
-        while (1) {
-          switch (_context9.prev = _context9.next) {
-            case 0:
-              _context9.next = 2;
-              return _this.getByUsername(username);
-
-            case 2:
-              return _context9.abrupt('return', !!_context9.sent);
-
-            case 3:
-            case 'end':
-              return _context9.stop();
+        }, _callee6, _this2);
+      }));
+
+      return function (_x7) {
+        return _ref6.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
+        return _regenerator2.default.wrap(function _callee7$(_context7) {
+          while (1) {
+            switch (_context7.prev = _context7.next) {
+              case 0:
+                throw new Error('The method is not implemented');
+
+              case 1:
+              case 'end':
+                return _context7.stop();
+            }
           }
-        }
-      }, _callee9, _this);
-    }));
-
-    return function (_x10) {
-      return _ref9.apply(this, arguments);
-    };
-  }();
-
-  this.saveAccessToken = function () {
-    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(userID, tokenObject) {
-      return _regenerator2.default.wrap(function _callee10$(_context10) {
-        while (1) {
-          switch (_context10.prev = _context10.next) {
-            case 0:
-              _context10.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $push: { accessTokens: tokenObject } });
-
-            case 2:
-              return _context10.abrupt('return', _context10.sent);
-
-            case 3:
-            case 'end':
-              return _context10.stop();
+        }, _callee7, _this2);
+      }));
+
+      return function (_x8) {
+        return _ref7.apply(this, arguments);
+      };
+    }();
+
+    _this.getByUsername = function () {
+      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(username) {
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
+          while (1) {
+            switch (_context8.prev = _context8.next) {
+              case 0:
+                _context8.next = 2;
+                return _this._database.findOne(_this._collectionName, { username: username });
+
+              case 2:
+                return _context8.abrupt('return', _context8.sent);
+
+              case 3:
+              case 'end':
+                return _context8.stop();
+            }
           }
-        }
-      }, _callee10, _this);
-    }));
+        }, _callee8, _this2);
+      }));
 
-    return function (_x11, _x12) {
-      return _ref10.apply(this, arguments);
-    };
-  }();
-
-  this.setCurrentUser = function (user) {
-    _this._currentUser = user;
-  };
-
-  this.updateByID = function () {
-    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id, props) {
-      return _regenerator2.default.wrap(function _callee11$(_context11) {
-        while (1) {
-          switch (_context11.prev = _context11.next) {
-            case 0:
-              _context11.next = 2;
-              return _this._database.findAndModify(_this._collectionName, { _id: id }, { $set: (0, _extends3.default)({}, props) });
-
-            case 2:
-              return _context11.abrupt('return', _context11.sent);
-
-            case 3:
-            case 'end':
-              return _context11.stop();
-          }
-        }
-      }, _callee11, _this);
-    }));
+      return function (_x9) {
+        return _ref8.apply(this, arguments);
+      };
+    }();
 
-    return function (_x13, _x14) {
-      return _ref11.apply(this, arguments);
+    _this.getCurrentUser = function () {
+      return _this._currentUser;
     };
-  }();
-
-  this.validateLogin = function () {
-    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username, password) {
-      var user, hash;
-      return _regenerator2.default.wrap(function _callee12$(_context12) {
-        while (1) {
-          switch (_context12.prev = _context12.next) {
-            case 0:
-              _context12.prev = 0;
-              _context12.next = 3;
-              return _this._database.findOne(_this._collectionName, {
-                username: username
-              });
 
-            case 3:
-              user = _context12.sent;
-
-              if (user) {
-                _context12.next = 6;
-                break;
-              }
-
-              throw new _HttpError2.default("User doesn't exist", 404);
-
-            case 6:
-              _context12.next = 8;
-              return _PasswordHasher2.default.hash(password, user.salt);
-
-            case 8:
-              hash = _context12.sent;
+    _this.isUserNameInUse = function () {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(username) {
+        return _regenerator2.default.wrap(function _callee9$(_context9) {
+          while (1) {
+            switch (_context9.prev = _context9.next) {
+              case 0:
+                _context9.next = 2;
+                return _this.getByUsername(username);
+
+              case 2:
+                return _context9.abrupt('return', !!_context9.sent);
+
+              case 3:
+              case 'end':
+                return _context9.stop();
+            }
+          }
+        }, _callee9, _this2);
+      }));
+
+      return function (_x10) {
+        return _ref9.apply(this, arguments);
+      };
+    }();
+
+    _this.saveAccessToken = function () {
+      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(userID, tokenObject) {
+        return _regenerator2.default.wrap(function _callee10$(_context10) {
+          while (1) {
+            switch (_context10.prev = _context10.next) {
+              case 0:
+                _context10.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $push: { accessTokens: tokenObject } });
+
+              case 2:
+                return _context10.abrupt('return', _context10.sent);
+
+              case 3:
+              case 'end':
+                return _context10.stop();
+            }
+          }
+        }, _callee10, _this2);
+      }));
 
-              if (!(hash !== user.passwordHash)) {
-                _context12.next = 11;
-                break;
-              }
+      return function (_x11, _x12) {
+        return _ref10.apply(this, arguments);
+      };
+    }();
 
-              throw new _HttpError2.default('Wrong password');
+    _this.setCurrentUser = function (user) {
+      _this._currentUser = user;
+    };
 
-            case 11:
-              return _context12.abrupt('return', user);
+    _this.updateByID = function () {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id, props) {
+        return _regenerator2.default.wrap(function _callee11$(_context11) {
+          while (1) {
+            switch (_context11.prev = _context11.next) {
+              case 0:
+                _context11.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { _id: id }, { $set: (0, _extends3.default)({}, props) });
+
+              case 2:
+                return _context11.abrupt('return', _context11.sent);
+
+              case 3:
+              case 'end':
+                return _context11.stop();
+            }
+          }
+        }, _callee11, _this2);
+      }));
+
+      return function (_x13, _x14) {
+        return _ref11.apply(this, arguments);
+      };
+    }();
+
+    _this.validateLogin = function () {
+      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username, password) {
+        var user, hash;
+        return _regenerator2.default.wrap(function _callee12$(_context12) {
+          while (1) {
+            switch (_context12.prev = _context12.next) {
+              case 0:
+                _context12.prev = 0;
+                _context12.next = 3;
+                return _this._database.findOne(_this._collectionName, {
+                  username: username
+                });
+
+              case 3:
+                user = _context12.sent;
+
+                if (user) {
+                  _context12.next = 6;
+                  break;
+                }
+
+                throw new _HttpError2.default("User doesn't exist", 404);
+
+              case 6:
+                _context12.next = 8;
+                return _PasswordHasher2.default.hash(password, user.salt);
+
+              case 8:
+                hash = _context12.sent;
+
+                if (!(hash !== user.passwordHash)) {
+                  _context12.next = 11;
+                  break;
+                }
+
+                throw new _HttpError2.default('Wrong password');
+
+              case 11:
+                return _context12.abrupt('return', user);
+
+              case 14:
+                _context12.prev = 14;
+                _context12.t0 = _context12['catch'](0);
+                throw _context12.t0;
+
+              case 17:
+              case 'end':
+                return _context12.stop();
+            }
+          }
+        }, _callee12, _this2, [[0, 14]]);
+      }));
 
-            case 14:
-              _context12.prev = 14;
-              _context12.t0 = _context12['catch'](0);
-              throw _context12.t0;
+      return function (_x15, _x16) {
+        return _ref12.apply(this, arguments);
+      };
+    }();
 
-            case 17:
-            case 'end':
-              return _context12.stop();
-          }
-        }
-      }, _callee12, _this, [[0, 14]]);
-    }));
+    _this._database = database;
+    return _this;
+  }
 
-    return function (_x15, _x16) {
-      return _ref12.apply(this, arguments);
-    };
-  }();
+  // eslint-disable-next-line no-unused-vars
 
-  this._database = database;
-}
 
-// eslint-disable-next-line no-unused-vars
+  // eslint-disable-next-line no-unused-vars
 
 
-// eslint-disable-next-line no-unused-vars
-;
+  return UserDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = UserDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index ae9eed18..f04a3181 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -85,25 +85,46 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
     (0, _classCallCheck3.default)(this, UserFileRepository);
 
+    this.count = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                return _context.abrupt('return', _this._fileManager.count());
+
+              case 1:
+              case 'end':
+                return _context.stop();
+            }
+          }
+        }, _callee, _this);
+      }));
+
+      return function () {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
     this.createWithCredentials = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
         var userRole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
         var username, password, salt, passwordHash, modelToSave;
-        return _regenerator2.default.wrap(function _callee$(_context) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
-            switch (_context.prev = _context.next) {
+            switch (_context2.prev = _context2.next) {
               case 0:
                 username = userCredentials.username, password = userCredentials.password;
-                _context.next = 3;
+                _context2.next = 3;
                 return _PasswordHasher2.default.generateSalt();
 
               case 3:
-                salt = _context.sent;
-                _context.next = 6;
+                salt = _context2.sent;
+                _context2.next = 6;
                 return _PasswordHasher2.default.hash(password, salt);
 
               case 6:
-                passwordHash = _context.sent;
+                passwordHash = _context2.sent;
                 modelToSave = {
                   accessTokens: [],
                   passwordHash: passwordHash,
@@ -111,47 +132,47 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                   salt: salt,
                   username: username
                 };
-                _context.next = 10;
+                _context2.next = 10;
                 return _this.create(modelToSave);
 
               case 10:
-                return _context.abrupt('return', _context.sent);
+                return _context2.abrupt('return', _context2.sent);
 
               case 11:
               case 'end':
-                return _context.stop();
+                return _context2.stop();
             }
           }
-        }, _callee, _this);
+        }, _callee2, _this);
       }));
 
       return function (_x) {
-        return _ref.apply(this, arguments);
+        return _ref2.apply(this, arguments);
       };
     }();
 
     this.deleteAccessToken = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userID, token) {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID, token) {
         var user;
-        return _regenerator2.default.wrap(function _callee2$(_context2) {
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
-            switch (_context2.prev = _context2.next) {
+            switch (_context3.prev = _context3.next) {
               case 0:
-                _context2.next = 2;
+                _context3.next = 2;
                 return _this.getByID(userID);
 
               case 2:
-                user = _context2.sent;
+                user = _context3.sent;
 
                 if (user) {
-                  _context2.next = 5;
+                  _context3.next = 5;
                   break;
                 }
 
                 throw new Error("User doesn't exist");
 
               case 5:
-                _context2.next = 7;
+                _context3.next = 7;
                 return _this.updateByID(userID, {
                   accessTokens: user.accessTokens.filter(function (tokenObject) {
                     return tokenObject.accessToken !== token;
@@ -159,49 +180,49 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                 });
 
               case 7:
-                return _context2.abrupt('return', _context2.sent);
+                return _context3.abrupt('return', _context3.sent);
 
               case 8:
               case 'end':
-                return _context2.stop();
+                return _context3.stop();
             }
           }
-        }, _callee2, _this);
+        }, _callee3, _this);
       }));
 
       return function (_x3, _x4) {
-        return _ref2.apply(this, arguments);
+        return _ref3.apply(this, arguments);
       };
     }();
 
     this.getByAccessToken = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(accessToken) {
-        return _regenerator2.default.wrap(function _callee3$(_context3) {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(accessToken) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
-            switch (_context3.prev = _context3.next) {
+            switch (_context4.prev = _context4.next) {
               case 0:
-                _context3.next = 2;
+                _context4.next = 2;
                 return _this.getAll();
 
               case 2:
-                _context3.t0 = function (user) {
+                _context4.t0 = function (user) {
                   return user.accessTokens.some(function (tokenObject) {
                     return tokenObject.accessToken === accessToken;
                   });
                 };
 
-                return _context3.abrupt('return', _context3.sent.find(_context3.t0));
+                return _context4.abrupt('return', _context4.sent.find(_context4.t0));
 
               case 4:
               case 'end':
-                return _context3.stop();
+                return _context4.stop();
             }
           }
-        }, _callee3, _this);
+        }, _callee4, _this);
       }));
 
       return function (_x5) {
-        return _ref3.apply(this, arguments);
+        return _ref4.apply(this, arguments);
       };
     }();
 
@@ -210,44 +231,44 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     };
 
     this.saveAccessToken = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID, tokenObject) {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) {
         var user;
-        return _regenerator2.default.wrap(function _callee4$(_context4) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
-            switch (_context4.prev = _context4.next) {
+            switch (_context5.prev = _context5.next) {
               case 0:
-                _context4.next = 2;
+                _context5.next = 2;
                 return _this.getByID(userID);
 
               case 2:
-                user = _context4.sent;
+                user = _context5.sent;
 
                 if (user) {
-                  _context4.next = 5;
+                  _context5.next = 5;
                   break;
                 }
 
                 throw new _HttpError2.default('Could not find user for user ID');
 
               case 5:
-                _context4.next = 7;
+                _context5.next = 7;
                 return _this.updateByID(userID, {
                   accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject])
                 });
 
               case 7:
-                return _context4.abrupt('return', _context4.sent);
+                return _context5.abrupt('return', _context5.sent);
 
               case 8:
               case 'end':
-                return _context4.stop();
+                return _context5.stop();
             }
           }
-        }, _callee4, _this);
+        }, _callee5, _this);
       }));
 
       return function (_x6, _x7) {
-        return _ref4.apply(this, arguments);
+        return _ref5.apply(this, arguments);
       };
     }();
 
@@ -256,58 +277,58 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     };
 
     this.validateLogin = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(username, password) {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(username, password) {
         var user, hash;
-        return _regenerator2.default.wrap(function _callee5$(_context5) {
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
-            switch (_context5.prev = _context5.next) {
+            switch (_context6.prev = _context6.next) {
               case 0:
-                _context5.prev = 0;
-                _context5.next = 3;
+                _context6.prev = 0;
+                _context6.next = 3;
                 return _this.getByUsername(username);
 
               case 3:
-                user = _context5.sent;
+                user = _context6.sent;
 
                 if (user) {
-                  _context5.next = 6;
+                  _context6.next = 6;
                   break;
                 }
 
                 throw new Error("User doesn't exist");
 
               case 6:
-                _context5.next = 8;
+                _context6.next = 8;
                 return _PasswordHasher2.default.hash(password, user.salt);
 
               case 8:
-                hash = _context5.sent;
+                hash = _context6.sent;
 
                 if (!(hash !== user.passwordHash)) {
-                  _context5.next = 11;
+                  _context6.next = 11;
                   break;
                 }
 
                 throw new Error('Wrong password');
 
               case 11:
-                return _context5.abrupt('return', user);
+                return _context6.abrupt('return', user);
 
               case 14:
-                _context5.prev = 14;
-                _context5.t0 = _context5['catch'](0);
-                throw _context5.t0;
+                _context6.prev = 14;
+                _context6.t0 = _context6['catch'](0);
+                throw _context6.t0;
 
               case 17:
               case 'end':
-                return _context5.stop();
+                return _context6.stop();
             }
           }
-        }, _callee5, _this, [[0, 14]]);
+        }, _callee6, _this, [[0, 14]]);
       }));
 
       return function (_x8, _x9) {
-        return _ref5.apply(this, arguments);
+        return _ref6.apply(this, arguments);
       };
     }();
 
@@ -317,26 +338,26 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   (0, _createClass3.default)(UserFileRepository, [{
     key: 'create',
     value: function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(user) {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(user) {
         var id, modelToSave;
-        return _regenerator2.default.wrap(function _callee6$(_context6) {
+        return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
-            switch (_context6.prev = _context6.next) {
+            switch (_context7.prev = _context7.next) {
               case 0:
                 id = (0, _uuid2.default)();
 
               case 1:
-                _context6.next = 3;
+                _context7.next = 3;
                 return this._fileManager.hasFile(id + '.json');
 
               case 3:
-                if (!_context6.sent) {
-                  _context6.next = 7;
+                if (!_context7.sent) {
+                  _context7.next = 7;
                   break;
                 }
 
                 id = (0, _uuid2.default)();
-                _context6.next = 1;
+                _context7.next = 1;
                 break;
 
               case 7:
@@ -348,18 +369,18 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
 
 
                 this._fileManager.createFile(modelToSave.id + '.json', modelToSave);
-                return _context6.abrupt('return', modelToSave);
+                return _context7.abrupt('return', modelToSave);
 
               case 10:
               case 'end':
-                return _context6.stop();
+                return _context7.stop();
             }
           }
-        }, _callee6, this);
+        }, _callee7, this);
       }));
 
       function create(_x10) {
-        return _ref6.apply(this, arguments);
+        return _ref7.apply(this, arguments);
       }
 
       return create;
@@ -367,23 +388,23 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'deleteByID',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
-        return _regenerator2.default.wrap(function _callee7$(_context7) {
+      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(id) {
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
-            switch (_context7.prev = _context7.next) {
+            switch (_context8.prev = _context8.next) {
               case 0:
                 this._fileManager.deleteFile(id + '.json');
 
               case 1:
               case 'end':
-                return _context7.stop();
+                return _context8.stop();
             }
           }
-        }, _callee7, this);
+        }, _callee8, this);
       }));
 
       function deleteByID(_x11) {
-        return _ref7.apply(this, arguments);
+        return _ref8.apply(this, arguments);
       }
 
       return deleteByID;
@@ -391,23 +412,23 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'getAll',
     value: function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() {
-        return _regenerator2.default.wrap(function _callee8$(_context8) {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9() {
+        return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
-            switch (_context8.prev = _context8.next) {
+            switch (_context9.prev = _context9.next) {
               case 0:
-                return _context8.abrupt('return', this._fileManager.getAllData());
+                return _context9.abrupt('return', this._fileManager.getAllData());
 
               case 1:
               case 'end':
-                return _context8.stop();
+                return _context9.stop();
             }
           }
-        }, _callee8, this);
+        }, _callee9, this);
       }));
 
       function getAll() {
-        return _ref8.apply(this, arguments);
+        return _ref9.apply(this, arguments);
       }
 
       return getAll;
@@ -419,23 +440,23 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'getByID',
     value: function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id) {
-        return _regenerator2.default.wrap(function _callee9$(_context9) {
+      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(id) {
+        return _regenerator2.default.wrap(function _callee10$(_context10) {
           while (1) {
-            switch (_context9.prev = _context9.next) {
+            switch (_context10.prev = _context10.next) {
               case 0:
-                return _context9.abrupt('return', this._fileManager.getFile(id + '.json'));
+                return _context10.abrupt('return', this._fileManager.getFile(id + '.json'));
 
               case 1:
               case 'end':
-                return _context9.stop();
+                return _context10.stop();
             }
           }
-        }, _callee9, this);
+        }, _callee10, this);
       }));
 
       function getByID(_x12) {
-        return _ref9.apply(this, arguments);
+        return _ref10.apply(this, arguments);
       }
 
       return getByID;
@@ -443,31 +464,31 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'getByUsername',
     value: function () {
-      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(username) {
-        return _regenerator2.default.wrap(function _callee10$(_context10) {
+      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(username) {
+        return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
-            switch (_context10.prev = _context10.next) {
+            switch (_context11.prev = _context11.next) {
               case 0:
-                _context10.next = 2;
+                _context11.next = 2;
                 return this.getAll();
 
               case 2:
-                _context10.t0 = function (user) {
+                _context11.t0 = function (user) {
                   return user.username === username;
                 };
 
-                return _context10.abrupt('return', _context10.sent.find(_context10.t0));
+                return _context11.abrupt('return', _context11.sent.find(_context11.t0));
 
               case 4:
               case 'end':
-                return _context10.stop();
+                return _context11.stop();
             }
           }
-        }, _callee10, this);
+        }, _callee11, this);
       }));
 
       function getByUsername(_x13) {
-        return _ref10.apply(this, arguments);
+        return _ref11.apply(this, arguments);
       }
 
       return getByUsername;
@@ -475,31 +496,31 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'isUserNameInUse',
     value: function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(username) {
-        return _regenerator2.default.wrap(function _callee11$(_context11) {
+      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) {
+        return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
-            switch (_context11.prev = _context11.next) {
+            switch (_context12.prev = _context12.next) {
               case 0:
-                _context11.next = 2;
+                _context12.next = 2;
                 return this.getAll();
 
               case 2:
-                _context11.t0 = function (user) {
+                _context12.t0 = function (user) {
                   return user.username === username;
                 };
 
-                return _context11.abrupt('return', _context11.sent.some(_context11.t0));
+                return _context12.abrupt('return', _context12.sent.some(_context12.t0));
 
               case 4:
               case 'end':
-                return _context11.stop();
+                return _context12.stop();
             }
           }
-        }, _callee11, this);
+        }, _callee12, this);
       }));
 
       function isUserNameInUse(_x14) {
-        return _ref11.apply(this, arguments);
+        return _ref12.apply(this, arguments);
       }
 
       return isUserNameInUse;
@@ -507,33 +528,33 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'updateByID',
     value: function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(id, props) {
+      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(id, props) {
         var user, modelToSave;
-        return _regenerator2.default.wrap(function _callee12$(_context12) {
+        return _regenerator2.default.wrap(function _callee13$(_context13) {
           while (1) {
-            switch (_context12.prev = _context12.next) {
+            switch (_context13.prev = _context13.next) {
               case 0:
-                _context12.next = 2;
+                _context13.next = 2;
                 return this.getByID(id);
 
               case 2:
-                user = _context12.sent;
+                user = _context13.sent;
                 modelToSave = (0, _extends3.default)({}, user || {}, props);
 
 
                 this._fileManager.writeFile(id + '.json', modelToSave);
-                return _context12.abrupt('return', modelToSave);
+                return _context13.abrupt('return', modelToSave);
 
               case 6:
               case 'end':
-                return _context12.stop();
+                return _context13.stop();
             }
           }
-        }, _callee12, this);
+        }, _callee13, this);
       }));
 
       function updateByID(_x15, _x16) {
-        return _ref12.apply(this, arguments);
+        return _ref13.apply(this, arguments);
       }
 
       return updateByID;
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index 9a2d112b..4daf7395 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -16,143 +16,169 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
+var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
+
+var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
+
 var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
-var _collectionNames = require('./collectionNames');
+var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
 
-var _collectionNames2 = _interopRequireDefault(_collectionNames);
+var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
 
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+var _inherits2 = require('babel-runtime/helpers/inherits');
 
-var WebhookDatabaseRepository = function WebhookDatabaseRepository(database) {
-  var _this = this;
+var _inherits3 = _interopRequireDefault(_inherits2);
 
-  (0, _classCallCheck3.default)(this, WebhookDatabaseRepository);
-  this._collectionName = _collectionNames2.default.WEBHOOKS;
+var _collectionNames = require('./collectionNames');
 
-  this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
-      return _regenerator2.default.wrap(function _callee$(_context) {
-        while (1) {
-          switch (_context.prev = _context.next) {
-            case 0:
-              _context.next = 2;
-              return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
-                created_at: new Date()
-              }));
+var _collectionNames2 = _interopRequireDefault(_collectionNames);
 
-            case 2:
-              return _context.abrupt('return', _context.sent);
+var _BaseRepository2 = require('./BaseRepository');
 
-            case 3:
-            case 'end':
-              return _context.stop();
-          }
-        }
-      }, _callee, _this);
-    }));
+var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
 
-    return function (_x) {
-      return _ref.apply(this, arguments);
-    };
-  }();
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-  this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
-        while (1) {
-          switch (_context2.prev = _context2.next) {
-            case 0:
-              _context2.next = 2;
-              return _this._database.remove(_this._collectionName, { _id: id });
+var WebhookDatabaseRepository = function (_BaseRepository) {
+  (0, _inherits3.default)(WebhookDatabaseRepository, _BaseRepository);
 
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
+  function WebhookDatabaseRepository(database) {
+    var _this2 = this;
 
-            case 3:
-            case 'end':
-              return _context2.stop();
-          }
-        }
-      }, _callee2, _this);
-    }));
+    (0, _classCallCheck3.default)(this, WebhookDatabaseRepository);
 
-    return function (_x2) {
-      return _ref2.apply(this, arguments);
-    };
-  }();
+    var _this = (0, _possibleConstructorReturn3.default)(this, (WebhookDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(WebhookDatabaseRepository)).call(this, database, _collectionNames2.default.WEBHOOKS));
 
-  this.getAll = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
-      var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-      var query;
-      return _regenerator2.default.wrap(function _callee3$(_context3) {
-        while (1) {
-          switch (_context3.prev = _context3.next) {
-            case 0:
-              query = userID ? { ownerID: userID } : {};
-              _context3.next = 3;
-              return _this._database.find(_this._collectionName, query);
+    _this._collectionName = _collectionNames2.default.WEBHOOKS;
 
-            case 3:
-              return _context3.abrupt('return', _context3.sent);
+    _this.create = function () {
+      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+        return _regenerator2.default.wrap(function _callee$(_context) {
+          while (1) {
+            switch (_context.prev = _context.next) {
+              case 0:
+                _context.next = 2;
+                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
+                  created_at: new Date()
+                }));
 
-            case 4:
-            case 'end':
-              return _context3.stop();
+              case 2:
+                return _context.abrupt('return', _context.sent);
+
+              case 3:
+              case 'end':
+                return _context.stop();
+            }
           }
-        }
-      }, _callee3, _this);
-    }));
+        }, _callee, _this2);
+      }));
+
+      return function (_x) {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    _this.deleteByID = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
+                return _this._database.remove(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context2.abrupt('return', _context2.sent);
+
+              case 3:
+              case 'end':
+                return _context2.stop();
+            }
+          }
+        }, _callee2, _this2);
+      }));
+
+      return function (_x2) {
+        return _ref2.apply(this, arguments);
+      };
+    }();
+
+    _this.getAll = function () {
+      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var query;
+        return _regenerator2.default.wrap(function _callee3$(_context3) {
+          while (1) {
+            switch (_context3.prev = _context3.next) {
+              case 0:
+                query = userID ? { ownerID: userID } : {};
+                _context3.next = 3;
+                return _this._database.find(_this._collectionName, query);
+
+              case 3:
+                return _context3.abrupt('return', _context3.sent);
+
+              case 4:
+              case 'end':
+                return _context3.stop();
+            }
+          }
+        }, _callee3, _this2);
+      }));
+
+      return function () {
+        return _ref3.apply(this, arguments);
+      };
+    }();
+
+    _this.getByID = function () {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
+          while (1) {
+            switch (_context4.prev = _context4.next) {
+              case 0:
+                _context4.next = 2;
+                return _this._database.findOne(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context4.abrupt('return', _context4.sent);
+
+              case 3:
+              case 'end':
+                return _context4.stop();
+            }
+          }
+        }, _callee4, _this2);
+      }));
 
-    return function () {
-      return _ref3.apply(this, arguments);
-    };
-  }();
+      return function (_x4) {
+        return _ref4.apply(this, arguments);
+      };
+    }();
 
-  this.getByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
-      return _regenerator2.default.wrap(function _callee4$(_context4) {
+    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+      return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
-          switch (_context4.prev = _context4.next) {
+          switch (_context5.prev = _context5.next) {
             case 0:
-              _context4.next = 2;
-              return _this._database.findOne(_this._collectionName, { _id: id });
-
-            case 2:
-              return _context4.abrupt('return', _context4.sent);
+              throw new Error('The method is not implemented');
 
-            case 3:
+            case 1:
             case 'end':
-              return _context4.stop();
+              return _context5.stop();
           }
         }
-      }, _callee4, _this);
+      }, _callee5, _this2);
     }));
 
-    return function (_x4) {
-      return _ref4.apply(this, arguments);
-    };
-  }();
-
-  this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
-    return _regenerator2.default.wrap(function _callee5$(_context5) {
-      while (1) {
-        switch (_context5.prev = _context5.next) {
-          case 0:
-            throw new Error('The method is not implemented');
-
-          case 1:
-          case 'end':
-            return _context5.stop();
-        }
-      }
-    }, _callee5, _this);
-  }));
+    _this._database = database;
+    return _this;
+  }
 
-  this._database = database;
-};
+  return WebhookDatabaseRepository;
+}(_BaseRepository3.default);
 
 exports.default = WebhookDatabaseRepository;
\ No newline at end of file
diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js
index afe2f171..6d7a6235 100644
--- a/dist/repository/WebhookFileRepository.js
+++ b/dist/repository/WebhookFileRepository.js
@@ -77,58 +77,79 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
 
     (0, _classCallCheck3.default)(this, WebhookFileRepository);
 
-    this.getAll = function () {
+    this.count = function () {
       var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
-        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-        var allData;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
+                return _context.abrupt('return', _this._fileManager.count());
+
+              case 1:
+              case 'end':
+                return _context.stop();
+            }
+          }
+        }, _callee, _this);
+      }));
+
+      return function () {
+        return _ref.apply(this, arguments);
+      };
+    }();
+
+    this.getAll = function () {
+      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
+        var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+        var allData;
+        return _regenerator2.default.wrap(function _callee2$(_context2) {
+          while (1) {
+            switch (_context2.prev = _context2.next) {
+              case 0:
+                _context2.next = 2;
                 return _this._getAll();
 
               case 2:
-                allData = _context.sent;
+                allData = _context2.sent;
 
                 if (!userID) {
-                  _context.next = 5;
+                  _context2.next = 5;
                   break;
                 }
 
-                return _context.abrupt('return', allData.filter(function (webhook) {
+                return _context2.abrupt('return', allData.filter(function (webhook) {
                   return webhook.ownerID === userID;
                 }));
 
               case 5:
-                return _context.abrupt('return', allData);
+                return _context2.abrupt('return', allData);
 
               case 6:
               case 'end':
-                return _context.stop();
+                return _context2.stop();
             }
           }
-        }, _callee, _this);
+        }, _callee2, _this);
       }));
 
       return function () {
-        return _ref.apply(this, arguments);
+        return _ref2.apply(this, arguments);
       };
     }();
 
-    this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
-      return _regenerator2.default.wrap(function _callee2$(_context2) {
+    this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
-          switch (_context2.prev = _context2.next) {
+          switch (_context3.prev = _context3.next) {
             case 0:
               throw new _HttpError2.default('Not implemented');
 
             case 1:
             case 'end':
-              return _context2.stop();
+              return _context3.stop();
           }
         }
-      }, _callee2, _this);
+      }, _callee3, _this);
     }));
 
     this._fileManager = new _sparkProtocol.JSONFileManager(path);
@@ -137,26 +158,26 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   (0, _createClass3.default)(WebhookFileRepository, [{
     key: 'create',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) {
+      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) {
         var id, modelToSave;
-        return _regenerator2.default.wrap(function _callee3$(_context3) {
+        return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
-            switch (_context3.prev = _context3.next) {
+            switch (_context4.prev = _context4.next) {
               case 0:
                 id = (0, _uuid2.default)();
 
               case 1:
-                _context3.next = 3;
+                _context4.next = 3;
                 return this._fileManager.hasFile(id + '.json');
 
               case 3:
-                if (!_context3.sent) {
-                  _context3.next = 7;
+                if (!_context4.sent) {
+                  _context4.next = 7;
                   break;
                 }
 
                 id = (0, _uuid2.default)();
-                _context3.next = 1;
+                _context4.next = 1;
                 break;
 
               case 7:
@@ -167,18 +188,18 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
 
 
                 this._fileManager.createFile(modelToSave.id + '.json', modelToSave);
-                return _context3.abrupt('return', modelToSave);
+                return _context4.abrupt('return', modelToSave);
 
               case 10:
               case 'end':
-                return _context3.stop();
+                return _context4.stop();
             }
           }
-        }, _callee3, this);
+        }, _callee4, this);
       }));
 
       function create(_x2) {
-        return _ref3.apply(this, arguments);
+        return _ref4.apply(this, arguments);
       }
 
       return create;
@@ -186,23 +207,23 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   }, {
     key: 'deleteByID',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
-        return _regenerator2.default.wrap(function _callee4$(_context4) {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+        return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
-            switch (_context4.prev = _context4.next) {
+            switch (_context5.prev = _context5.next) {
               case 0:
                 this._fileManager.deleteFile(id + '.json');
 
               case 1:
               case 'end':
-                return _context4.stop();
+                return _context5.stop();
             }
           }
-        }, _callee4, this);
+        }, _callee5, this);
       }));
 
       function deleteByID(_x3) {
-        return _ref4.apply(this, arguments);
+        return _ref5.apply(this, arguments);
       }
 
       return deleteByID;
@@ -210,23 +231,23 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   }, {
     key: 'getByID',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
-        return _regenerator2.default.wrap(function _callee5$(_context5) {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(id) {
+        return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
-            switch (_context5.prev = _context5.next) {
+            switch (_context6.prev = _context6.next) {
               case 0:
-                return _context5.abrupt('return', this._fileManager.getFile(id + '.json'));
+                return _context6.abrupt('return', this._fileManager.getFile(id + '.json'));
 
               case 1:
               case 'end':
-                return _context5.stop();
+                return _context6.stop();
             }
           }
-        }, _callee5, this);
+        }, _callee6, this);
       }));
 
       function getByID(_x4) {
-        return _ref5.apply(this, arguments);
+        return _ref6.apply(this, arguments);
       }
 
       return getByID;
@@ -237,23 +258,23 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   }, {
     key: '_getAll',
     value: function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
-        return _regenerator2.default.wrap(function _callee6$(_context6) {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
+        return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
-            switch (_context6.prev = _context6.next) {
+            switch (_context7.prev = _context7.next) {
               case 0:
-                return _context6.abrupt('return', this._fileManager.getAllData());
+                return _context7.abrupt('return', this._fileManager.getAllData());
 
               case 1:
               case 'end':
-                return _context6.stop();
+                return _context7.stop();
             }
           }
-        }, _callee6, this);
+        }, _callee7, this);
       }));
 
       function _getAll() {
-        return _ref6.apply(this, arguments);
+        return _ref7.apply(this, arguments);
       }
 
       return _getAll;
diff --git a/dist/repository/collectionNames.js b/dist/repository/collectionNames.js
index 1ac6376d..f62e4284 100644
--- a/dist/repository/collectionNames.js
+++ b/dist/repository/collectionNames.js
@@ -10,6 +10,7 @@ var COLLECTION_NAMES = {
   DEVICE_KEYS: 'deviceKeys',
   ORGANIZATIONS: 'organizations',
   PRODUCT_CONFIGS: 'productConfigs',
+  PRODUCT_DEVICES: 'productDevices',
   PRODUCT_FIRMWARE: 'productFirmware',
   PRODUCTS: 'products',
   USERS: 'users',
diff --git a/examples/Products.md b/examples/Products.md
index e365ce3b..ba4da1c9 100644
--- a/examples/Products.md
+++ b/examples/Products.md
@@ -328,3 +328,111 @@ starts up.
     -H 'cache-control: no-cache' \
     -H 'content-type: application/json'
   ```
+  ##### Product Devices List
+  Pass `per_page` and `page` to paginate. Pages start at `1` instead of zero.
+
+  Example cURL
+  ```
+  curl -X GET \
+    http://192.168.1.19:8080/v1/products/test-device-v100/devices \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json'
+  ```
+  Sample Response:
+  ```
+  {
+      "accounts": [],
+      "devices": [
+          {
+              "deviceID": "2c0040000547343337373737",
+              "ownerID": "d5C5YtUw0OO9f4mw",
+              "registrar": "d5C5YtUw0OO9f4mw",
+              "timestamp": "2017-07-24T01:24:07.083Z",
+              "timeStamp": "2017-07-24T01:24:07.083Z",
+              "particleProductId": 6,
+              "id": "H4a4pe8dZIaTG829",
+              "denied": false,
+              "development": false,
+              "quarantined": false
+          }
+      ],
+      "meta": {
+          "total_pages": 1
+      }
+  }
+  ```
+  ##### Product Device Get
+  Example cURL
+  ```
+  curl -X GET \
+    http://192.168.1.19:8080/v1/products/test-device-v100/devices/2c0040000547343337373737 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json'
+  ```
+  Sample Response:
+  ```
+  {
+      "deviceID": "2c0040000547343337373737",
+      "ownerID": "d5C5YtUw0OO9f4mw",
+      "registrar": "d5C5YtUw0OO9f4mw",
+      "timestamp": "2017-07-24T01:24:07.083Z",
+      "particleProductId": 6,
+      "id": "H4a4pe8dZIaTG829",
+      "denied": false,
+      "development": false,
+      "quarantined": false
+  }
+  ```
+  ##### Product Devices Create
+  This endpoint can be used in two ways. You can either pass `one` as the
+  `import_method` and send an `id`. If you have a CSV, you can pass `many` and
+  send the file (with a field name of `file`).  The format of the file should
+  be a single column of device IDs.
+
+  Example cURL
+  ```
+  curl -X POST \
+    http://192.168.1.19:8080/v1/products/test-device-v100/devices \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+    -d '{"id": "27002d001547343339383037", "import_method": "one"}'
+  ```
+  Sample Response:
+  ```
+  {
+      "updated": 1,
+      "nonmemberDeviceIds": [],
+      "invalidDeviceIds": []
+  }
+  ```
+  ##### Product Devices Update
+  You can update `desired_firmware_version` and `notes`
+  Example cURL
+  ```
+  curl -X PUT \
+    http://192.168.1.19:8080/v1/products/test-device-v100/devices/2c0040000547343337373737 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json' \
+    -d '{"desired_firmware_version": 2}'
+  ```
+  Sample Response:
+  ```
+  {
+      "id": "41CvO5SGrd6UUDlO",
+      "updated": "2017-07-24T02:23:05.735Z",
+      "parsedFirmware": 2
+  }
+  ```
+  ##### Product Devices Delete
+  Example cURL
+  ```
+  curl -X DELETE \
+    http://192.168.1.19:8080/v1/products/test-device-v100/devices/2c0040000547343337373737 \
+    -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
+    -H 'cache-control: no-cache' \
+    -H 'content-type: application/json'
+  ```
diff --git a/package.json b/package.json
index 70429eb6..0536ae37 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
     "bunyan-middleware": "^0.8.0",
     "chalk": "^1.1.3",
     "constitute": "^1.6.2",
+    "csv": "^1.1.1",
     "ec-key": "0.0.2",
     "express": "^4.14.0",
     "express-oauth-server": "^2.0.0-b3",
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 727cdb81..2ee93baa 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -3,8 +3,10 @@
 
 import type { File } from 'express';
 import type {
+  IDeviceAttributeRepository,
   IOrganizationRepository,
   IProductConfigRepository,
+  IProductDeviceRepository,
   IProductFirmwareRepository,
   IProductRepository,
   Product,
@@ -14,7 +16,9 @@ import type {
 import Controller from './Controller';
 import allowUpload from '../decorators/allowUpload';
 import { HalModuleParser } from 'binary-version-reader';
+import csv from 'csv';
 import httpVerb from '../decorators/httpVerb';
+import nullthrows from 'nullthrows';
 import route from '../decorators/route';
 import HttpError from '../lib/HttpError';
 
@@ -27,21 +31,27 @@ type ProductFirmwareUpload = {
 };
 
 class ProductsController extends Controller {
+  _deviceAttributeRepository: IDeviceAttributeRepository;
   _organizationRepository: IOrganizationRepository;
   _productConfigRepository: IProductConfigRepository;
+  _productDeviceRepository: IProductDeviceRepository;
   _productFirmwareRepository: IProductFirmwareRepository;
   _productRepository: IProductRepository;
 
   constructor(
+    deviceAttributeRepository: IDeviceAttributeRepository,
     organizationRepository: IOrganizationRepository,
     productRepository: IProductRepository,
     productConfigRepository: IProductConfigRepository,
+    productDeviceRepository: IProductDeviceRepository,
     productFirmwareRepository: IProductFirmwareRepository,
   ) {
     super();
 
+    this._deviceAttributeRepository = deviceAttributeRepository;
     this._organizationRepository = organizationRepository;
     this._productConfigRepository = productConfigRepository;
+    this._productDeviceRepository = productDeviceRepository;
     this._productFirmwareRepository = productFirmwareRepository;
     this._productRepository = productRepository;
   }
@@ -225,7 +235,7 @@ class ProductsController extends Controller {
     productIDOrSlug: string,
     body: ProductFirmwareUpload,
   ): Promise<*> {
-    const missingFields = ['binary', 'description', 'title'].filter(
+    const missingFields = ['binary', 'description', 'title', 'version'].filter(
       key => !body[key],
     );
     if (missingFields.length) {
@@ -266,13 +276,27 @@ class ProductsController extends Controller {
       );
     }
 
-    if (productVersion !== parseInt(body.version, 10)) {
+    const version = parseInt(body.version, 10);
+    if (productVersion !== version) {
       return this.bad(
         `Firmware had incorrect product version ${productVersion}. Expected ` +
           body.version,
       );
     }
 
+    const firmwareList = await this._productFirmwareRepository.getAllByProductID(
+      product.product_id,
+    );
+    const maxExistingFirmwareVersion = Math.max(
+      ...firmwareList.map(firmware => parseInt(firmware.version, 10)),
+    );
+
+    if (version <= maxExistingFirmwareVersion) {
+      return this.bad(
+        `version must be greater than ${maxExistingFirmwareVersion}`,
+      );
+    }
+
     const firmware = await this._productFirmwareRepository.create({
       current: body.current,
       data: body.binary.buffer,
@@ -282,7 +306,7 @@ class ProductsController extends Controller {
       product_id: product.product_id,
       size: body.binary.size,
       title: body.title,
-      version: body.version,
+      version: version,
     });
     const { data, id, ...output } = firmware;
     return this.ok(output);
@@ -355,35 +379,296 @@ class ProductsController extends Controller {
   }
 
   @httpVerb('get')
-  @route('/v1/products/:productIdOrSlug/devices')
-  async getDevices(productIdOrSlug: string): Promise<*> {
-    throw new HttpError('Not implemented');
+  @route('/v1/products/:productIDOrSlug/devices')
+  async getDevices(
+    productIDOrSlug: string,
+    query: { page: number, per_page: number },
+  ): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+
+    query.page = Math.max(1, query.page);
+    const { page, per_page = 25 } = query;
+    const totalDevices = await this._productDeviceRepository.count({
+      productID: product.id,
+    });
+    const productDevices = await this._productDeviceRepository.getAllByProductID(
+      product.id,
+      page,
+      per_page,
+    );
+
+    const deviceIDs = productDevices.map(
+      productDevice => productDevice.deviceID,
+    );
+
+    const devices = (await this._deviceAttributeRepository.getManyFromIDs(
+      deviceIDs,
+      this.user.id,
+    )).map(device => {
+      const { denied, development, quarantined } = nullthrows(
+        productDevices.find(
+          productDevice => productDevice.deviceID === device.deviceID,
+        ),
+      );
+
+      return {
+        ...device,
+        denied,
+        development,
+        quarantined,
+      };
+    });
+    console.log(totalDevices, per_page);
+    return this.ok({
+      accounts: [],
+      devices,
+      meta: { total_pages: Math.ceil(totalDevices / per_page) },
+    });
+  }
+
+  @httpVerb('get')
+  @route('/v1/products/:productIDOrSlug/devices/:deviceID')
+  async getSingleDevices(
+    productIDOrSlug: string,
+    deviceID: string,
+  ): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+
+    const deviceAttributes = await this._deviceAttributeRepository.getByID(
+      deviceID,
+    );
+
+    if (!deviceAttributes) {
+      return this.bad(`Device ${deviceID} doesn't exist.`);
+    }
+
+    const productDevice = (await this._productDeviceRepository.getManyFromDeviceIDs(
+      [deviceID],
+    ))[0];
+
+    if (!productDevice) {
+      return this.bad(`Device ${deviceID} hasn't been assigned to a product`);
+    }
+
+    const { denied, development, quarantined } = productDevice;
+
+    return this.ok({
+      ...deviceAttributes,
+      denied,
+      development,
+      quarantined,
+    });
+  }
+
+  @httpVerb('post')
+  @route('/v1/products/:productIDOrSlug/devices')
+  @allowUpload('file', 1)
+  async addDevice(
+    productIDOrSlug: string,
+    body: { file?: File, id?: string, import_method: 'many' | 'one' },
+  ): Promise<*> {
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+
+    let ids = null;
+    if (body.import_method === 'many') {
+      const file = body.file;
+      if (!file) {
+        return this.bad('No file uploaded');
+      }
+
+      const originalname = file.originalname;
+      if (!originalname.endsWith('.txt') && !originalname.endsWith('.csv')) {
+        return this.bad('File must be csv or txt file.');
+      }
+
+      const records = csv.parse(file.buffer.toString('utf8'));
+      if (!records.length) {
+        return this.bad(`File didn't have any ids`);
+      }
+
+      if (records.some(record => record.length !== 1)) {
+        return this.bad('File should only have a single column of device ids');
+      }
+
+      ids = [].concat.apply([], records);
+    } else {
+      if (!body.id) {
+        return this.bad('You must pass an id for a device');
+      }
+
+      ids = [body.id];
+    }
+
+    const deviceAttributes = await this._deviceAttributeRepository.getManyFromIDs(
+      ids,
+      this.user.id,
+    );
+    const incorrectPlatformDeviceIDs = deviceAttributes
+      .filter(
+        deviceAttribute =>
+          deviceAttribute.particleProductId !== product.platform_id,
+      )
+      .map(deviceAttribute => deviceAttribute.deviceID);
+    const deviceAttributeIDs = deviceAttributes.map(
+      deviceAttribute => deviceAttribute.deviceID,
+    );
+    const existingProductDeviceIDs = (await this._productDeviceRepository.getManyFromDeviceIDs(
+      ids,
+    )).map(productDevice => productDevice.deviceID);
+
+    const invalidDeviceIds = [
+      ...incorrectPlatformDeviceIDs,
+      ...existingProductDeviceIDs,
+    ];
+
+    const nonmemberDeviceIds = ids.filter(
+      id => !deviceAttributeIDs.includes(id),
+    );
+
+    if (invalidDeviceIds.length) {
+      return {
+        data: {
+          updated: 0,
+          nonmemberDeviceIds,
+          invalidDeviceIds,
+        },
+        status: 400,
+      };
+    }
+
+    const idsToCreate = ids.filter(
+      id =>
+        !invalidDeviceIds.includes(id) &&
+        !existingProductDeviceIDs.includes(id),
+    );
+    await Promise.all(
+      idsToCreate.map(id =>
+        this._productDeviceRepository.create({
+          denied: false,
+          development: false,
+          deviceID: id,
+          lockedFirmwareVersion: null,
+          productID: product.id,
+          quarantined: nonmemberDeviceIds.includes(id),
+        }),
+      ),
+    );
+
+    return this.ok({
+      updated: idsToCreate.length,
+      nonmemberDeviceIds,
+      invalidDeviceIds,
+    });
   }
 
   @httpVerb('put')
-  @route('/v1/products/:productIdOrSlug/devices/:deviceID')
-  async setFirmwareVersion(
-    productIdOrSlug: string,
+  @route('/v1/products/:productIDOrSlug/devices/:deviceID')
+  async updateDeviceProduct(
+    productIDOrSlug: string,
     deviceID: string,
-    body: { desired_firmware_version: number },
+    body: { desired_firmware_version?: number, notes?: string },
   ): Promise<*> {
-    /*
-    {
-      "desired_firmware_version": 1,
-      "updated_at": "2017-01-23T05:59:35.809Z",
-      "id": "some_device_id"
+    const { desired_firmware_version, notes } = body;
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
     }
-    */
-    throw new HttpError('Not implemented');
+
+    const deviceAttributes = await this._deviceAttributeRepository.getByID(
+      deviceID,
+    );
+
+    if (!deviceAttributes) {
+      return this.bad(`Device ${deviceID} doesn't exist.`);
+    }
+
+    const productDevice = (await this._productDeviceRepository.getManyFromDeviceIDs(
+      [deviceID],
+    ))[0];
+
+    let output = { id: productDevice.id, updated: new Date() };
+    if (desired_firmware_version) {
+      const deviceFirmwares = await this._productFirmwareRepository.getAllByProductID(
+        product.product_id,
+      );
+      console.log(deviceFirmwares);
+
+      const parsedFirmware = parseInt(desired_firmware_version, 10);
+      if (
+        !deviceFirmwares.find(firmware => firmware.version === parsedFirmware)
+      ) {
+        return this.bad(
+          `Firmware version ${desired_firmware_version} does not exist`,
+        );
+      }
+
+      productDevice.lockedFirmwareVersion = parsedFirmware;
+      output = { ...output, desired_firmware_version };
+    }
+
+    if (notes !== undefined) {
+      productDevice.notes = notes;
+      output = { ...output, notes };
+    }
+
+    const updatedProductDevice = await this._productDeviceRepository.updateByID(
+      productDevice.id,
+      productDevice,
+    );
+
+    return this.ok(output);
   }
 
   @httpVerb('delete')
-  @route('/v1/products/:productIdOrSlug/devices/:deviceID')
+  @route('/v1/products/:productIDOrSlug/devices/:deviceID')
   async removeDeviceFromProduct(
-    productIdOrSlug: string,
+    productIDOrSlug: string,
     deviceID: string,
   ): Promise<*> {
-    throw new HttpError('not supported in the current server version');
+    const product = await this._productRepository.getByIDOrSlug(
+      productIDOrSlug,
+    );
+    if (!product) {
+      return this.bad(`${productIDOrSlug} does not exist`);
+    }
+
+    const deviceAttributes = await this._deviceAttributeRepository.getByID(
+      deviceID,
+    );
+
+    if (!deviceAttributes) {
+      return this.bad(`Device ${deviceID} doesn't exist.`);
+    }
+
+    const productDevice = (await this._productDeviceRepository.getManyFromDeviceIDs(
+      [deviceID],
+    ))[0];
+
+    if (!productDevice) {
+      return this.bad(
+        `Device ${deviceID} was not mapped to ${productIDOrSlug}`,
+      );
+    }
+
+    await this._productDeviceRepository.deleteByID(productDevice.id);
+    return this.ok();
   }
 
   @httpVerb('get')
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index 992b00d7..df4bad2b 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -26,6 +26,7 @@ import DeviceKeyDatabaseRepository from './repository/DeviceKeyDatabaseRepositor
 import OrganizationDatabaseRepository from './repository/OrganizationDatabaseRepository';
 import ProductDatabaseRepository from './repository/ProductDatabaseRepository';
 import ProductConfigDatabaseRepository from './repository/ProductConfigDatabaseRepository';
+import ProductDeviceDatabaseRepository from './repository/ProductDeviceDatabaseRepository';
 import ProductFirmwareDatabaseRepository from './repository/ProductFirmwareDatabaseRepository';
 import UserDatabaseRepository from './repository/UserDatabaseRepository';
 import WebhookDatabaseRepository from './repository/WebhookDatabaseRepository';
@@ -85,9 +86,11 @@ export default (container: Container, newSettings: Settings) => {
   ]);
   container.bindClass('OauthClientsController', OauthClientsController, []);
   container.bindClass('ProductsController', ProductsController, [
+    'IDeviceAttributeRepository',
     'IOrganizationRepository',
     'IProductRepository',
     'IProductConfigRepository',
+    'IProductDeviceRepository',
     'IProductFirmwareRepository',
   ]);
   container.bindClass('ProvisioningController', ProvisioningController, [
@@ -141,6 +144,11 @@ export default (container: Container, newSettings: Settings) => {
     ProductConfigDatabaseRepository,
     ['IDatabase'],
   );
+  container.bindClass(
+    'IProductDeviceRepository',
+    ProductDeviceDatabaseRepository,
+    ['IDatabase'],
+  );
   container.bindClass(
     'IProductFirmwareRepository',
     ProductFirmwareDatabaseRepository,
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 0e5f5751..a9e36d56 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -45,7 +45,11 @@ class DeviceManager {
     const attributes = await this._deviceAttributeRepository.getByID(deviceID);
 
     if (!attributes) {
-      throw new HttpError('No device found', 404);
+      return await this._deviceAttributeRepository.updateByID(deviceID, {
+        deviceID,
+        ownerID: userID,
+        registrar: userID,
+      });
     }
     if (attributes.ownerID && attributes.ownerID !== userID) {
       throw new HttpError('The device belongs to someone else.');
@@ -95,13 +99,14 @@ class DeviceManager {
       },
     );
 
-    const attributes = !connectedDeviceAttributes.error &&
+    const attributes =
+      !connectedDeviceAttributes.error &&
       this._permissionManager.doesUserHaveAccess(connectedDeviceAttributes)
-      ? connectedDeviceAttributes
-      : await this._permissionManager.getEntityByID(
-          'deviceAttributes',
-          deviceID,
-        );
+        ? connectedDeviceAttributes
+        : await this._permissionManager.getEntityByID(
+            'deviceAttributes',
+            deviceID,
+          );
 
     if (!attributes) {
       throw new HttpError('No device found', 404);
@@ -271,7 +276,6 @@ class DeviceManager {
     await this._deviceAttributeRepository.updateByID(deviceID, {
       ownerID: userID,
       registrar: userID,
-      timestamp: new Date(),
     });
     return await this.getByID(deviceID);
   };
diff --git a/src/repository/BaseRepository.js b/src/repository/BaseRepository.js
new file mode 100644
index 00000000..661e6fe7
--- /dev/null
+++ b/src/repository/BaseRepository.js
@@ -0,0 +1,22 @@
+// @flow
+
+import type { CollectionName } from './collectionNames';
+import type { IBaseDatabase } from '../types';
+
+class BaseRepository {
+  _database: IBaseDatabase;
+  _collectionName: CollectionName;
+
+  constructor(database: IBaseDatabase, collectionName: CollectionName) {
+    this._database = database;
+    this._collectionName = collectionName;
+  }
+
+  count = async (...filters: Array): Promise =>
+    await this._database.count(
+      this._collectionName,
+      ...(filters.length ? filters : [{}]),
+    );
+}
+
+export default BaseRepository;
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index b9a38d03..ecb3c4f7 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -8,14 +8,17 @@ import type {
 } from '../types';
 
 import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
 
 // getByID, deleteByID and update uses model.deviceID as ID for querying
-class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
+class DeviceAttributeDatabaseRepository extends BaseRepository
+  implements IDeviceAttributeRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.DEVICE_ATTRIBUTES;
   _permissionManager: Object;
 
   constructor(database: IBaseDatabase, permissionManager: Object) {
+    super(database, COLLECTION_NAMES.DEVICE_ATTRIBUTES);
     this._database = database;
     this._permissionManager = permissionManager;
   }
@@ -39,6 +42,15 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
       await this._database.findOne(this._collectionName, { deviceID }),
     );
 
+  getManyFromIDs = async (
+    deviceIDs: Array,
+    ownerID?: string,
+  ): Promise> =>
+    (await this._database.find(this._collectionName, {
+      deviceID: { $in: deviceIDs },
+      ownerID,
+    })).map(this._parseVariables);
+
   updateByID = async (
     deviceID: string,
     { variables, ...props }: $Shape,
@@ -51,7 +63,7 @@ class DeviceAttributeDatabaseRepository implements IDeviceAttributeRepository {
     return await this._database.findAndModify(
       this._collectionName,
       { deviceID },
-      { $set: { ...attributesToSave, timeStamp: new Date() } },
+      { $set: { ...attributesToSave, timestamp: new Date() } },
     );
   };
 
diff --git a/src/repository/DeviceKeyDatabaseRepository.js b/src/repository/DeviceKeyDatabaseRepository.js
index 378f38f9..c1067a2f 100644
--- a/src/repository/DeviceKeyDatabaseRepository.js
+++ b/src/repository/DeviceKeyDatabaseRepository.js
@@ -8,13 +8,16 @@ import type {
 } from '../types';
 
 import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
 
 // getByID, deleteByID and update uses model.deviceID as ID for querying
-class DeviceKeyDatabaseRepository implements IDeviceKeyRepository {
+class DeviceKeyDatabaseRepository extends BaseRepository
+  implements IDeviceKeyRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.DEVICE_KEYS;
 
   constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.DEVICE_KEYS);
     this._database = database;
   }
 
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index df2829c2..90be8e35 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -21,13 +21,13 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
   }
 
   count = async (collectionName: string, query: Object): Promise =>
-    await this.__runForCollection(
+    (await this.__runForCollection(
       collectionName,
-      async (collection: Object): Promise<*> =>
+      async (collection: Object): Promise =>
         await collection.count(this.__translateQuery(query), {
           timeout: false,
         }),
-    );
+    )) || 0;
 
   insertOne = async (collectionName: string, entity: Object): Promise<*> =>
     await this.__runForCollection(
@@ -42,10 +42,16 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
     await this.__runForCollection(
       collectionName,
       async (collection: Object): Promise<*> => {
-        const resultItems = await collection
-          .find(this.__translateQuery(query), { timeout: false })
-          .toArray();
+        const { page, pageSize = 25, ...otherQuery } = query;
+        let result = collection.find(this.__translateQuery(otherQuery), {
+          timeout: false,
+        });
+
+        if (page) {
+          result = result.skip((page - 1) * pageSize).limit(pageSize);
+        }
 
+        const resultItems = await result;
         return resultItems.map(this.__translateResultItem);
       },
     );
diff --git a/src/repository/NeDb.js b/src/repository/NeDb.js
index f4f9033f..c28bcf21 100644
--- a/src/repository/NeDb.js
+++ b/src/repository/NeDb.js
@@ -35,7 +35,7 @@ class NeDb extends BaseMongoDb implements IBaseDatabase {
     (await this.__runForCollection(
       collectionName,
       async (collection: Object): Promise =>
-        await promisify(collection, 'find', query),
+        await promisify(collection, 'count', query),
     )) || 0;
 
   insertOne = async (collectionName: string, entity: Object): Promise<*> =>
@@ -52,7 +52,15 @@ class NeDb extends BaseMongoDb implements IBaseDatabase {
     await this.__runForCollection(
       collectionName,
       async (collection: Object): Promise<*> => {
-        const resultItems = await promisify(collection, 'find', query);
+        const { page, pageSize = 25, ...otherQuery } = query;
+        let boundFunction = collection.find(otherQuery);
+        if (page) {
+          console.log(page, pageSize);
+          boundFunction = boundFunction
+            .skip((page - 1) * pageSize)
+            .limit(pageSize);
+        }
+        const resultItems = await promisify(boundFunction, 'exec');
         return resultItems.map(this.__translateResultItem);
       },
     );
diff --git a/src/repository/OrganizationDatabaseRepository.js b/src/repository/OrganizationDatabaseRepository.js
index c81e64e6..41d9d4b1 100644
--- a/src/repository/OrganizationDatabaseRepository.js
+++ b/src/repository/OrganizationDatabaseRepository.js
@@ -8,12 +8,15 @@ import type {
 } from '../types';
 
 import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
 
-class OrganizationDatabaseRepository implements IOrganizationRepository {
+class OrganizationDatabaseRepository extends BaseRepository
+  implements IOrganizationRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.ORGANIZATIONS;
 
   constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.ORGANIZATIONS);
     this._database = database;
   }
 
diff --git a/src/repository/ProductConfigDatabaseRepository.js b/src/repository/ProductConfigDatabaseRepository.js
index 6e31aafe..15af8a22 100644
--- a/src/repository/ProductConfigDatabaseRepository.js
+++ b/src/repository/ProductConfigDatabaseRepository.js
@@ -8,12 +8,15 @@ import type {
 } from '../types';
 
 import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
 
-class ProductConfigDatabaseRepository implements IProductConfigRepository {
+class ProductConfigDatabaseRepository extends BaseRepository
+  implements IProductConfigRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.PRODUCT_CONFIGS;
 
   constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.PRODUCT_CONFIGS);
     this._database = database;
   }
 
diff --git a/src/repository/ProductDatabaseRepository.js b/src/repository/ProductDatabaseRepository.js
index 5c4a0195..cb868603 100644
--- a/src/repository/ProductDatabaseRepository.js
+++ b/src/repository/ProductDatabaseRepository.js
@@ -4,12 +4,15 @@ import type { CollectionName } from './collectionNames';
 import type { IBaseDatabase, IProductRepository, Product } from '../types';
 
 import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
 
-class ProductDatabaseRepository implements IProductRepository {
+class ProductDatabaseRepository extends BaseRepository
+  implements IProductRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.PRODUCTS;
 
   constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.PRODUCTS);
     this._database = database;
   }
 
diff --git a/src/repository/ProductDeviceDatabaseRepository.js b/src/repository/ProductDeviceDatabaseRepository.js
new file mode 100644
index 00000000..17a39541
--- /dev/null
+++ b/src/repository/ProductDeviceDatabaseRepository.js
@@ -0,0 +1,73 @@
+// @flow
+
+import type { CollectionName } from './collectionNames';
+import type {
+  IBaseDatabase,
+  IProductDeviceRepository,
+  ProductDevice,
+} from '../types';
+
+import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
+
+class ProductDeviceDatabaseRepository extends BaseRepository
+  implements IProductDeviceRepository {
+  _database: IBaseDatabase;
+  _collectionName: CollectionName = COLLECTION_NAMES.PRODUCT_DEVICES;
+
+  constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.PRODUCT_DEVICES);
+    this._database = database;
+  }
+
+  create = async (model: $Shape): Promise =>
+    await this._database.insertOne(this._collectionName, {
+      ...model,
+    });
+
+  deleteByID = async (id: string): Promise =>
+    await this._database.remove(this._collectionName, { _id: id });
+
+  getAll = async (userID: ?string = null): Promise> => {
+    // TODO - this should probably just query the organization
+    const query = userID ? { ownerID: userID } : {};
+    return await this._database.find(this._collectionName, query);
+  };
+
+  getAllByProductID = async (
+    productID: string,
+    page: number,
+    pageSize: number,
+  ): Promise> =>
+    await this._database.find(this._collectionName, {
+      page,
+      pageSize,
+      productID,
+    });
+
+  getByID = async (id: string): Promise =>
+    await this._database.findOne(this._collectionName, { _id: id });
+
+  getManyFromDeviceIDs = async (
+    deviceIDs: Array,
+  ): Promise> =>
+    await this._database.find(this._collectionName, {
+      deviceID: { $in: deviceIDs },
+    });
+
+  updateByID = async (): Promise => {
+    throw new Error('The method is not implemented');
+  };
+
+  updateByID = async (
+    productDeviceID: string,
+    productDevice: ProductDevice,
+  ): Promise =>
+    await this._database.findAndModify(
+      this._collectionName,
+      { _id: productDeviceID },
+      { $set: { ...productDevice } },
+    );
+}
+
+export default ProductDeviceDatabaseRepository;
diff --git a/src/repository/ProductFirmwareDatabaseRepository.js b/src/repository/ProductFirmwareDatabaseRepository.js
index fe66fa67..102f430b 100644
--- a/src/repository/ProductFirmwareDatabaseRepository.js
+++ b/src/repository/ProductFirmwareDatabaseRepository.js
@@ -8,12 +8,15 @@ import type {
 } from '../types';
 
 import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
 
-class ProductFirmwareDatabaseRepository implements IProductFirmwareRepository {
+class ProductFirmwareDatabaseRepository extends BaseRepository
+  implements IProductFirmwareRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.PRODUCT_FIRMWARE;
 
   constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.PRODUCT_FIRMWARE);
     this._database = database;
   }
 
diff --git a/src/repository/UserDatabaseRepository.js b/src/repository/UserDatabaseRepository.js
index 37abb303..4d17903b 100644
--- a/src/repository/UserDatabaseRepository.js
+++ b/src/repository/UserDatabaseRepository.js
@@ -10,16 +10,18 @@ import type {
   UserRole,
 } from '../types';
 
+import BaseRepository from './BaseRepository';
 import COLLECTION_NAMES from './collectionNames';
 import PasswordHasher from '../lib/PasswordHasher';
 import HttpError from '../lib/HttpError';
 
-class UserDatabaseRepository implements IUserRepository {
+class UserDatabaseRepository extends BaseRepository implements IUserRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.USERS;
   _currentUser: User;
 
   constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.USERS);
     this._database = database;
   }
 
diff --git a/src/repository/UserFileRepository.js b/src/repository/UserFileRepository.js
index 143e05f8..7dbb455c 100644
--- a/src/repository/UserFileRepository.js
+++ b/src/repository/UserFileRepository.js
@@ -21,6 +21,8 @@ class UserFileRepository implements IUserRepository {
     this._fileManager = new JSONFileManager(path);
   }
 
+  count = async (): Promise => this._fileManager.count();
+
   createWithCredentials = async (
     userCredentials: UserCredentials,
     userRole: ?UserRole = null,
diff --git a/src/repository/WebhookDatabaseRepository.js b/src/repository/WebhookDatabaseRepository.js
index f9ea7145..872c34ac 100644
--- a/src/repository/WebhookDatabaseRepository.js
+++ b/src/repository/WebhookDatabaseRepository.js
@@ -4,12 +4,15 @@ import type { CollectionName } from './collectionNames';
 import type { IBaseDatabase, IWebhookRepository, Webhook } from '../types';
 
 import COLLECTION_NAMES from './collectionNames';
+import BaseRepository from './BaseRepository';
 
-class WebhookDatabaseRepository implements IWebhookRepository {
+class WebhookDatabaseRepository extends BaseRepository
+  implements IWebhookRepository {
   _database: IBaseDatabase;
   _collectionName: CollectionName = COLLECTION_NAMES.WEBHOOKS;
 
   constructor(database: IBaseDatabase) {
+    super(database, COLLECTION_NAMES.WEBHOOKS);
     this._database = database;
   }
 
diff --git a/src/repository/WebhookFileRepository.js b/src/repository/WebhookFileRepository.js
index 0b28c4a1..084cc6c2 100644
--- a/src/repository/WebhookFileRepository.js
+++ b/src/repository/WebhookFileRepository.js
@@ -13,6 +13,8 @@ class WebhookFileRepository implements IWebhookRepository {
     this._fileManager = new JSONFileManager(path);
   }
 
+  count = async (): Promise => this._fileManager.count();
+
   @memoizeSet()
   async create(model: WebhookMutator): Promise {
     let id = uuid();
diff --git a/src/repository/collectionNames.js b/src/repository/collectionNames.js
index 0006a098..816bb5ab 100644
--- a/src/repository/collectionNames.js
+++ b/src/repository/collectionNames.js
@@ -6,6 +6,7 @@ export type CollectionName =
   | 'organizations'
   | 'products'
   | 'productConfigs'
+  | 'productDevices'
   | 'productFirmware'
   | 'users'
   | 'webhooks';
@@ -15,6 +16,7 @@ const COLLECTION_NAMES: { [key: string]: CollectionName } = {
   DEVICE_KEYS: 'deviceKeys',
   ORGANIZATIONS: 'organizations',
   PRODUCT_CONFIGS: 'productConfigs',
+  PRODUCT_DEVICES: 'productDevices',
   PRODUCT_FIRMWARE: 'productFirmware',
   PRODUCTS: 'products',
   USERS: 'users',
diff --git a/src/types.js b/src/types.js
index c7abc0ee..c6b095e9 100644
--- a/src/types.js
+++ b/src/types.js
@@ -75,7 +75,7 @@ export type DeviceAttributes = {
   lastHeard: Date,
   name: string,
   ownerID: ?string,
-  particleProductId: number,
+  particleProductId: PlatformType,
   productFirmwareVersion: number,
   registrar: string,
   timestamp: Date,
@@ -227,7 +227,19 @@ export type ProductConfig = {|
   product_id: string,
 |};
 
+export type ProductDevice = {|
+  denied: boolean,
+  development: boolean,
+  deviceID: string,
+  id: string,
+  lockedFirmwareVersion: ?number,
+  notes: string,
+  productID: string,
+  quarantined: boolean,
+|};
+
 export interface IBaseRepository {
+  count(...filters: Array): Promise,
   create(model: $Shape): Promise,
   deleteByID(id: string): Promise,
   getAll(): Promise>,
@@ -246,6 +258,16 @@ export interface IProductConfigRepository
   getByProductID(productID: string): Promise,
 }
 
+export interface IProductDeviceRepository
+  extends IBaseRepository {
+  getAllByProductID(
+    productID: string,
+    page: number,
+    perPage: number,
+  ): Promise>,
+  getManyFromDeviceIDs(deviceIDs: Array): Promise>,
+}
+
 export interface IOrganizationRepository extends IBaseRepository {
   getByUserID(userID: string): Promise>,
 }
@@ -256,7 +278,12 @@ export interface IProductFirmwareRepository
 }
 
 export interface IDeviceAttributeRepository
-  extends IBaseRepository {}
+  extends IBaseRepository {
+  getManyFromIDs(
+    deviceIDs: Array,
+    ownerID?: string,
+  ): Promise>,
+}
 
 export interface IDeviceKeyRepository
   extends IBaseRepository {}

From 0d91a1965f437162d7e2a18679efc07a9e06757e Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Tue, 25 Jul 2017 19:53:34 -0700
Subject: [PATCH 472/504] Added changes so that when a product firmware is
 updated, it sends an event to the device server to start updating all
 connected devices.

---
 dist/controllers/ProductsController.js        | 111 +++++++++++-------
 dist/managers/DeviceManager.js                |   7 ++
 .../ProductDeviceDatabaseRepository.js        |  63 +++++++---
 .../ProductFirmwareDatabaseRepository.js      |  72 ++++++++++--
 dist/repository/UserFileRepository.js         |  33 ++----
 dist/repository/WebhookFileRepository.js      |  31 ++---
 examples/Products.md                          |  21 +++-
 src/controllers/ProductsController.js         |  68 ++++++++---
 src/managers/DeviceManager.js                 |   7 ++
 .../ProductDeviceDatabaseRepository.js        |   5 +
 .../ProductFirmwareDatabaseRepository.js      |  15 +++
 src/types.js                                  |  14 ++-
 12 files changed, 317 insertions(+), 130 deletions(-)

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 696fb76a..14cdcc21 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -119,7 +119,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
 var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _route2.default)('/v1/products'), _dec3 = (0, _httpVerb2.default)('post'), _dec4 = (0, _route2.default)('/v1/products'), _dec5 = (0, _httpVerb2.default)('get'), _dec6 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec7 = (0, _httpVerb2.default)('put'), _dec8 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec9 = (0, _httpVerb2.default)('delete'), _dec10 = (0, _route2.default)('/v1/products/:productIDOrSlug'), _dec11 = (0, _httpVerb2.default)('get'), _dec12 = (0, _route2.default)('/v1/products/:productIDOrSlug/config'), _dec13 = (0, _httpVerb2.default)('get'), _dec14 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec15 = (0, _httpVerb2.default)('get'), _dec16 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec17 = (0, _httpVerb2.default)('post'), _dec18 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware'), _dec19 = (0, _allowUpload2.default)('binary', 1), _dec20 = (0, _httpVerb2.default)('put'), _dec21 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec22 = (0, _httpVerb2.default)('delete'), _dec23 = (0, _route2.default)('/v1/products/:productIDOrSlug/firmware/:version'), _dec24 = (0, _httpVerb2.default)('get'), _dec25 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices'), _dec26 = (0, _httpVerb2.default)('get'), _dec27 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices/:deviceID'), _dec28 = (0, _httpVerb2.default)('post'), _dec29 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices'), _dec30 = (0, _allowUpload2.default)('file', 1), _dec31 = (0, _httpVerb2.default)('put'), _dec32 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices/:deviceID'), _dec33 = (0, _httpVerb2.default)('delete'), _dec34 = (0, _route2.default)('/v1/products/:productIDOrSlug/devices/:deviceID'), _dec35 = (0, _httpVerb2.default)('get'), _dec36 = (0, _route2.default)('/v1/products/:productIdOrSlug/events/:eventPrefix?*'), _dec37 = (0, _httpVerb2.default)('delete'), _dec38 = (0, _route2.default)('/v1/products/:productIdOrSlug/team/:username'), (_class = function (_Controller) {
   (0, _inherits3.default)(ProductsController, _Controller);
 
-  function ProductsController(deviceAttributeRepository, organizationRepository, productRepository, productConfigRepository, productDeviceRepository, productFirmwareRepository) {
+  function ProductsController(deviceManager, deviceAttributeRepository, organizationRepository, productRepository, productConfigRepository, productDeviceRepository, productFirmwareRepository) {
     (0, _classCallCheck3.default)(this, ProductsController);
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (ProductsController.__proto__ || (0, _getPrototypeOf2.default)(ProductsController)).call(this));
@@ -654,10 +654,14 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 31:
                 firmware = _context9.sent;
+
+
+                this._deviceManager.flashProductFirmware(product.product_id, body.binary);
+
                 data = firmware.data, id = firmware.id, output = (0, _objectWithoutProperties3.default)(firmware, ['data', 'id']);
                 return _context9.abrupt('return', this.ok(output));
 
-              case 34:
+              case 35:
               case 'end':
                 return _context9.stop();
             }
@@ -863,15 +867,13 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 };
 
                 devices = _context12.sent.map(_context12.t0);
-
-                console.log(totalDevices, per_page);
                 return _context12.abrupt('return', this.ok({
                   accounts: [],
                   devices: devices,
                   meta: { total_pages: Math.ceil(totalDevices / per_page) }
                 }));
 
-              case 20:
+              case 19:
               case 'end':
                 return _context12.stop();
             }
@@ -923,10 +925,10 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 10:
                 _context13.next = 12;
-                return this._productDeviceRepository.getManyFromDeviceIDs([deviceID]);
+                return this._productDeviceRepository.getFromDeviceID(deviceID);
 
               case 12:
-                productDevice = _context13.sent[0];
+                productDevice = _context13.sent;
 
                 if (productDevice) {
                   _context13.next = 15;
@@ -1129,71 +1131,81 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'updateDeviceProduct',
     value: function () {
-      var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(productIDOrSlug, deviceID, body) {
-        var desired_firmware_version, notes, product, deviceAttributes, productDevice, output, deviceFirmwares, parsedFirmware, updatedProductDevice;
+      var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(productIDOrSlug, deviceID, _ref17) {
+        var denied = _ref17.denied,
+            desired_firmware_version = _ref17.desired_firmware_version,
+            development = _ref17.development,
+            notes = _ref17.notes,
+            quarantined = _ref17.quarantined;
+        var product, deviceAttributes, productDevice, output, deviceFirmwares, parsedFirmware, updatedProductDevice;
         return _regenerator2.default.wrap(function _callee15$(_context15) {
           while (1) {
             switch (_context15.prev = _context15.next) {
               case 0:
-                desired_firmware_version = body.desired_firmware_version, notes = body.notes;
-                _context15.next = 3;
+                _context15.next = 2;
                 return this._productRepository.getByIDOrSlug(productIDOrSlug);
 
-              case 3:
+              case 2:
                 product = _context15.sent;
 
                 if (product) {
-                  _context15.next = 6;
+                  _context15.next = 5;
                   break;
                 }
 
                 return _context15.abrupt('return', this.bad(productIDOrSlug + ' does not exist'));
 
-              case 6:
-                _context15.next = 8;
+              case 5:
+                _context15.next = 7;
                 return this._deviceAttributeRepository.getByID(deviceID);
 
-              case 8:
+              case 7:
                 deviceAttributes = _context15.sent;
 
                 if (deviceAttributes) {
-                  _context15.next = 11;
+                  _context15.next = 10;
                   break;
                 }
 
                 return _context15.abrupt('return', this.bad('Device ' + deviceID + ' doesn\'t exist.'));
 
-              case 11:
-                _context15.next = 13;
-                return this._productDeviceRepository.getManyFromDeviceIDs([deviceID]);
+              case 10:
+                _context15.next = 12;
+                return this._productDeviceRepository.getFromDeviceID(deviceID);
 
-              case 13:
-                productDevice = _context15.sent[0];
-                output = { id: productDevice.id, updated: new Date() };
+              case 12:
+                productDevice = _context15.sent;
 
-                if (!desired_firmware_version) {
+                if (productDevice) {
+                  _context15.next = 15;
+                  break;
+                }
+
+                return _context15.abrupt('return', this.bad('Device ' + deviceID + ' is not associated with a product'));
+
+              case 15:
+                output = { id: productDevice.id, updated_at: new Date() };
+
+                if (!(desired_firmware_version !== undefined)) {
                   _context15.next = 25;
                   break;
                 }
 
-                _context15.next = 18;
+                _context15.next = 19;
                 return this._productFirmwareRepository.getAllByProductID(product.product_id);
 
-              case 18:
+              case 19:
                 deviceFirmwares = _context15.sent;
+                parsedFirmware = desired_firmware_version !== null ? parseInt(desired_firmware_version, 10) : null;
 
-                console.log(deviceFirmwares);
-
-                parsedFirmware = parseInt(desired_firmware_version, 10);
-
-                if (deviceFirmwares.find(function (firmware) {
+                if (!(parsedFirmware !== null && !deviceFirmwares.find(function (firmware) {
                   return firmware.version === parsedFirmware;
-                })) {
+                }))) {
                   _context15.next = 23;
                   break;
                 }
 
-                return _context15.abrupt('return', this.bad('Firmware version ' + desired_firmware_version + ' does not exist'));
+                return _context15.abrupt('return', this.bad('Firmware version ' + parsedFirmware + ' does not exist'));
 
               case 23:
 
@@ -1207,14 +1219,29 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                   output = (0, _extends3.default)({}, output, { notes: notes });
                 }
 
-                _context15.next = 28;
+                if (development !== undefined) {
+                  productDevice.development = development;
+                  output = (0, _extends3.default)({}, output, { development: development });
+                }
+
+                if (denied !== undefined) {
+                  productDevice.denied = denied;
+                  output = (0, _extends3.default)({}, output, { denied: denied });
+                }
+
+                if (quarantined !== undefined) {
+                  productDevice.quarantined = quarantined;
+                  output = (0, _extends3.default)({}, output, { quarantined: quarantined });
+                }
+
+                _context15.next = 31;
                 return this._productDeviceRepository.updateByID(productDevice.id, productDevice);
 
-              case 28:
+              case 31:
                 updatedProductDevice = _context15.sent;
                 return _context15.abrupt('return', this.ok(output));
 
-              case 30:
+              case 33:
               case 'end':
                 return _context15.stop();
             }
@@ -1231,7 +1258,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'removeDeviceFromProduct',
     value: function () {
-      var _ref17 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16(productIDOrSlug, deviceID) {
+      var _ref18 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16(productIDOrSlug, deviceID) {
         var product, deviceAttributes, productDevice;
         return _regenerator2.default.wrap(function _callee16$(_context16) {
           while (1) {
@@ -1294,7 +1321,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
       }));
 
       function removeDeviceFromProduct(_x26, _x27) {
-        return _ref17.apply(this, arguments);
+        return _ref18.apply(this, arguments);
       }
 
       return removeDeviceFromProduct;
@@ -1302,7 +1329,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getEvents',
     value: function () {
-      var _ref18 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee17(productIdOrSlug, eventName) {
+      var _ref19 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee17(productIdOrSlug, eventName) {
         return _regenerator2.default.wrap(function _callee17$(_context17) {
           while (1) {
             switch (_context17.prev = _context17.next) {
@@ -1318,7 +1345,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
       }));
 
       function getEvents(_x28, _x29) {
-        return _ref18.apply(this, arguments);
+        return _ref19.apply(this, arguments);
       }
 
       return getEvents;
@@ -1326,7 +1353,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'removeTeamMember',
     value: function () {
-      var _ref19 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee18(productIdOrSlug, username) {
+      var _ref20 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee18(productIdOrSlug, username) {
         return _regenerator2.default.wrap(function _callee18$(_context18) {
           while (1) {
             switch (_context18.prev = _context18.next) {
@@ -1342,7 +1369,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
       }));
 
       function removeTeamMember(_x30, _x31) {
-        return _ref19.apply(this, arguments);
+        return _ref20.apply(this, arguments);
       }
 
       return removeTeamMember;
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 165d1dd7..c3c71d22 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -491,6 +491,13 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     };
   }();
 
+  this.flashProductFirmware = function (productID, file) {
+    _this._eventPublisher.publish({
+      context: { fileBuffer: file.buffer, productID: productID },
+      name: _sparkProtocol.SPARK_SERVER_EVENTS.FLASH_PRODUCT_FIRMWARE
+    });
+  };
+
   this.provision = function () {
     var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey, algorithm) {
       var eccKey, createdKey;
diff --git a/dist/repository/ProductDeviceDatabaseRepository.js b/dist/repository/ProductDeviceDatabaseRepository.js
index fb60793a..323ed485 100644
--- a/dist/repository/ProductDeviceDatabaseRepository.js
+++ b/dist/repository/ProductDeviceDatabaseRepository.js
@@ -187,15 +187,15 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.getManyFromDeviceIDs = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceIDs) {
+    _this.getFromDeviceID = function () {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
                 _context6.next = 2;
-                return _this._database.find(_this._collectionName, {
-                  deviceID: { $in: deviceIDs }
+                return _this._database.findOne(_this._collectionName, {
+                  deviceID: deviceID
                 });
 
               case 2:
@@ -214,43 +214,70 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
-      return _regenerator2.default.wrap(function _callee7$(_context7) {
+    _this.getManyFromDeviceIDs = function () {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceIDs) {
+        return _regenerator2.default.wrap(function _callee7$(_context7) {
+          while (1) {
+            switch (_context7.prev = _context7.next) {
+              case 0:
+                _context7.next = 2;
+                return _this._database.find(_this._collectionName, {
+                  deviceID: { $in: deviceIDs }
+                });
+
+              case 2:
+                return _context7.abrupt('return', _context7.sent);
+
+              case 3:
+              case 'end':
+                return _context7.stop();
+            }
+          }
+        }, _callee7, _this2);
+      }));
+
+      return function (_x9) {
+        return _ref7.apply(this, arguments);
+      };
+    }();
+
+    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() {
+      return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
-          switch (_context7.prev = _context7.next) {
+          switch (_context8.prev = _context8.next) {
             case 0:
               throw new Error('The method is not implemented');
 
             case 1:
             case 'end':
-              return _context7.stop();
+              return _context8.stop();
           }
         }
-      }, _callee7, _this2);
+      }, _callee8, _this2);
     }));
 
     _this.updateByID = function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productDeviceID, productDevice) {
-        return _regenerator2.default.wrap(function _callee8$(_context8) {
+      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productDeviceID, productDevice) {
+        return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
-            switch (_context8.prev = _context8.next) {
+            switch (_context9.prev = _context9.next) {
               case 0:
-                _context8.next = 2;
+                _context9.next = 2;
                 return _this._database.findAndModify(_this._collectionName, { _id: productDeviceID }, { $set: (0, _extends3.default)({}, productDevice) });
 
               case 2:
-                return _context8.abrupt('return', _context8.sent);
+                return _context9.abrupt('return', _context9.sent);
 
               case 3:
               case 'end':
-                return _context8.stop();
+                return _context9.stop();
             }
           }
-        }, _callee8, _this2);
+        }, _callee9, _this2);
       }));
 
-      return function (_x9, _x10) {
-        return _ref8.apply(this, arguments);
+      return function (_x10, _x11) {
+        return _ref9.apply(this, arguments);
       };
     }();
 
diff --git a/dist/repository/ProductFirmwareDatabaseRepository.js b/dist/repository/ProductFirmwareDatabaseRepository.js
index 2d47f0ce..640ac482 100644
--- a/dist/repository/ProductFirmwareDatabaseRepository.js
+++ b/dist/repository/ProductFirmwareDatabaseRepository.js
@@ -160,14 +160,17 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.getByID = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+    _this.getByVersionForProduct = function () {
+      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productID, version) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
                 _context5.next = 2;
-                return _this._database.findOne(_this._collectionName, { _id: id });
+                return _this._database.findOne(_this._collectionName, {
+                  product_id: productID,
+                  version: version
+                });
 
               case 2:
                 return _context5.abrupt('return', _context5.sent);
@@ -180,19 +183,22 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
         }, _callee5, _this2);
       }));
 
-      return function (_x5) {
+      return function (_x5, _x6) {
         return _ref5.apply(this, arguments);
       };
     }();
 
-    _this.updateByID = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productFirmwareID, productFirmware) {
+    _this.getCurrentForProduct = function () {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productID) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
                 _context6.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, { updated_at: new Date() }) });
+                return _this._database.findOne(_this._collectionName, {
+                  current: true,
+                  product_id: productID
+                });
 
               case 2:
                 return _context6.abrupt('return', _context6.sent);
@@ -205,11 +211,61 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
         }, _callee6, _this2);
       }));
 
-      return function (_x6, _x7) {
+      return function (_x7) {
         return _ref6.apply(this, arguments);
       };
     }();
 
+    _this.getByID = function () {
+      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
+        return _regenerator2.default.wrap(function _callee7$(_context7) {
+          while (1) {
+            switch (_context7.prev = _context7.next) {
+              case 0:
+                _context7.next = 2;
+                return _this._database.findOne(_this._collectionName, { _id: id });
+
+              case 2:
+                return _context7.abrupt('return', _context7.sent);
+
+              case 3:
+              case 'end':
+                return _context7.stop();
+            }
+          }
+        }, _callee7, _this2);
+      }));
+
+      return function (_x8) {
+        return _ref7.apply(this, arguments);
+      };
+    }();
+
+    _this.updateByID = function () {
+      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productFirmwareID, productFirmware) {
+        return _regenerator2.default.wrap(function _callee8$(_context8) {
+          while (1) {
+            switch (_context8.prev = _context8.next) {
+              case 0:
+                _context8.next = 2;
+                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, { updated_at: new Date() }) });
+
+              case 2:
+                return _context8.abrupt('return', _context8.sent);
+
+              case 3:
+              case 'end':
+                return _context8.stop();
+            }
+          }
+        }, _callee8, _this2);
+      }));
+
+      return function (_x9, _x10) {
+        return _ref8.apply(this, arguments);
+      };
+    }();
+
     _this._database = database;
     return _this;
   }
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index f04a3181..1f954f6f 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -84,27 +84,20 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     var _this = this;
 
     (0, _classCallCheck3.default)(this, UserFileRepository);
-
-    this.count = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
-        return _regenerator2.default.wrap(function _callee$(_context) {
-          while (1) {
-            switch (_context.prev = _context.next) {
-              case 0:
-                return _context.abrupt('return', _this._fileManager.count());
-
-              case 1:
-              case 'end':
-                return _context.stop();
-            }
+    this.count = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              return _context.abrupt('return', _this._fileManager.count());
+
+            case 1:
+            case 'end':
+              return _context.stop();
           }
-        }, _callee, _this);
-      }));
-
-      return function () {
-        return _ref.apply(this, arguments);
-      };
-    }();
+        }
+      }, _callee, _this);
+    }));
 
     this.createWithCredentials = function () {
       var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js
index 6d7a6235..24d15d46 100644
--- a/dist/repository/WebhookFileRepository.js
+++ b/dist/repository/WebhookFileRepository.js
@@ -76,27 +76,20 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
     var _this = this;
 
     (0, _classCallCheck3.default)(this, WebhookFileRepository);
+    this.count = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      return _regenerator2.default.wrap(function _callee$(_context) {
+        while (1) {
+          switch (_context.prev = _context.next) {
+            case 0:
+              return _context.abrupt('return', _this._fileManager.count());
 
-    this.count = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
-        return _regenerator2.default.wrap(function _callee$(_context) {
-          while (1) {
-            switch (_context.prev = _context.next) {
-              case 0:
-                return _context.abrupt('return', _this._fileManager.count());
-
-              case 1:
-              case 'end':
-                return _context.stop();
-            }
+            case 1:
+            case 'end':
+              return _context.stop();
           }
-        }, _callee, _this);
-      }));
-
-      return function () {
-        return _ref.apply(this, arguments);
-      };
-    }();
+        }
+      }, _callee, _this);
+    }));
 
     this.getAll = function () {
       var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
diff --git a/examples/Products.md b/examples/Products.md
index ba4da1c9..eee6c9bd 100644
--- a/examples/Products.md
+++ b/examples/Products.md
@@ -17,11 +17,25 @@ organization. This adds a small amount of security in that you will only be
 able to upload new firmware or edit products if you have the admin's access
 token.
 
+In the future we may improve this flow and allow you to manage organizations,
+but the main thing we are trying to handle here is product firmware versioning.
+Since we haven't implemented a customers endpoint, it doesn't make much sense
+to create new organizations.
+
 ## Products Initial Setup
 1. Grab the admin access token. This is logged to the console when the server
 starts up.
 2. Call the [Product Post Endpoint](#product-create) to create your first product.
-3. Call the [Firmware Post Endpoint](#firmware-create) to add a new firmware.  You must set `current` to true in order to activate the firmware for your fleet.
+3. Call the [Firmware Post Endpoint](#firmware-create) to add a new firmware.
+You must set `current` to true in order to activate the firmware for your fleet.
+4. Add some devices to your product with
+[Product Device Post Endpoint](#product-devices-create)
+
+Once you've done this, your products should automatically update when you add
+new versions of the firmware. **If the admin account is not the owner of the
+device, it will be automatically quarantined. You will need to update the
+[Product Device Put Endpoint](#product-devices-update) with
+`{"quarantined": false}` if it's owned by another user.**
 
 ## API
 
@@ -409,7 +423,8 @@ starts up.
   }
   ```
   ##### Product Devices Update
-  You can update `desired_firmware_version` and `notes`
+  You can update `desired_firmware_version`, `notes`, `quarantined`,
+  `denied`, and `development`.
   Example cURL
   ```
   curl -X PUT \
@@ -423,7 +438,7 @@ starts up.
   ```
   {
       "id": "41CvO5SGrd6UUDlO",
-      "updated": "2017-07-24T02:23:05.735Z",
+      "updated_at": "2017-07-24T02:23:05.735Z",
       "parsedFirmware": 2
   }
   ```
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 2ee93baa..5339e9b7 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -2,6 +2,7 @@
 /* eslint-disable */
 
 import type { File } from 'express';
+import type DeviceManager from '../managers/DeviceManager';
 import type {
   IDeviceAttributeRepository,
   IOrganizationRepository,
@@ -32,6 +33,7 @@ type ProductFirmwareUpload = {
 
 class ProductsController extends Controller {
   _deviceAttributeRepository: IDeviceAttributeRepository;
+  _deviceManager: DeviceManager;
   _organizationRepository: IOrganizationRepository;
   _productConfigRepository: IProductConfigRepository;
   _productDeviceRepository: IProductDeviceRepository;
@@ -39,6 +41,7 @@ class ProductsController extends Controller {
   _productRepository: IProductRepository;
 
   constructor(
+    deviceManager: DeviceManager,
     deviceAttributeRepository: IDeviceAttributeRepository,
     organizationRepository: IOrganizationRepository,
     productRepository: IProductRepository,
@@ -308,6 +311,9 @@ class ProductsController extends Controller {
       title: body.title,
       version: version,
     });
+
+    this._deviceManager.flashProductFirmware(product.product_id, body.binary);
+
     const { data, id, ...output } = firmware;
     return this.ok(output);
   }
@@ -423,7 +429,6 @@ class ProductsController extends Controller {
         quarantined,
       };
     });
-    console.log(totalDevices, per_page);
     return this.ok({
       accounts: [],
       devices,
@@ -452,9 +457,9 @@ class ProductsController extends Controller {
       return this.bad(`Device ${deviceID} doesn't exist.`);
     }
 
-    const productDevice = (await this._productDeviceRepository.getManyFromDeviceIDs(
-      [deviceID],
-    ))[0];
+    const productDevice = await this._productDeviceRepository.getFromDeviceID(
+      deviceID,
+    );
 
     if (!productDevice) {
       return this.bad(`Device ${deviceID} hasn't been assigned to a product`);
@@ -581,9 +586,20 @@ class ProductsController extends Controller {
   async updateDeviceProduct(
     productIDOrSlug: string,
     deviceID: string,
-    body: { desired_firmware_version?: number, notes?: string },
+    {
+      denied,
+      desired_firmware_version,
+      development,
+      notes,
+      quarantined,
+    }: {
+      denied?: boolean,
+      desired_firmware_version?: ?number,
+      development?: boolean,
+      notes?: string,
+      quarantined?: boolean,
+    },
   ): Promise<*> {
-    const { desired_firmware_version, notes } = body;
     const product = await this._productRepository.getByIDOrSlug(
       productIDOrSlug,
     );
@@ -599,24 +615,29 @@ class ProductsController extends Controller {
       return this.bad(`Device ${deviceID} doesn't exist.`);
     }
 
-    const productDevice = (await this._productDeviceRepository.getManyFromDeviceIDs(
-      [deviceID],
-    ))[0];
+    const productDevice = await this._productDeviceRepository.getFromDeviceID(
+      deviceID,
+    );
+
+    if (!productDevice) {
+      return this.bad(`Device ${deviceID} is not associated with a product`);
+    }
 
-    let output = { id: productDevice.id, updated: new Date() };
-    if (desired_firmware_version) {
+    let output = { id: productDevice.id, updated_at: new Date() };
+    if (desired_firmware_version !== undefined) {
       const deviceFirmwares = await this._productFirmwareRepository.getAllByProductID(
         product.product_id,
       );
-      console.log(deviceFirmwares);
 
-      const parsedFirmware = parseInt(desired_firmware_version, 10);
+      const parsedFirmware =
+        desired_firmware_version !== null
+          ? parseInt(desired_firmware_version, 10)
+          : null;
       if (
+        parsedFirmware !== null &&
         !deviceFirmwares.find(firmware => firmware.version === parsedFirmware)
       ) {
-        return this.bad(
-          `Firmware version ${desired_firmware_version} does not exist`,
-        );
+        return this.bad(`Firmware version ${parsedFirmware} does not exist`);
       }
 
       productDevice.lockedFirmwareVersion = parsedFirmware;
@@ -628,6 +649,21 @@ class ProductsController extends Controller {
       output = { ...output, notes };
     }
 
+    if (development !== undefined) {
+      productDevice.development = development;
+      output = { ...output, development };
+    }
+
+    if (denied !== undefined) {
+      productDevice.denied = denied;
+      output = { ...output, denied };
+    }
+
+    if (quarantined !== undefined) {
+      productDevice.quarantined = quarantined;
+      output = { ...output, quarantined };
+    }
+
     const updatedProductDevice = await this._productDeviceRepository.updateByID(
       productDevice.id,
       productDevice,
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index a9e36d56..c25436d1 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -240,6 +240,13 @@ class DeviceManager {
     return flashResponse;
   };
 
+  flashProductFirmware = (productID: string, file: File) => {
+    this._eventPublisher.publish({
+      context: { fileBuffer: file.buffer, productID },
+      name: SPARK_SERVER_EVENTS.FLASH_PRODUCT_FIRMWARE,
+    });
+  };
+
   provision = async (
     deviceID: string,
     userID: string,
diff --git a/src/repository/ProductDeviceDatabaseRepository.js b/src/repository/ProductDeviceDatabaseRepository.js
index 17a39541..50ecfb1f 100644
--- a/src/repository/ProductDeviceDatabaseRepository.js
+++ b/src/repository/ProductDeviceDatabaseRepository.js
@@ -48,6 +48,11 @@ class ProductDeviceDatabaseRepository extends BaseRepository
   getByID = async (id: string): Promise =>
     await this._database.findOne(this._collectionName, { _id: id });
 
+  getFromDeviceID = async (deviceID: string): Promise =>
+    await this._database.findOne(this._collectionName, {
+      deviceID,
+    });
+
   getManyFromDeviceIDs = async (
     deviceIDs: Array,
   ): Promise> =>
diff --git a/src/repository/ProductFirmwareDatabaseRepository.js b/src/repository/ProductFirmwareDatabaseRepository.js
index 102f430b..024fe81e 100644
--- a/src/repository/ProductFirmwareDatabaseRepository.js
+++ b/src/repository/ProductFirmwareDatabaseRepository.js
@@ -40,6 +40,21 @@ class ProductFirmwareDatabaseRepository extends BaseRepository
   ): Promise> =>
     await this._database.find(this._collectionName, { product_id: productID });
 
+  getByVersionForProduct = async (
+    productID: string,
+    version: number,
+  ): Promise =>
+    await this._database.findOne(this._collectionName, {
+      product_id: productID,
+      version,
+    });
+
+  getCurrentForProduct = async (productID: string): Promise =>
+    await this._database.findOne(this._collectionName, {
+      current: true,
+      product_id: productID,
+    });
+
   getByID = async (id: string): Promise =>
     await this._database.findOne(this._collectionName, { _id: id });
 
diff --git a/src/types.js b/src/types.js
index 94ec7fb4..bec307af 100644
--- a/src/types.js
+++ b/src/types.js
@@ -265,16 +265,22 @@ export interface IProductDeviceRepository
     page: number,
     perPage: number,
   ): Promise>,
+  getFromDeviceID(deviceID: string): Promise,
   getManyFromDeviceIDs(deviceIDs: Array): Promise>,
 }
 
-export interface IOrganizationRepository extends IBaseRepository {
-  getByUserID(userID: string): Promise>,
-}
-
 export interface IProductFirmwareRepository
   extends IBaseRepository {
   getAllByProductID(productID: string): Promise>,
+  getByVersionForProduct(
+    productID: string,
+    version: number,
+  ): Promise,
+  getCurrentForProduct(productID: string): Promise,
+}
+
+export interface IOrganizationRepository extends IBaseRepository {
+  getByUserID(userID: string): Promise>,
 }
 
 export interface IDeviceAttributeRepository

From 8cd5c5c9170e1b56c4741ff1f613eb0ddd3ebaef Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Tue, 1 Aug 2017 06:52:27 -0700
Subject: [PATCH 473/504] Rebuilt dist

---
 dist/repository/DeviceAttributeDatabaseRepository.js | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 801f0e52..89b07ea9 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -174,9 +174,13 @@ var DeviceAttributeDatabaseRepository = function DeviceAttributeDatabaseReposito
 
     var variables = attributesFromDB.variables;
 
-    return (0, _extends3.default)({}, attributesFromDB, {
-      variables: variables ? JSON.parse(variables) : undefined
-    });
+    try {
+      return (0, _extends3.default)({}, attributesFromDB, {
+        variables: variables ? JSON.parse(variables) : undefined
+      });
+    } catch (ignore) {
+      return attributesFromDB;
+    }
   };
 
   this._database = database;

From 8f8c21ef2962b29e33c6382ca739c6b679708738 Mon Sep 17 00:00:00 2001
From: John Kalberer 
Date: Tue, 1 Aug 2017 07:23:36 -0700
Subject: [PATCH 474/504] Fixed DB so it fetches correctly for mongo.

---
 dist/controllers/ProductsController.js |  4 +--
 dist/repository/MongoDb.js             |  2 +-
 package-lock.json                      | 41 +++++++++++++++++++++++++-
 src/controllers/ProductsController.js  |  1 -
 src/repository/MongoDb.js              |  2 +-
 5 files changed, 43 insertions(+), 7 deletions(-)

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 93376da5..14cdcc21 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -147,11 +147,9 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 2:
                 products = _context.sent;
-
-                console.log(products);
                 return _context.abrupt('return', this.ok({ products: products.map(this._formatProduct) }));
 
-              case 5:
+              case 4:
               case 'end':
                 return _context.stop();
             }
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index aa7ca2bf..fce7cd22 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -235,7 +235,7 @@ var _initialiseProps = function _initialiseProps() {
                           }
 
                           _context6.next = 5;
-                          return result;
+                          return result.toArray();
 
                         case 5:
                           resultItems = _context6.sent;
diff --git a/package-lock.json b/package-lock.json
index c005ba9c..4e3d4019 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2038,6 +2038,35 @@
         "boom": "2.10.1"
       }
     },
+    "csv": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/csv/-/csv-1.1.1.tgz",
+      "integrity": "sha1-2ZUtWbH5ZKevvN2ATWgYpzGZpHc=",
+      "requires": {
+        "csv-generate": "1.0.0",
+        "csv-parse": "1.2.1",
+        "csv-stringify": "1.0.4",
+        "stream-transform": "0.1.2"
+      }
+    },
+    "csv-generate": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-1.0.0.tgz",
+      "integrity": "sha1-vVKIaFnQySXz5R9g86vtJi+hXK8="
+    },
+    "csv-parse": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-1.2.1.tgz",
+      "integrity": "sha1-kZnCPySQ2YxNmrKgFnsGknSYyd8="
+    },
+    "csv-stringify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-1.0.4.tgz",
+      "integrity": "sha1-vBi6ua1M7zGV/SV5gLWLR5xC0+U=",
+      "requires": {
+        "lodash.get": "4.4.2"
+      }
+    },
     "currently-unhandled": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -4174,6 +4203,11 @@
       "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
       "dev": true
     },
+    "lodash.get": {
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+      "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
+    },
     "lodash.isarguments": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
@@ -6010,7 +6044,7 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#6313b7238cf181fcbbde97c1cd7f3f8d38731da7",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#442ae1c09d14ccd40162a8401e81fbdcaf7bcccc",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.1",
@@ -6144,6 +6178,11 @@
       "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=",
       "dev": true
     },
+    "stream-transform": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-0.1.2.tgz",
+      "integrity": "sha1-fY5rTgOsR4F3j4x5UXUBv7B2Kp8="
+    },
     "streamsearch": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 0dcf1da5..5339e9b7 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -63,7 +63,6 @@ class ProductsController extends Controller {
   @route('/v1/products')
   async getProducts(): Promise<*> {
     const products = await this._productRepository.getAll();
-    console.log(products);
     return this.ok({ products: products.map(this._formatProduct) });
   }
 
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index 90be8e35..8aa03b08 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -51,7 +51,7 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
           result = result.skip((page - 1) * pageSize).limit(pageSize);
         }
 
-        const resultItems = await result;
+        const resultItems = await result.toArray();
         return resultItems.map(this.__translateResultItem);
       },
     );

From b0091a9cffc5fc0b0b79f16a114191ba1ed73894 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 6 Aug 2017 21:02:33 +0200
Subject: [PATCH 475/504] fix variables loss on update device name.

---
 dist/repository/DeviceAttributeDatabaseRepository.js | 4 +---
 src/repository/DeviceAttributeDatabaseRepository.js  | 2 +-
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index c6978f53..6106a6cb 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -196,9 +196,7 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
-                attributesToSave = (0, _extends3.default)({}, props, {
-                  variables: variables ? (0, _stringify2.default)(variables) : undefined
-                });
+                attributesToSave = (0, _extends3.default)({}, props, variables ? { variables: (0, _stringify2.default)(variables) } : {});
                 _context6.next = 3;
                 return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, attributesToSave, { timestamp: new Date() }) });
 
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index ecb3c4f7..56877fa9 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -57,7 +57,7 @@ class DeviceAttributeDatabaseRepository extends BaseRepository
   ): Promise => {
     const attributesToSave = {
       ...props,
-      variables: variables ? JSON.stringify(variables) : undefined,
+      ...(variables ? { variables: JSON.stringify(variables) } : {}),
     };
 
     return await this._database.findAndModify(

From 4ce79ffe96371c8fe4716abe8bf94cc457d16da5 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 9 Aug 2017 21:05:43 +0200
Subject: [PATCH 476/504] remove console.logs; add webhookLoggerStub

---
 dist/RouteConfig.js                 |  6 ++----
 dist/repository/NeDb.js             |  1 -
 src/RouteConfig.js                  |  1 -
 src/repository/NeDb.js              |  1 -
 test/ProvisioningController.test.js |  1 -
 test/WebhookManager.test.js         | 30 +++++++++++++++--------------
 6 files changed, 18 insertions(+), 22 deletions(-)

diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js
index b2899bc3..5482cfea 100644
--- a/dist/RouteConfig.js
+++ b/dist/RouteConfig.js
@@ -205,14 +205,12 @@ exports.default = function (app, container, controllers, settings) {
                   response.status(functionResult.status).json(functionResult.data);
 
                 case 26:
-                  _context.next = 33;
+                  _context.next = 32;
                   break;
 
                 case 28:
                   _context.prev = 28;
                   _context.t1 = _context['catch'](8);
-
-                  console.log(_context.t1);
                   httpError = new _HttpError2.default(_context.t1);
 
                   response.status(httpError.status).json({
@@ -220,7 +218,7 @@ exports.default = function (app, container, controllers, settings) {
                     ok: false
                   });
 
-                case 33:
+                case 32:
                 case 'end':
                   return _context.stop();
               }
diff --git a/dist/repository/NeDb.js b/dist/repository/NeDb.js
index 8876ddfc..e2b7083d 100644
--- a/dist/repository/NeDb.js
+++ b/dist/repository/NeDb.js
@@ -201,7 +201,6 @@ var NeDb = function (_BaseMongoDb) {
                             boundFunction = collection.find(otherQuery);
 
                             if (page) {
-                              console.log(page, pageSize);
                               boundFunction = boundFunction.skip((page - 1) * pageSize).limit(pageSize);
                             }
                             _context5.next = 5;
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index b1a6b61c..14c7e9fb 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -178,7 +178,6 @@ export default (
               response.status(functionResult.status).json(functionResult.data);
             }
           } catch (error) {
-            console.log(error);
             const httpError = new HttpError(error);
             response.status(httpError.status).json({
               error: httpError.message,
diff --git a/src/repository/NeDb.js b/src/repository/NeDb.js
index c28bcf21..539960a6 100644
--- a/src/repository/NeDb.js
+++ b/src/repository/NeDb.js
@@ -55,7 +55,6 @@ class NeDb extends BaseMongoDb implements IBaseDatabase {
         const { page, pageSize = 25, ...otherQuery } = query;
         let boundFunction = collection.find(otherQuery);
         if (page) {
-          console.log(page, pageSize);
           boundFunction = boundFunction
             .skip((page - 1) * pageSize)
             .limit(pageSize);
diff --git a/test/ProvisioningController.test.js b/test/ProvisioningController.test.js
index 0513ac6e..4a191f54 100644
--- a/test/ProvisioningController.test.js
+++ b/test/ProvisioningController.test.js
@@ -65,7 +65,6 @@ test('provision and add keys for a device.', async t => {
     .post(`/v1/provisioning/${DEVICE_ID}`)
     .query({ access_token: userToken })
     .send({ publicKey: TEST_PUBLIC_KEY });
-  console.log(response.body);
 
   t.is(response.status, 200);
   t.is(response.body.id, DEVICE_ID);
diff --git a/test/WebhookManager.test.js b/test/WebhookManager.test.js
index 3263bb1f..bc52ad67 100644
--- a/test/WebhookManager.test.js
+++ b/test/WebhookManager.test.js
@@ -16,6 +16,8 @@ const WEBHOOK_BASE = {
   url: 'https://test.com/',
 };
 
+const webhookLoggerStub = { log: () => {} };
+
 const getEvent = (data?: string): Event => ({
   data,
   deviceID: TestData.getID(),
@@ -45,7 +47,7 @@ test('should run basic request', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = 'testData';
@@ -74,7 +76,7 @@ test('should run basic request without default data', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const webhook = {
@@ -103,7 +105,7 @@ test('should compile json body', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = '{"t":"123"}';
@@ -138,7 +140,7 @@ test('should compile form body', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = '{"t":"123","g": "foo bar"}';
@@ -178,7 +180,7 @@ test('should compile request auth header', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = `{"username":"123","password": "foobar"}`;
@@ -219,7 +221,7 @@ test('should compile request headers', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = `{"t":"123","g": "foobar"}`;
@@ -260,7 +262,7 @@ test('should compile request url', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = '{"t":"123","g": "foobar"}';
@@ -293,7 +295,7 @@ test('should compile request query', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = '{"t":"123","g": "foobar"}';
@@ -332,7 +334,7 @@ test('should compile requestType', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const data = `{"t":"123","requestType": "post"}`;
@@ -364,7 +366,7 @@ test('should throw an error if wrong requestType is provided', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const testRequestType = 'wrongRequestType';
@@ -387,7 +389,7 @@ test('should publish sent event', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const event = getEvent();
@@ -405,7 +407,7 @@ test('should publish default topic', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const event = getEvent();
@@ -424,7 +426,7 @@ test('should compile response topic and publish', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const event = getEvent();
@@ -447,7 +449,7 @@ test('should compile response body and publish', async t => {
   const manager = new WebhookManager(
     t.context.eventPublisher,
     null,
-    null,
+    webhookLoggerStub,
     t.context.repository,
   );
   const event = getEvent();

From c6751478ceb3c06a30a9d5a6997401e3bbf09a2a Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 10 Aug 2017 20:52:21 +0200
Subject: [PATCH 477/504] add bunyan to start scripts

---
 package.json | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 0536ae37..0aec70c4 100644
--- a/package.json
+++ b/package.json
@@ -36,10 +36,10 @@
     "migrate-files-to-nedb": "babel-node ./src/scripts/migrateFilesToDatabase nedb",
     "prebuild": "npm run build:clean",
     "prettify": "prettier --single-quote --trailing-comma all --write",
-    "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data",
-    "start:debug": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data",
-    "start:prod": "node ./dist/main.js",
-    "start:warn": "babel-node ./src/main.js --trace-warnings",
+    "start": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/dist --ignore data | bunyan",
+    "start:debug": "nodemon --exec babel-node ./src/main.js --watch src --watch ../spark-protocol/src --ignore data | bunyan",
+    "start:prod": "node ./dist/main.js | bunyan",
+    "start:warn": "babel-node ./src/main.js --trace-warnings | bunyan",
     "test": "ava --serial --no-cache",
     "test:watch": "ava --watch --serial",
     "update-firmware": "node ./node_modules/spark-protocol/dist/scripts/update-firmware-binaries",

From 5651a3018c670ef7c624cf0a7936a7513268c1bc Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 10 Aug 2017 20:55:32 +0200
Subject: [PATCH 478/504] add DeviceManager to ProductsController bindings

---
 dist/defaultBindings.js | 2 +-
 src/defaultBindings.js  | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/dist/defaultBindings.js b/dist/defaultBindings.js
index 62aa42a5..eb4b6c6d 100644
--- a/dist/defaultBindings.js
+++ b/dist/defaultBindings.js
@@ -160,7 +160,7 @@ exports.default = function (container, newSettings) {
   container.bindClass('EventsController', _EventsController2.default, ['EventManager']);
   container.bindClass('PermissionManager', _PermissionManager2.default, ['IDeviceAttributeRepository', 'IOrganizationRepository', 'IUserRepository', 'IWebhookRepository', 'OAuthServer']);
   container.bindClass('OauthClientsController', _OauthClientsController2.default, []);
-  container.bindClass('ProductsController', _ProductsController2.default, ['IDeviceAttributeRepository', 'IOrganizationRepository', 'IProductRepository', 'IProductConfigRepository', 'IProductDeviceRepository', 'IProductFirmwareRepository']);
+  container.bindClass('ProductsController', _ProductsController2.default, ['DeviceManager', 'IDeviceAttributeRepository', 'IOrganizationRepository', 'IProductRepository', 'IProductConfigRepository', 'IProductDeviceRepository', 'IProductFirmwareRepository']);
   container.bindClass('ProvisioningController', _ProvisioningController2.default, ['DeviceManager']);
   container.bindClass('UsersController', _UsersController2.default, ['IUserRepository']);
   container.bindClass('WebhooksController', _WebhooksController2.default, ['WebhookManager']);
diff --git a/src/defaultBindings.js b/src/defaultBindings.js
index df4bad2b..b044daa9 100644
--- a/src/defaultBindings.js
+++ b/src/defaultBindings.js
@@ -86,6 +86,7 @@ export default (container: Container, newSettings: Settings) => {
   ]);
   container.bindClass('OauthClientsController', OauthClientsController, []);
   container.bindClass('ProductsController', ProductsController, [
+    'DeviceManager',
     'IDeviceAttributeRepository',
     'IOrganizationRepository',
     'IProductRepository',

From 0c0c08a9fa1783e9d12582a93bfc53d0d6d08ab7 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 10 Aug 2017 20:58:17 +0200
Subject: [PATCH 479/504] provide webhookData as querystring for GET requests

---
 dist/managers/WebhookManager.js | 32 ++++++++++++++++++--------------
 src/managers/WebhookManager.js  | 32 +++++++++++++++++++-------------
 2 files changed, 37 insertions(+), 27 deletions(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index 0963f2e9..ab473b2c 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -307,7 +307,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
 
   this.runWebhook = function () {
     var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(webhook, event) {
-      var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, requestOptions, _responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks;
+      var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, isGetRequest, requestOptions, _responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks;
 
       return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
@@ -323,31 +323,32 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
               requestQuery = _this._compileJsonTemplate(webhook.query, webhookVariablesObject);
               responseTopic = _this._compileTemplate(webhook.responseTopic, webhookVariablesObject);
               requestType = _this._compileTemplate(webhook.requestType, webhookVariablesObject);
+              isGetRequest = requestType === 'GET';
               requestOptions = {
                 auth: requestAuth,
-                body: requestJson ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
-                form: !requestJson ? _this._getRequestData(requestFormData || null, event, webhook.noDefaults) || event.data : undefined,
+                body: requestJson && !isGetRequest ? _this._getRequestData(requestJson, event, webhook.noDefaults) : undefined,
+                form: !requestJson && !isGetRequest ? _this._getRequestData(requestFormData || null, event, webhook.noDefaults) || event.data : undefined,
                 headers: requestHeaders,
                 json: true,
                 method: validateRequestType((0, _nullthrows2.default)(requestType)),
-                qs: requestQuery,
+                qs: isGetRequest ? _this._getRequestData(requestQuery, event, webhook.noDefaults) : requestQuery,
                 strictSSL: webhook.rejectUnauthorized,
                 url: (0, _nullthrows2.default)(requestUrl)
               };
-              _context6.next = 13;
+              _context6.next = 14;
               return _this._callWebhook(webhook, event, requestOptions);
 
-            case 13:
+            case 14:
               _responseBody = _context6.sent;
 
               if (_responseBody) {
-                _context6.next = 16;
+                _context6.next = 17;
                 break;
               }
 
               return _context6.abrupt('return');
 
-            case 16:
+            case 17:
               isResponseBodyAnObject = _responseBody === Object(_responseBody);
               responseTemplate = webhook.responseTemplate && isResponseBodyAnObject && _hogan2.default.compile(webhook.responseTemplate).render(_responseBody);
               responseEventData = responseTemplate || (isResponseBodyAnObject ? (0, _stringify2.default)(_responseBody) : _responseBody);
@@ -365,22 +366,25 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
                 });
               });
 
-              _this._webhookLogger.log(event, webhook, requestOptions, _responseBody, responseEventData);
-              _context6.next = 27;
+              _this._webhookLogger.log(event,
+              // webhook,
+              // requestOptions,
+              _responseBody, responseEventData);
+              _context6.next = 28;
               break;
 
-            case 24:
-              _context6.prev = 24;
+            case 25:
+              _context6.prev = 25;
               _context6.t0 = _context6['catch'](0);
 
               logger.error({ err: _context6.t0 }, 'webhookError');
 
-            case 27:
+            case 28:
             case 'end':
               return _context6.stop();
           }
         }
-      }, _callee6, _this, [[0, 24]]);
+      }, _callee6, _this, [[0, 25]]);
     }));
 
     return function (_x4, _x5) {
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 8dd6844d..95ca9f43 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -225,22 +225,28 @@ class WebhookManager {
         webhookVariablesObject,
       );
 
+      const isGetRequest = requestType === 'GET';
+
       const requestOptions = {
         auth: (requestAuth: any),
-        body: requestJson
-          ? this._getRequestData(requestJson, event, webhook.noDefaults)
-          : undefined,
-        form: !requestJson
-          ? this._getRequestData(
-              requestFormData || null,
-              event,
-              webhook.noDefaults,
-            ) || event.data
-          : undefined,
+        body:
+          requestJson && !isGetRequest
+            ? this._getRequestData(requestJson, event, webhook.noDefaults)
+            : undefined,
+        form:
+          !requestJson && !isGetRequest
+            ? this._getRequestData(
+                requestFormData || null,
+                event,
+                webhook.noDefaults,
+              ) || event.data
+            : undefined,
         headers: requestHeaders,
         json: true,
         method: validateRequestType(nullthrows(requestType)),
-        qs: requestQuery,
+        qs: isGetRequest
+          ? this._getRequestData(requestQuery, event, webhook.noDefaults)
+          : requestQuery,
         strictSSL: webhook.rejectUnauthorized,
         url: nullthrows(requestUrl),
       };
@@ -286,8 +292,8 @@ class WebhookManager {
 
       this._webhookLogger.log(
         event,
-        webhook,
-        requestOptions,
+        // webhook,
+        // requestOptions,
         responseBody,
         responseEventData,
       );

From 85152e8435648caad7865d0df7d22e2c2af94672 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 10 Aug 2017 21:05:06 +0200
Subject: [PATCH 480/504] update package-lock.json to get last spark-protocol
 changes.

---
 package-lock.json | 617 ++++++++++++++++++++++------------------------
 1 file changed, 301 insertions(+), 316 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 4e3d4019..6a9e0a60 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
       "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-0.0.36.tgz",
       "integrity": "sha1-/dhlxY6OqvCtQBzQMtyGH1xXNCo=",
       "requires": {
-        "@types/node": "8.0.7"
+        "@types/node": "8.0.20"
       }
     },
     "@types/express": {
@@ -17,16 +17,16 @@
       "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz",
       "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A==",
       "requires": {
-        "@types/express-serve-static-core": "4.0.48",
+        "@types/express-serve-static-core": "4.0.49",
         "@types/serve-static": "1.7.31"
       }
     },
     "@types/express-serve-static-core": {
-      "version": "4.0.48",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz",
-      "integrity": "sha512-+W+fHO/hUI6JX36H8FlgdMHU3Dk4a/Fn08fW5qdd7MjPP/wJlzq9fkCrgaH0gES8vohVeqwefHwPa4ylVKyYIg==",
+      "version": "4.0.49",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.49.tgz",
+      "integrity": "sha512-b7mVHoURu1xaP/V6xw1sYwyv9V0EZ7euyi+sdnbnTZxEkAh4/hzPsI6Eflq+ZzHQ/Tgl7l16Jz+0oz8F46MLnA==",
       "requires": {
-        "@types/node": "8.0.7"
+        "@types/node": "8.0.20"
       }
     },
     "@types/mime": {
@@ -35,16 +35,16 @@
       "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A=="
     },
     "@types/node": {
-      "version": "8.0.7",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.7.tgz",
-      "integrity": "sha512-fuCPLPe4yY0nv6Z1rTLFCEC452jl0k7i3gF/c8hdEKpYtEpt6Sk67hTGbxx8C0wmifFGPvKYd/O8CvS6dpgxMQ=="
+      "version": "8.0.20",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.20.tgz",
+      "integrity": "sha512-MnB7YEpmLUyEWRVRhKpRs4swwqITnY8BcVFPoTuCl99SCplI/lLUiU5vcJ/OANDqwkpdIg0pDEM38K22KQT2RA=="
     },
     "@types/serve-static": {
       "version": "1.7.31",
       "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz",
       "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=",
       "requires": {
-        "@types/express-serve-static-core": "4.0.48",
+        "@types/express-serve-static-core": "4.0.49",
         "@types/mime": "1.3.1"
       }
     },
@@ -58,7 +58,7 @@
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
       "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
       "requires": {
-        "mime-types": "2.1.15",
+        "mime-types": "2.1.16",
         "negotiator": "0.6.1"
       }
     },
@@ -146,12 +146,12 @@
       "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
     },
     "anymatch": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
-      "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=",
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
+      "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
       "requires": {
-        "arrify": "1.0.1",
-        "micromatch": "2.3.11"
+        "micromatch": "2.3.11",
+        "normalize-path": "2.1.1"
       }
     },
     "app-root-path": {
@@ -233,7 +233,8 @@
     "arrify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
-      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+      "dev": true
     },
     "asn1": {
       "version": "0.2.3",
@@ -311,7 +312,7 @@
         "babel-preset-es2015": "6.24.1",
         "babel-preset-es2015-node4": "2.1.1",
         "babel-preset-stage-2": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "bluebird": "3.5.0",
         "caching-transform": "1.0.1",
         "chalk": "1.1.3",
@@ -361,7 +362,7 @@
         "repeating": "2.0.1",
         "require-precompiled": "0.1.0",
         "resolve-cwd": "1.0.0",
-        "semver": "5.3.0",
+        "semver": "5.4.1",
         "set-immediate-shim": "1.0.1",
         "source-map-support": "0.4.15",
         "stack-utils": "0.4.0",
@@ -427,7 +428,7 @@
         "babel-core": "6.25.0",
         "babel-polyfill": "6.23.0",
         "babel-register": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "chokidar": "1.7.0",
         "commander": "2.11.0",
         "convert-source-map": "1.5.0",
@@ -461,7 +462,7 @@
         "babel-helpers": "6.24.1",
         "babel-messages": "6.23.0",
         "babel-register": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0",
@@ -495,7 +496,7 @@
       "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=",
       "requires": {
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0",
         "detect-indent": "4.0.0",
         "jsesc": "1.3.0",
@@ -509,7 +510,7 @@
       "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
       "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=",
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
       }
@@ -521,7 +522,7 @@
       "dev": true,
       "requires": {
         "babel-helper-explode-assignable-expression": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -532,7 +533,7 @@
       "dev": true,
       "requires": {
         "babel-helper-hoist-variables": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
       }
@@ -544,7 +545,7 @@
       "dev": true,
       "requires": {
         "babel-helper-function-name": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0",
         "lodash": "4.17.4"
       }
@@ -555,7 +556,7 @@
       "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
       }
@@ -566,7 +567,7 @@
       "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=",
       "requires": {
         "babel-helper-bindify-decorators": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
       }
@@ -578,7 +579,7 @@
       "dev": true,
       "requires": {
         "babel-helper-get-function-arity": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
@@ -590,7 +591,7 @@
       "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -600,7 +601,7 @@
       "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -610,7 +611,7 @@
       "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -620,7 +621,7 @@
       "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0",
         "lodash": "4.17.4"
       }
@@ -632,7 +633,7 @@
       "dev": true,
       "requires": {
         "babel-helper-function-name": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
@@ -646,7 +647,7 @@
       "requires": {
         "babel-helper-optimise-call-expression": "6.24.1",
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
@@ -657,7 +658,7 @@
       "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
       "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -666,7 +667,7 @@
       "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
       "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-ava-throws-helper": {
@@ -685,7 +686,7 @@
       "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-detective": {
@@ -703,7 +704,7 @@
         "babel-generator": "6.25.0",
         "babylon": "6.17.4",
         "call-matcher": "1.0.1",
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "espower-location-detector": "1.0.0",
         "espurify": "1.7.0",
         "estraverse": "4.2.0"
@@ -794,7 +795,7 @@
       "requires": {
         "babel-helper-remap-async-to-generator": "6.24.1",
         "babel-plugin-syntax-async-generators": "6.13.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-async-to-generator": {
@@ -805,7 +806,7 @@
       "requires": {
         "babel-helper-remap-async-to-generator": "6.24.1",
         "babel-plugin-syntax-async-functions": "6.13.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-class-constructor-call": {
@@ -815,7 +816,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-class-constructor-call": "6.18.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -827,7 +828,7 @@
       "requires": {
         "babel-helper-function-name": "6.24.1",
         "babel-plugin-syntax-class-properties": "6.13.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -838,7 +839,7 @@
       "requires": {
         "babel-helper-explode-class": "6.24.1",
         "babel-plugin-syntax-decorators": "6.13.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-types": "6.25.0"
       }
@@ -850,7 +851,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-decorators": "6.13.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -861,7 +862,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-do-expressions": "6.13.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-arrow-functions": {
@@ -870,7 +871,7 @@
       "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-block-scoped-functions": {
@@ -879,7 +880,7 @@
       "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-block-scoping": {
@@ -888,7 +889,7 @@
       "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0",
@@ -906,7 +907,7 @@
         "babel-helper-optimise-call-expression": "6.24.1",
         "babel-helper-replace-supers": "6.24.1",
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
@@ -918,7 +919,7 @@
       "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -928,7 +929,7 @@
       "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-duplicate-keys": {
@@ -937,7 +938,7 @@
       "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -947,7 +948,7 @@
       "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-function-name": {
@@ -957,7 +958,7 @@
       "dev": true,
       "requires": {
         "babel-helper-function-name": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -967,7 +968,7 @@
       "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-modules-amd": {
@@ -977,7 +978,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -988,7 +989,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-transform-strict-mode": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-types": "6.25.0"
       }
@@ -1000,7 +1001,7 @@
       "dev": true,
       "requires": {
         "babel-helper-hoist-variables": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -1011,7 +1012,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-transform-es2015-modules-amd": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0"
       }
     },
@@ -1022,7 +1023,7 @@
       "dev": true,
       "requires": {
         "babel-helper-replace-supers": "6.24.1",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-parameters": {
@@ -1033,7 +1034,7 @@
       "requires": {
         "babel-helper-call-delegate": "6.24.1",
         "babel-helper-get-function-arity": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-template": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0"
@@ -1045,7 +1046,7 @@
       "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -1055,7 +1056,7 @@
       "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-sticky-regex": {
@@ -1065,7 +1066,7 @@
       "dev": true,
       "requires": {
         "babel-helper-regex": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -1075,7 +1076,7 @@
       "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-typeof-symbol": {
@@ -1084,7 +1085,7 @@
       "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-es2015-unicode-regex": {
@@ -1094,7 +1095,7 @@
       "dev": true,
       "requires": {
         "babel-helper-regex": "6.24.1",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "regexpu-core": "2.0.0"
       }
     },
@@ -1106,7 +1107,7 @@
       "requires": {
         "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1",
         "babel-plugin-syntax-exponentiation-operator": "6.13.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-export-extensions": {
@@ -1116,7 +1117,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-export-extensions": "6.13.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-flow-strip-types": {
@@ -1126,7 +1127,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-flow": "6.18.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-function-bind": {
@@ -1136,7 +1137,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-function-bind": "6.13.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-object-rest-spread": {
@@ -1146,7 +1147,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-object-rest-spread": "6.13.0",
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-regenerator": {
@@ -1164,7 +1165,7 @@
       "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0"
+        "babel-runtime": "6.25.0"
       }
     },
     "babel-plugin-transform-strict-mode": {
@@ -1173,7 +1174,7 @@
       "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0"
       }
     },
@@ -1182,8 +1183,8 @@
       "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz",
       "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=",
       "requires": {
-        "babel-runtime": "6.23.0",
-        "core-js": "2.4.1",
+        "babel-runtime": "6.25.0",
+        "core-js": "2.5.0",
         "regenerator-runtime": "0.10.5"
       }
     },
@@ -1318,8 +1319,8 @@
       "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=",
       "requires": {
         "babel-core": "6.25.0",
-        "babel-runtime": "6.23.0",
-        "core-js": "2.4.1",
+        "babel-runtime": "6.25.0",
+        "core-js": "2.5.0",
         "home-or-tmp": "2.0.0",
         "lodash": "4.17.4",
         "mkdirp": "0.5.1",
@@ -1327,11 +1328,11 @@
       }
     },
     "babel-runtime": {
-      "version": "6.23.0",
-      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
-      "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
+      "version": "6.25.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
+      "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=",
       "requires": {
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "regenerator-runtime": "0.10.5"
       }
     },
@@ -1340,7 +1341,7 @@
       "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
       "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=",
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-traverse": "6.25.0",
         "babel-types": "6.25.0",
         "babylon": "6.17.4",
@@ -1354,7 +1355,7 @@
       "requires": {
         "babel-code-frame": "6.22.0",
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0",
         "babylon": "6.17.4",
         "debug": "2.6.8",
@@ -1368,7 +1369,7 @@
       "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
       "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=",
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "esutils": "2.0.2",
         "lodash": "4.17.4",
         "to-fast-properties": "1.0.3"
@@ -1409,9 +1410,9 @@
       }
     },
     "binary-extensions": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz",
-      "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q="
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.9.0.tgz",
+      "integrity": "sha1-ZlBsFs5vTWkopbPNajPKQelB43s="
     },
     "binary-version-reader": {
       "version": "0.5.1",
@@ -1444,8 +1445,8 @@
         "bytes": "2.4.0",
         "content-type": "1.0.2",
         "debug": "2.6.7",
-        "depd": "1.1.0",
-        "http-errors": "1.6.1",
+        "depd": "1.1.1",
+        "http-errors": "1.6.2",
         "iconv-lite": "0.4.15",
         "on-finished": "2.3.0",
         "qs": "6.4.0",
@@ -1535,11 +1536,11 @@
       "dev": true
     },
     "bunyan": {
-      "version": "1.8.10",
-      "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.10.tgz",
-      "integrity": "sha1-IB/t0mxwgLYy9BYHL1OpC5pSmBw=",
+      "version": "1.8.12",
+      "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
+      "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
       "requires": {
-        "dtrace-provider": "0.8.3",
+        "dtrace-provider": "0.8.5",
         "moment": "2.18.1",
         "mv": "2.1.1",
         "safe-json-stringify": "1.0.4"
@@ -1609,7 +1610,7 @@
       "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=",
       "dev": true,
       "requires": {
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "deep-equal": "1.0.1",
         "espurify": "1.7.0",
         "estraverse": "4.2.0"
@@ -1698,7 +1699,7 @@
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
       "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
       "requires": {
-        "anymatch": "1.3.0",
+        "anymatch": "1.3.2",
         "async-each": "1.0.1",
         "glob-parent": "2.0.0",
         "inherits": "2.0.3",
@@ -1715,9 +1716,9 @@
       "dev": true
     },
     "circular-json": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz",
-      "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=",
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
+      "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
       "dev": true
     },
     "clean-yaml-object": {
@@ -1966,9 +1967,9 @@
       }
     },
     "core-js": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
-      "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4="
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.0.tgz",
+      "integrity": "sha1-VpwFCRi+ZIazg3VSAorgRmtxcIY="
     },
     "core-util-is": {
       "version": "1.0.2",
@@ -1982,7 +1983,7 @@
       "dev": true,
       "requires": {
         "graceful-fs": "4.1.11",
-        "js-yaml": "3.8.4",
+        "js-yaml": "3.9.1",
         "minimist": "1.2.0",
         "object-assign": "4.1.1",
         "os-homedir": "1.0.2",
@@ -2015,7 +2016,7 @@
       "dev": true,
       "requires": {
         "lru-cache": "4.1.1",
-        "which": "1.2.14"
+        "which": "1.3.0"
       },
       "dependencies": {
         "lru-cache": {
@@ -2081,7 +2082,7 @@
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
       "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
       "requires": {
-        "es5-ext": "0.10.23"
+        "es5-ext": "0.10.26"
       }
     },
     "dashdash": {
@@ -2199,9 +2200,9 @@
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
     },
     "depd": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
-      "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM="
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
+      "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
     },
     "destroy": {
       "version": "1.0.4",
@@ -2268,9 +2269,9 @@
       }
     },
     "dtrace-provider": {
-      "version": "0.8.3",
-      "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.3.tgz",
-      "integrity": "sha1-uhv8ZJMoXM/PxqtpzVxh10wqQ78=",
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.5.tgz",
+      "integrity": "sha1-mOu6Ihr6xG4cOf02hY2Pk2dSS5I=",
       "optional": true,
       "requires": {
         "nan": "2.6.2"
@@ -2292,12 +2293,12 @@
       }
     },
     "duplexify": {
-      "version": "3.5.0",
-      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz",
-      "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=",
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz",
+      "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==",
       "dev": true,
       "requires": {
-        "end-of-stream": "1.0.0",
+        "end-of-stream": "1.4.0",
         "inherits": "2.0.3",
         "readable-stream": "2.3.3",
         "stream-shift": "1.0.0"
@@ -2344,7 +2345,7 @@
       "dev": true,
       "requires": {
         "call-signature": "0.0.2",
-        "core-js": "2.4.1"
+        "core-js": "2.5.0"
       }
     },
     "encodeurl": {
@@ -2353,23 +2354,12 @@
       "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
     },
     "end-of-stream": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
-      "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
+      "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
       "dev": true,
       "requires": {
-        "once": "1.3.3"
-      },
-      "dependencies": {
-        "once": {
-          "version": "1.3.3",
-          "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
-          "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
-          "dev": true,
-          "requires": {
-            "wrappy": "1.0.2"
-          }
-        }
+        "once": "1.4.0"
       }
     },
     "error-ex": {
@@ -2392,9 +2382,9 @@
       }
     },
     "es5-ext": {
-      "version": "0.10.23",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.23.tgz",
-      "integrity": "sha1-dXi1G+l0IHpUh4IbVlOMIk5Oezg=",
+      "version": "0.10.26",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.26.tgz",
+      "integrity": "sha1-UbISilMbcMT2dkCTpzy+u4IYY3I=",
       "requires": {
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
@@ -2406,7 +2396,7 @@
       "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.26",
         "es6-symbol": "3.1.1"
       }
     },
@@ -2417,7 +2407,7 @@
       "dev": true,
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.26",
         "es6-iterator": "2.0.1",
         "es6-set": "0.1.5",
         "es6-symbol": "3.1.1",
@@ -2436,7 +2426,7 @@
       "dev": true,
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.26",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1",
         "event-emitter": "0.3.5"
@@ -2448,7 +2438,7 @@
       "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23"
+        "es5-ext": "0.10.26"
       }
     },
     "es6-weak-map": {
@@ -2457,7 +2447,7 @@
       "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.26",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
       }
@@ -2496,7 +2486,7 @@
         "debug": "2.6.8",
         "doctrine": "2.0.0",
         "escope": "3.6.0",
-        "espree": "3.4.3",
+        "espree": "3.5.0",
         "esquery": "1.0.0",
         "estraverse": "4.2.0",
         "esutils": "2.0.2",
@@ -2508,7 +2498,7 @@
         "inquirer": "0.12.0",
         "is-my-json-valid": "2.16.0",
         "is-resolvable": "1.0.0",
-        "js-yaml": "3.8.4",
+        "js-yaml": "3.9.1",
         "json-stable-stringify": "1.0.1",
         "levn": "0.3.0",
         "lodash": "4.17.4",
@@ -2574,7 +2564,7 @@
       "dev": true,
       "requires": {
         "debug": "2.6.8",
-        "resolve": "1.3.3"
+        "resolve": "1.4.0"
       }
     },
     "eslint-module-utils": {
@@ -2588,18 +2578,18 @@
       }
     },
     "eslint-plugin-flowtype": {
-      "version": "2.34.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.34.1.tgz",
-      "integrity": "sha512-xwXpTW7Xv+wfuQdfPILmFl9HWBdWbDjE1aZWWQ4EgCpQtMzymEkDQfyD1ME0VA8C0HTXV7cufypQRvLi+Hk/og==",
+      "version": "2.35.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.35.0.tgz",
+      "integrity": "sha512-zjXGjOsHds8b84C0Ad3VViKh+sUA9PeXKHwPRlSLwwSX0v1iUJf/6IX7wxc+w2T2tnDH8PT6B/YgtcEuNI3ssA==",
       "dev": true,
       "requires": {
         "lodash": "4.17.4"
       }
     },
     "eslint-plugin-import": {
-      "version": "2.6.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.6.1.tgz",
-      "integrity": "sha512-aAMb32eHCQaQmgdb1MOG1hfu/rPiNgGur2IF71VJeDfTXdLpPiKALKWlzxMdcxQOZZ2CmYVKabAxCvjACxH1uQ==",
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz",
+      "integrity": "sha512-HGYmpU9f/zJaQiKNQOVfHUh2oLWW3STBrCgH0sHTX1xtsxYlH1zjLh8FlQGEIdZSdTbUMaV36WaZ6ImXkenGxQ==",
       "dev": true,
       "requires": {
         "builtin-modules": "1.1.1",
@@ -2707,19 +2697,19 @@
       }
     },
     "espree": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
-      "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz",
+      "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=",
       "dev": true,
       "requires": {
-        "acorn": "5.1.0",
+        "acorn": "5.1.1",
         "acorn-jsx": "3.0.1"
       },
       "dependencies": {
         "acorn": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.0.tgz",
-          "integrity": "sha512-WXZ0VTJT8EE25BmZjc+wr0qIwG7QaEna9csPKHS6WQp8gDo4V376wUWi222LXRiuAF6CAS4Ejv736DdRwuPK9g==",
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz",
+          "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==",
           "dev": true
         }
       }
@@ -2735,7 +2725,7 @@
       "integrity": "sha1-HFz2y8zDLm9jk4C9T5kfq5up0iY=",
       "dev": true,
       "requires": {
-        "core-js": "2.4.1"
+        "core-js": "2.5.0"
       }
     },
     "esquery": {
@@ -2779,7 +2769,7 @@
       "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23"
+        "es5-ext": "0.10.26"
       }
     },
     "event-stream": {
@@ -2798,9 +2788,9 @@
       }
     },
     "execa": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
-      "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
+      "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=",
       "dev": true,
       "requires": {
         "cross-spawn": "5.1.0",
@@ -2820,7 +2810,7 @@
           "requires": {
             "lru-cache": "4.1.1",
             "shebang-command": "1.2.0",
-            "which": "1.2.14"
+            "which": "1.3.0"
           }
         },
         "lru-cache": {
@@ -2858,9 +2848,9 @@
       }
     },
     "express": {
-      "version": "4.15.3",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz",
-      "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=",
+      "version": "4.15.4",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz",
+      "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=",
       "requires": {
         "accepts": "1.3.3",
         "array-flatten": "1.1.1",
@@ -2868,23 +2858,23 @@
         "content-type": "1.0.2",
         "cookie": "0.3.1",
         "cookie-signature": "1.0.6",
-        "debug": "2.6.7",
-        "depd": "1.1.0",
+        "debug": "2.6.8",
+        "depd": "1.1.1",
         "encodeurl": "1.0.1",
         "escape-html": "1.0.3",
         "etag": "1.8.0",
-        "finalhandler": "1.0.3",
+        "finalhandler": "1.0.4",
         "fresh": "0.5.0",
         "merge-descriptors": "1.0.1",
         "methods": "1.1.2",
         "on-finished": "2.3.0",
         "parseurl": "1.3.1",
         "path-to-regexp": "0.1.7",
-        "proxy-addr": "1.1.4",
-        "qs": "6.4.0",
+        "proxy-addr": "1.1.5",
+        "qs": "6.5.0",
         "range-parser": "1.2.0",
-        "send": "0.15.3",
-        "serve-static": "1.12.3",
+        "send": "0.15.4",
+        "serve-static": "1.12.4",
         "setprototypeof": "1.0.3",
         "statuses": "1.3.1",
         "type-is": "1.6.15",
@@ -2897,24 +2887,21 @@
           "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
           "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
         },
-        "debug": {
-          "version": "2.6.7",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
-          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
-          "requires": {
-            "ms": "2.0.0"
-          }
+        "qs": {
+          "version": "6.5.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz",
+          "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg=="
         }
       }
     },
     "express-oauth-server": {
-      "version": "2.0.0-b3",
-      "resolved": "https://registry.npmjs.org/express-oauth-server/-/express-oauth-server-2.0.0-b3.tgz",
-      "integrity": "sha1-+Mgw2/kSkc6pJ+YdYD7C7Rr+fAw=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/express-oauth-server/-/express-oauth-server-2.0.0.tgz",
+      "integrity": "sha1-V7CGZcEgFTL1LEwC8ZcJI4uZpI0=",
       "requires": {
         "bluebird": "3.5.0",
-        "express": "4.15.3",
-        "oauth2-server": "3.0.0-b4"
+        "express": "4.15.4",
+        "oauth2-server": "3.0.0"
       }
     },
     "extend": {
@@ -2931,9 +2918,9 @@
       }
     },
     "extsprintf": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
-      "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
     },
     "falafel": {
       "version": "1.2.0",
@@ -3003,27 +2990,17 @@
       "dev": true
     },
     "finalhandler": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz",
-      "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=",
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.4.tgz",
+      "integrity": "sha512-16l/r8RgzlXKmFOhZpHBztvye+lAhC5SU7hXavnerC9UfZqZxxXl3BzL8MhffPT3kF61lj9Oav2LKEzh0ei7tg==",
       "requires": {
-        "debug": "2.6.7",
+        "debug": "2.6.8",
         "encodeurl": "1.0.1",
         "escape-html": "1.0.3",
         "on-finished": "2.3.0",
         "parseurl": "1.3.1",
         "statuses": "1.3.1",
         "unpipe": "1.0.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "2.6.7",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
-          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        }
       }
     },
     "find-cache-dir": {
@@ -3053,7 +3030,7 @@
       "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=",
       "dev": true,
       "requires": {
-        "circular-json": "0.3.1",
+        "circular-json": "0.3.3",
         "del": "2.2.2",
         "graceful-fs": "4.1.11",
         "write": "0.2.1"
@@ -3110,7 +3087,7 @@
       "requires": {
         "asynckit": "0.4.0",
         "combined-stream": "1.0.5",
-        "mime-types": "2.1.15"
+        "mime-types": "2.1.16"
       }
     },
     "formatio": {
@@ -3394,11 +3371,11 @@
       "dev": true
     },
     "http-errors": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz",
-      "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=",
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
+      "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
       "requires": {
-        "depd": "1.1.0",
+        "depd": "1.1.1",
         "inherits": "2.0.3",
         "setprototypeof": "1.0.3",
         "statuses": "1.3.1"
@@ -3410,7 +3387,7 @@
       "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
       "requires": {
         "assert-plus": "0.2.0",
-        "jsprim": "1.4.0",
+        "jsprim": "1.4.1",
         "sshpk": "1.13.1"
       }
     },
@@ -3532,9 +3509,9 @@
       }
     },
     "ipaddr.js": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz",
-      "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew="
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz",
+      "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA="
     },
     "irregular-plurals": {
       "version": "1.3.0",
@@ -3558,7 +3535,7 @@
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
       "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
       "requires": {
-        "binary-extensions": "1.8.0"
+        "binary-extensions": "1.9.0"
       }
     },
     "is-buffer": {
@@ -3815,19 +3792,19 @@
       "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
     },
     "js-yaml": {
-      "version": "3.8.4",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
-      "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
+      "version": "3.9.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz",
+      "integrity": "sha512-CbcG379L1e+mWBnLvHWWeLs8GyV/EMw862uLI3c+GxVyDHWZcjZinwuBd3iW2pgxgIlksW/1vNJa4to+RvDOww==",
       "dev": true,
       "requires": {
         "argparse": "1.0.9",
-        "esprima": "3.1.3"
+        "esprima": "4.0.0"
       },
       "dependencies": {
         "esprima": {
-          "version": "3.1.3",
-          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
-          "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+          "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
           "dev": true
         }
       }
@@ -3878,14 +3855,14 @@
       "dev": true
     },
     "jsprim": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
-      "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
       "requires": {
         "assert-plus": "1.0.0",
-        "extsprintf": "1.0.2",
+        "extsprintf": "1.3.0",
         "json-schema": "0.2.3",
-        "verror": "1.3.6"
+        "verror": "1.10.0"
       },
       "dependencies": {
         "assert-plus": {
@@ -3981,14 +3958,14 @@
       }
     },
     "lint-staged": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.0.0.tgz",
-      "integrity": "sha1-wVZp9ZhhSm5oCQMD4XWnmdSODYU=",
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.0.3.tgz",
+      "integrity": "sha1-HOVVkbwsg6eBqQtpoKDIqg/GNws=",
       "dev": true,
       "requires": {
         "app-root-path": "2.0.1",
         "cosmiconfig": "1.1.0",
-        "execa": "0.7.0",
+        "execa": "0.8.0",
         "listr": "0.12.0",
         "lodash.chunk": "4.2.0",
         "minimatch": "3.0.4",
@@ -4037,16 +4014,16 @@
         "cli-truncate": "0.2.1",
         "elegant-spinner": "1.0.1",
         "figures": "1.7.0",
-        "indent-string": "3.1.0",
+        "indent-string": "3.2.0",
         "log-symbols": "1.0.2",
         "log-update": "1.0.2",
         "strip-ansi": "3.0.1"
       },
       "dependencies": {
         "indent-string": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.1.0.tgz",
-          "integrity": "sha1-CP9DNGAziDmbMp5rlTjcejz13n0=",
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+          "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
           "dev": true
         }
       }
@@ -4302,7 +4279,7 @@
       "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
       "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
       "requires": {
-        "es5-ext": "0.10.23"
+        "es5-ext": "0.10.26"
       }
     },
     "map-obj": {
@@ -4358,7 +4335,7 @@
       "integrity": "sha1-G8PqHkvgVt1HXVIZede+PV5bIcg=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.26",
         "es6-weak-map": "2.0.2",
         "event-emitter": "0.3.5",
         "is-promise": "2.1.0",
@@ -4429,16 +4406,16 @@
       "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM="
     },
     "mime-db": {
-      "version": "1.27.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
-      "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
+      "version": "1.29.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz",
+      "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg="
     },
     "mime-types": {
-      "version": "2.1.15",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
-      "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz",
+      "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=",
       "requires": {
-        "mime-db": "1.27.0"
+        "mime-db": "1.29.0"
       }
     },
     "minimalistic-assert": {
@@ -4473,12 +4450,12 @@
       "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
     },
     "mongodb": {
-      "version": "2.2.29",
-      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.29.tgz",
-      "integrity": "sha512-MrQvIsN6zN80I4hdFo8w46w51cIqD2FJBGsUfApX9GmjXA1aCclEAJbOHaQWjCtabeWq57S3ECzqEKg/9bdBhA==",
+      "version": "2.2.31",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.31.tgz",
+      "integrity": "sha1-GUBEXGYeGSF7s7+CRdmFSq71SNs=",
       "requires": {
         "es6-promise": "3.2.1",
-        "mongodb-core": "2.1.13",
+        "mongodb-core": "2.1.15",
         "readable-stream": "2.2.7"
       },
       "dependencies": {
@@ -4499,9 +4476,9 @@
       }
     },
     "mongodb-core": {
-      "version": "2.1.13",
-      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.13.tgz",
-      "integrity": "sha512-mbcvqLLZwVcpTrsfBDY3hRNk2SDNJWOvKKxFJSc0pnUBhYojymBc/L0THfQsWwKJrkb2nIXSjfFll1mG/I5OqQ==",
+      "version": "2.1.15",
+      "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.15.tgz",
+      "integrity": "sha1-hB9TuH//9MdFgYnDXIroJ+EWl2Q=",
       "requires": {
         "bson": "1.0.4",
         "require_optional": "1.0.1"
@@ -4519,7 +4496,7 @@
       "requires": {
         "basic-auth": "1.1.0",
         "debug": "2.6.8",
-        "depd": "1.1.0",
+        "depd": "1.1.1",
         "on-finished": "2.3.0",
         "on-headers": "1.0.1"
       }
@@ -4722,7 +4699,7 @@
           "integrity": "sha1-5dDtSvVfw+701WAHdp2YGSvLLso=",
           "dev": true,
           "requires": {
-            "duplexify": "3.5.0",
+            "duplexify": "3.5.1",
             "infinity-agent": "2.0.3",
             "is-redirect": "1.0.0",
             "is-stream": "1.1.0",
@@ -4815,7 +4792,7 @@
       "requires": {
         "hosted-git-info": "2.5.0",
         "is-builtin-module": "1.0.0",
-        "semver": "5.3.0",
+        "semver": "5.4.1",
         "validate-npm-package-license": "3.0.1"
       }
     },
@@ -4833,7 +4810,7 @@
       "integrity": "sha1-Fc/04ciaONp39W9gVbJPl137K74=",
       "dev": true,
       "requires": {
-        "which": "1.2.14"
+        "which": "1.3.0"
       }
     },
     "npm-run-path": {
@@ -4853,7 +4830,7 @@
       "requires": {
         "commander": "2.11.0",
         "npm-path": "2.0.3",
-        "which": "1.2.14"
+        "which": "1.3.0"
       }
     },
     "nullthrows": {
@@ -4872,9 +4849,9 @@
       "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
     },
     "oauth2-server": {
-      "version": "3.0.0-b4",
-      "resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.0-b4.tgz",
-      "integrity": "sha1-9915nX+JvJYR3YCvOd9SFECFa3M=",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.0.tgz",
+      "integrity": "sha1-xGJ2t0w9KGNNWe6YH3a1imRZzCg=",
       "requires": {
         "basic-auth": "1.1.0",
         "bluebird": "3.5.0",
@@ -5061,7 +5038,7 @@
         "got": "5.7.1",
         "registry-auth-token": "3.3.1",
         "registry-url": "3.1.0",
-        "semver": "5.3.0"
+        "semver": "5.4.1"
       }
     },
     "parse-glob": {
@@ -5220,7 +5197,7 @@
       "integrity": "sha1-7bo1LT7YpgMRTWZyZazOYNaJzN8=",
       "dev": true,
       "requires": {
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "power-assert-context-traversal": "1.1.1"
       }
     },
@@ -5230,7 +5207,7 @@
       "integrity": "sha1-iMq8oNE7Y1nwfT0+ivppkmRXftk=",
       "dev": true,
       "requires": {
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "estraverse": "4.2.0"
       }
     },
@@ -5256,7 +5233,7 @@
       "integrity": "sha1-ZV+PcRk1qbbVQbhjJ2VHF8Y3qYY=",
       "dev": true,
       "requires": {
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "power-assert-renderer-base": "1.1.1",
         "power-assert-util-string-width": "1.1.1",
         "stringifier": "1.3.0"
@@ -5268,7 +5245,7 @@
       "integrity": "sha1-wqRosjgiq9b4Diq6UyI0ewnfR24=",
       "dev": true,
       "requires": {
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "power-assert-renderer-diagram": "1.1.2"
       }
     },
@@ -5312,6 +5289,15 @@
             "pseudomap": "1.0.2",
             "yallist": "2.1.2"
           }
+        },
+        "which": {
+          "version": "1.2.14",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
+          "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
+          "dev": true,
+          "requires": {
+            "isexe": "2.0.0"
+          }
         }
       }
     },
@@ -5333,9 +5319,9 @@
       "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
     },
     "prettier": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.5.2.tgz",
-      "integrity": "sha512-f55mvineQ5yc36cLX4n4RWP6JH6MLcfi5f9MVsjpfBs4MVSG2GYT4v6cukzmvkIOvmNOdCZfDSMY3hQcMcDQbQ==",
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.5.3.tgz",
+      "integrity": "sha1-WdrcaDNF7GuI+IuU7Urn4do5S/4=",
       "dev": true
     },
     "pretty-ms": {
@@ -5391,12 +5377,12 @@
       }
     },
     "proxy-addr": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz",
-      "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=",
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz",
+      "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=",
       "requires": {
         "forwarded": "0.1.0",
-        "ipaddr.js": "1.3.0"
+        "ipaddr.js": "1.4.0"
       }
     },
     "ps-tree": {
@@ -5587,7 +5573,7 @@
       "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
       "dev": true,
       "requires": {
-        "resolve": "1.3.3"
+        "resolve": "1.4.0"
       }
     },
     "redent": {
@@ -5617,7 +5603,7 @@
       "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.23.0",
+        "babel-runtime": "6.25.0",
         "babel-types": "6.25.0",
         "private": "0.1.7"
       }
@@ -5725,7 +5711,7 @@
         "is-typedarray": "1.0.0",
         "isstream": "0.1.2",
         "json-stringify-safe": "5.0.1",
-        "mime-types": "2.1.15",
+        "mime-types": "2.1.16",
         "oauth-sign": "0.8.2",
         "performance-now": "0.2.0",
         "qs": "6.4.0",
@@ -5742,7 +5728,7 @@
       "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
       "requires": {
         "resolve-from": "2.0.0",
-        "semver": "5.3.0"
+        "semver": "5.4.1"
       }
     },
     "require-from-string": {
@@ -5776,9 +5762,9 @@
       }
     },
     "resolve": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
-      "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
+      "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
       "dev": true,
       "requires": {
         "path-parse": "1.0.5"
@@ -5883,9 +5869,9 @@
       "dev": true
     },
     "semver": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
-      "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
+      "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
     },
     "semver-diff": {
       "version": "2.1.0",
@@ -5893,48 +5879,38 @@
       "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
       "dev": true,
       "requires": {
-        "semver": "5.3.0"
+        "semver": "5.4.1"
       }
     },
     "send": {
-      "version": "0.15.3",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz",
-      "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=",
+      "version": "0.15.4",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.15.4.tgz",
+      "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=",
       "requires": {
-        "debug": "2.6.7",
-        "depd": "1.1.0",
+        "debug": "2.6.8",
+        "depd": "1.1.1",
         "destroy": "1.0.4",
         "encodeurl": "1.0.1",
         "escape-html": "1.0.3",
         "etag": "1.8.0",
         "fresh": "0.5.0",
-        "http-errors": "1.6.1",
+        "http-errors": "1.6.2",
         "mime": "1.3.4",
         "ms": "2.0.0",
         "on-finished": "2.3.0",
         "range-parser": "1.2.0",
         "statuses": "1.3.1"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "2.6.7",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
-          "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        }
       }
     },
     "serve-static": {
-      "version": "1.12.3",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz",
-      "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=",
+      "version": "1.12.4",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz",
+      "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=",
       "requires": {
         "encodeurl": "1.0.1",
         "escape-html": "1.0.3",
         "parseurl": "1.3.1",
-        "send": "0.15.3"
+        "send": "0.15.4"
       }
     },
     "set-immediate-shim": {
@@ -6044,12 +6020,12 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#442ae1c09d14ccd40162a8401e81fbdcaf7bcccc",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#71d0a2c29a70c046ca356285c983c1d8d7e1b139",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.1",
         "buffer-crc32": "0.2.13",
-        "bunyan": "1.8.10",
+        "bunyan": "1.8.12",
         "chalk": "1.1.3",
         "coap-packet": "0.1.14",
         "compact-array": "0.0.1",
@@ -6222,7 +6198,7 @@
       "integrity": "sha1-3vGDQvaTPbDy2/yaoCF1tEjBeVk=",
       "dev": true,
       "requires": {
-        "core-js": "2.4.1",
+        "core-js": "2.5.0",
         "traverse": "0.6.6",
         "type-name": "2.0.2"
       }
@@ -6302,7 +6278,7 @@
           "requires": {
             "async": "1.5.2",
             "combined-stream": "1.0.5",
-            "mime-types": "2.1.15"
+            "mime-types": "2.1.16"
           }
         }
       }
@@ -6345,7 +6321,7 @@
         "chalk": "1.1.3",
         "lodash": "4.17.4",
         "slice-ansi": "0.0.4",
-        "string-width": "2.1.0"
+        "string-width": "2.1.1"
       },
       "dependencies": {
         "ansi-regex": {
@@ -6361,9 +6337,9 @@
           "dev": true
         },
         "string-width": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz",
-          "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=",
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
           "dev": true,
           "requires": {
             "is-fullwidth-code-point": "2.0.0",
@@ -6527,7 +6503,7 @@
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
       "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=",
       "requires": {
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.26",
         "next-tick": "1.0.0"
       }
     },
@@ -6610,7 +6586,7 @@
       "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=",
       "requires": {
         "media-typer": "0.3.0",
-        "mime-types": "2.1.15"
+        "mime-types": "2.1.16"
       }
     },
     "type-name": {
@@ -6765,11 +6741,20 @@
       "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc="
     },
     "verror": {
-      "version": "1.3.6",
-      "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
-      "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
       "requires": {
-        "extsprintf": "1.0.2"
+        "assert-plus": "1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "1.3.0"
+      },
+      "dependencies": {
+        "assert-plus": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+          "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+        }
       }
     },
     "when": {
@@ -6778,9 +6763,9 @@
       "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I="
     },
     "which": {
-      "version": "1.2.14",
-      "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
-      "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
+      "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
       "dev": true,
       "requires": {
         "isexe": "2.0.0"

From 607dfaea525f55816ec53b0556fe91d202b41845 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 10 Aug 2017 21:11:11 +0200
Subject: [PATCH 481/504] remove comments

---
 dist/managers/WebhookManager.js | 5 +----
 src/managers/WebhookManager.js  | 4 ++--
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index ab473b2c..a2bf6865 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -366,10 +366,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
                 });
               });
 
-              _this._webhookLogger.log(event,
-              // webhook,
-              // requestOptions,
-              _responseBody, responseEventData);
+              _this._webhookLogger.log(event, webhook, requestOptions, _responseBody, responseEventData);
               _context6.next = 28;
               break;
 
diff --git a/src/managers/WebhookManager.js b/src/managers/WebhookManager.js
index 95ca9f43..3a0f06c7 100644
--- a/src/managers/WebhookManager.js
+++ b/src/managers/WebhookManager.js
@@ -292,8 +292,8 @@ class WebhookManager {
 
       this._webhookLogger.log(
         event,
-        // webhook,
-        // requestOptions,
+        webhook,
+        requestOptions,
         responseBody,
         responseEventData,
       );

From c3982eb508b65ae50d4a55927bdd05fe1b28fffb Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 11 Aug 2017 11:29:24 +0200
Subject: [PATCH 482/504] add deviceManager assignment in productsController,
 closes https://github.com/Brewskey/spark-server/issues/245

---
 dist/controllers/ProductsController.js | 1 +
 src/controllers/ProductsController.js  | 1 +
 2 files changed, 2 insertions(+)

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 14cdcc21..3bde708e 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -124,6 +124,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (ProductsController.__proto__ || (0, _getPrototypeOf2.default)(ProductsController)).call(this));
 
+    _this._deviceManager = deviceManager;
     _this._deviceAttributeRepository = deviceAttributeRepository;
     _this._organizationRepository = organizationRepository;
     _this._productConfigRepository = productConfigRepository;
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 5339e9b7..c65e499c 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -51,6 +51,7 @@ class ProductsController extends Controller {
   ) {
     super();
 
+    this._deviceManager = deviceManager;
     this._deviceAttributeRepository = deviceAttributeRepository;
     this._organizationRepository = organizationRepository;
     this._productConfigRepository = productConfigRepository;

From 1b123a770dc469becfdd35513077cf45242b762b Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 12 Aug 2017 23:08:50 +0200
Subject: [PATCH 483/504] update package-lock.json for spark-protocol change:
 https://github.com/Brewskey/spark-protocol/commit/3472b1a3d312b1d50d537a8a28ce877f5e2c82b3

closes https://github.com/Brewskey/spark-server/issues/244
---
 package-lock.json | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 6a9e0a60..2abcd654 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2082,7 +2082,7 @@
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
       "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
       "requires": {
-        "es5-ext": "0.10.26"
+        "es5-ext": "0.10.27"
       }
     },
     "dashdash": {
@@ -2382,9 +2382,9 @@
       }
     },
     "es5-ext": {
-      "version": "0.10.26",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.26.tgz",
-      "integrity": "sha1-UbISilMbcMT2dkCTpzy+u4IYY3I=",
+      "version": "0.10.27",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.27.tgz",
+      "integrity": "sha512-3KXJRYzKXTd7xfFy5uZsJCXue55fAYQ035PRjyYk2PicllxIwcW9l3AbM/eGaw3vgVAUW4tl4xg9AXDEI6yw0w==",
       "requires": {
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
@@ -2396,7 +2396,7 @@
       "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.26",
+        "es5-ext": "0.10.27",
         "es6-symbol": "3.1.1"
       }
     },
@@ -2407,7 +2407,7 @@
       "dev": true,
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.26",
+        "es5-ext": "0.10.27",
         "es6-iterator": "2.0.1",
         "es6-set": "0.1.5",
         "es6-symbol": "3.1.1",
@@ -2426,7 +2426,7 @@
       "dev": true,
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.26",
+        "es5-ext": "0.10.27",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1",
         "event-emitter": "0.3.5"
@@ -2438,7 +2438,7 @@
       "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.26"
+        "es5-ext": "0.10.27"
       }
     },
     "es6-weak-map": {
@@ -2447,7 +2447,7 @@
       "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.26",
+        "es5-ext": "0.10.27",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
       }
@@ -2769,7 +2769,7 @@
       "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.26"
+        "es5-ext": "0.10.27"
       }
     },
     "event-stream": {
@@ -3993,7 +3993,7 @@
         "log-update": "1.0.2",
         "ora": "0.2.3",
         "p-map": "1.1.1",
-        "rxjs": "5.4.2",
+        "rxjs": "5.4.3",
         "stream-to-observable": "0.1.0",
         "strip-ansi": "3.0.1"
       }
@@ -4279,7 +4279,7 @@
       "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
       "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
       "requires": {
-        "es5-ext": "0.10.26"
+        "es5-ext": "0.10.27"
       }
     },
     "map-obj": {
@@ -4335,7 +4335,7 @@
       "integrity": "sha1-G8PqHkvgVt1HXVIZede+PV5bIcg=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.26",
+        "es5-ext": "0.10.27",
         "es6-weak-map": "2.0.2",
         "event-emitter": "0.3.5",
         "is-promise": "2.1.0",
@@ -5835,9 +5835,9 @@
       "dev": true
     },
     "rxjs": {
-      "version": "5.4.2",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz",
-      "integrity": "sha1-KjI2/L8D31e64G/Wly/ZnlwI/Pc=",
+      "version": "5.4.3",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz",
+      "integrity": "sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==",
       "dev": true,
       "requires": {
         "symbol-observable": "1.0.4"
@@ -6020,7 +6020,7 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#71d0a2c29a70c046ca356285c983c1d8d7e1b139",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#3472b1a3d312b1d50d537a8a28ce877f5e2c82b3",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.1",
@@ -6503,7 +6503,7 @@
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
       "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=",
       "requires": {
-        "es5-ext": "0.10.26",
+        "es5-ext": "0.10.27",
         "next-tick": "1.0.0"
       }
     },

From 2fc3aee39928a34613efe6861dd36fe51e1e7fbe Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 16 Aug 2017 15:56:57 +0200
Subject: [PATCH 484/504] update package-lock.json for spark-protocol change:
 https://github.com/Brewskey/spark-protocol/commit/b5ede6dbaa5cc16e6f4705d2dec930aacd64d089

---
 package-lock.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package-lock.json b/package-lock.json
index 2abcd654..17e0d716 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6020,7 +6020,7 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#3472b1a3d312b1d50d537a8a28ce877f5e2c82b3",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#b5ede6dbaa5cc16e6f4705d2dec930aacd64d089",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.1",

From ad9a973cc0afc61a40dea1dae7ba42a9cf8675d1 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 17 Aug 2017 19:04:10 +0200
Subject: [PATCH 485/504] update package-lock.json for spark-protocol change:
 https://github.com/Brewskey/spark-protocol/commit/2e1dde37210078221e22a085e0e8a89d642df15d

---
 package-lock.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package-lock.json b/package-lock.json
index 17e0d716..00c9a322 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6020,7 +6020,7 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#b5ede6dbaa5cc16e6f4705d2dec930aacd64d089",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#2e1dde37210078221e22a085e0e8a89d642df15d",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.1",

From 7827abeee5ab348abff30b98345223b86d1162a1 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 17 Aug 2017 19:16:18 +0200
Subject: [PATCH 486/504] update package-lock.json for spark-protocol change:
 https://github.com/Brewskey/spark-protocol/commit/6e83a4613f953485d4491d91bf5907adc5fe2d5d

---
 package-lock.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package-lock.json b/package-lock.json
index 00c9a322..fdcf8e48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6020,7 +6020,7 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#2e1dde37210078221e22a085e0e8a89d642df15d",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#6e83a4613f953485d4491d91bf5907adc5fe2d5d",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.1",

From a6fd3c1ae5d76ea209ef921dc66cd68eb2b1cd80 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 20 Aug 2017 13:33:29 +0200
Subject: [PATCH 487/504] product api fixes

---
 dist/controllers/ProductsController.js        |  2 +-
 .../DeviceAttributeDatabaseRepository.js      | 15 +++++------
 dist/repository/ProductDatabaseRepository.js  | 26 ++++++++++++++-----
 examples/Products.md                          |  2 +-
 src/controllers/ProductsController.js         |  2 --
 .../DeviceAttributeDatabaseRepository.js      |  2 +-
 src/repository/ProductDatabaseRepository.js   |  8 +++---
 7 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 3bde708e..bf551c0c 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -849,7 +849,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                   return productDevice.deviceID;
                 });
                 _context12.next = 16;
-                return this._deviceAttributeRepository.getManyFromIDs(deviceIDs, this.user.id);
+                return this._deviceAttributeRepository.getManyFromIDs(deviceIDs);
 
               case 16:
                 _context12.t0 = function (device) {
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index 6106a6cb..a20ed378 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -8,14 +8,14 @@ var _stringify = require('babel-runtime/core-js/json/stringify');
 
 var _stringify2 = _interopRequireDefault(_stringify);
 
-var _extends2 = require('babel-runtime/helpers/extends');
-
-var _extends3 = _interopRequireDefault(_extends2);
-
 var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
 
 var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -165,10 +165,9 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
             switch (_context5.prev = _context5.next) {
               case 0:
                 _context5.next = 2;
-                return _this._database.find(_this._collectionName, {
-                  deviceID: { $in: deviceIDs },
-                  ownerID: ownerID
-                });
+                return _this._database.find(_this._collectionName, (0, _extends3.default)({
+                  deviceID: { $in: deviceIDs }
+                }, ownerID ? { ownerID: ownerID } : {}));
 
               case 2:
                 _context5.t0 = _this._parseVariables;
diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
index c1d14725..0dc44489 100644
--- a/dist/repository/ProductDatabaseRepository.js
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -75,7 +75,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
 
               case 10:
                 _context.t6 = _context.sent;
-                _context.t7 = _context.t6 + 1;
+                _context.t7 = (_context.t6 + 1).toString();
                 _context.t8 = {
                   created_at: _context.t5,
                   product_id: _context.t7
@@ -212,13 +212,27 @@ var ProductDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
-                _context6.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: productID }, { $set: (0, _extends3.default)({}, product) });
+                _context6.t0 = _this._database;
+                _context6.t1 = _this._collectionName;
+                _context6.t2 = { _id: productID };
+                _context6.t3 = _extends3.default;
+                _context6.t4 = {};
+                _context6.next = 7;
+                return _this._formatProduct(product);
 
-              case 2:
+              case 7:
+                _context6.t5 = _context6.sent;
+                _context6.t6 = (0, _context6.t3)(_context6.t4, _context6.t5);
+                _context6.t7 = {
+                  $set: _context6.t6
+                };
+                _context6.next = 12;
+                return _context6.t0.findAndModify.call(_context6.t0, _context6.t1, _context6.t2, _context6.t7);
+
+              case 12:
                 return _context6.abrupt('return', _context6.sent);
 
-              case 3:
+              case 13:
               case 'end':
                 return _context6.stop();
             }
@@ -252,7 +266,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
               case 3:
                 existingProduct = _context7.sent;
 
-                if (!(existingProduct && existingProduct.id !== product.id)) {
+                if (!(existingProduct && existingProduct.product_id !== product.id)) {
                   _context7.next = 6;
                   break;
                 }
diff --git a/examples/Products.md b/examples/Products.md
index eee6c9bd..9c07b458 100644
--- a/examples/Products.md
+++ b/examples/Products.md
@@ -160,7 +160,7 @@ device, it will be automatically quarantined. You will need to update the
   Example cURL:
   ```
   curl -X PUT \
-    http://172.20.8.40:8080/v1/products \
+    http://172.20.8.40:8080/v1/products/test-device-2-v101 \
     -H 'authorization: Bearer d2fafe627086dc56472aa0d8cc13ae9c20293371' \
     -H 'cache-control: no-cache' \
     -H 'content-type: application/json' \
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index c65e499c..cab935cf 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -146,7 +146,6 @@ class ProductsController extends Controller {
     if (!product) {
       return this.bad(`Product ${productIDOrSlug} doesn't exist`);
     }
-
     product = await this._productRepository.updateByID(product.id, {
       ...product,
       ...model.product,
@@ -415,7 +414,6 @@ class ProductsController extends Controller {
 
     const devices = (await this._deviceAttributeRepository.getManyFromIDs(
       deviceIDs,
-      this.user.id,
     )).map(device => {
       const { denied, development, quarantined } = nullthrows(
         productDevices.find(
diff --git a/src/repository/DeviceAttributeDatabaseRepository.js b/src/repository/DeviceAttributeDatabaseRepository.js
index 56877fa9..8b0fbdb9 100644
--- a/src/repository/DeviceAttributeDatabaseRepository.js
+++ b/src/repository/DeviceAttributeDatabaseRepository.js
@@ -48,7 +48,7 @@ class DeviceAttributeDatabaseRepository extends BaseRepository
   ): Promise> =>
     (await this._database.find(this._collectionName, {
       deviceID: { $in: deviceIDs },
-      ownerID,
+      ...(ownerID ? { ownerID } : {}),
     })).map(this._parseVariables);
 
   updateByID = async (
diff --git a/src/repository/ProductDatabaseRepository.js b/src/repository/ProductDatabaseRepository.js
index cb868603..90b06522 100644
--- a/src/repository/ProductDatabaseRepository.js
+++ b/src/repository/ProductDatabaseRepository.js
@@ -20,7 +20,9 @@ class ProductDatabaseRepository extends BaseRepository
     await this._database.insertOne(this._collectionName, {
       ...(await this._formatProduct(model)),
       created_at: new Date(),
-      product_id: (await this._database.count(this._collectionName)) + 1,
+      // save it as string to be able to search in getByIDOrSlug() method
+      product_id: ((await this._database.count(this._collectionName)) +
+        1).toString(),
     });
 
   deleteByID = async (id: string): Promise =>
@@ -44,7 +46,7 @@ class ProductDatabaseRepository extends BaseRepository
     await this._database.findAndModify(
       this._collectionName,
       { _id: productID },
-      { $set: { ...product } },
+      { $set: { ...(await this._formatProduct(product)) } },
     );
 
   _formatProduct = async (
@@ -62,7 +64,7 @@ class ProductDatabaseRepository extends BaseRepository
       slug,
     });
 
-    if (existingProduct && existingProduct.id !== product.id) {
+    if (existingProduct && existingProduct.product_id !== product.id) {
       throw new Error('Product name or version already in use');
     }
 

From 4e080bfdb3d03c0b4521536b44b3b64564c89441 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Wed, 23 Aug 2017 19:35:21 +0200
Subject: [PATCH 488/504] revert back casting product_id to string on
 production creation, use helper function for getting numeric productID from
 IDOrSlug

---
 src/repository/ProductDatabaseRepository.js | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/repository/ProductDatabaseRepository.js b/src/repository/ProductDatabaseRepository.js
index 90b06522..8e5cd2fb 100644
--- a/src/repository/ProductDatabaseRepository.js
+++ b/src/repository/ProductDatabaseRepository.js
@@ -6,6 +6,13 @@ import type { IBaseDatabase, IProductRepository, Product } from '../types';
 import COLLECTION_NAMES from './collectionNames';
 import BaseRepository from './BaseRepository';
 
+const getProductIDFromIDOrSlug = (IDOrSlug: string): ?number => {
+  const numericStringValue = IDOrSlug.replace(/[^0-9]/g, '');
+  return numericStringValue.length === IDOrSlug.length
+    ? parseInt(numericStringValue, 10)
+    : null;
+};
+
 class ProductDatabaseRepository extends BaseRepository
   implements IProductRepository {
   _database: IBaseDatabase;
@@ -20,9 +27,7 @@ class ProductDatabaseRepository extends BaseRepository
     await this._database.insertOne(this._collectionName, {
       ...(await this._formatProduct(model)),
       created_at: new Date(),
-      // save it as string to be able to search in getByIDOrSlug() method
-      product_id: ((await this._database.count(this._collectionName)) +
-        1).toString(),
+      product_id: (await this._database.count(this._collectionName)) + 1,
     });
 
   deleteByID = async (id: string): Promise =>
@@ -39,13 +44,16 @@ class ProductDatabaseRepository extends BaseRepository
 
   getByIDOrSlug = async (productIDOrSlug: string): Promise =>
     await this._database.findOne(this._collectionName, {
-      $or: [{ product_id: productIDOrSlug }, { slug: productIDOrSlug }],
+      $or: [
+        { product_id: getProductIDFromIDOrSlug(productIDOrSlug) },
+        { slug: productIDOrSlug },
+      ],
     });
 
-  updateByID = async (productID: string, product: Product): Promise =>
+  updateByID = async (id: string, product: Product): Promise =>
     await this._database.findAndModify(
       this._collectionName,
-      { _id: productID },
+      { _id: id },
       { $set: { ...(await this._formatProduct(product)) } },
     );
 

From 438e59d56ed589ecce17d9384f1d3007900dc533 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 24 Aug 2017 01:36:22 +0200
Subject: [PATCH 489/504] add equals to 0 check for right platform_id
 filtering.

---
 src/controllers/ProductsController.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index cab935cf..334023cc 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -80,7 +80,7 @@ class ProductsController extends Controller {
       'name',
       'platform_id',
       'type',
-    ].filter(key => !model.product[key]);
+    ].filter(key => !model.product[key] && model.product[key] !== 0);
     if (missingFields.length) {
       return this.bad(`Missing fields: ${missingFields.join(', ')}`);
     }
@@ -137,7 +137,7 @@ class ProductsController extends Controller {
       'organization',
       'platform_id',
       'type',
-    ].filter(key => !model.product[key]);
+    ].filter(key => !model.product[key] && model.product[key] !== 0);
     if (missingFields.length) {
       return this.bad(`Missing fields: ${missingFields.join(', ')}`);
     }

From 2f85048f48e3af0ecad6464d9d65e4dd6e7c8f90 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 24 Aug 2017 01:44:40 +0200
Subject: [PATCH 490/504] fix _formatProduct in ProductDatabaseRepository

---
 src/repository/ProductDatabaseRepository.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/repository/ProductDatabaseRepository.js b/src/repository/ProductDatabaseRepository.js
index 8e5cd2fb..5ec7d56f 100644
--- a/src/repository/ProductDatabaseRepository.js
+++ b/src/repository/ProductDatabaseRepository.js
@@ -72,7 +72,7 @@ class ProductDatabaseRepository extends BaseRepository
       slug,
     });
 
-    if (existingProduct && existingProduct.product_id !== product.id) {
+    if (existingProduct && existingProduct.id !== product.id) {
       throw new Error('Product name or version already in use');
     }
 

From 4a683a50299bafafa38356675090a84ebff1f832 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 24 Aug 2017 01:54:00 +0200
Subject: [PATCH 491/504] add parseInt for firmware version comes from route
 params.

---
 src/controllers/ProductsController.js | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 334023cc..a4d82bd4 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -208,7 +208,7 @@ class ProductsController extends Controller {
   @route('/v1/products/:productIDOrSlug/firmware/:version')
   async getSingleFirmware(
     productIDOrSlug: string,
-    version: number,
+    version: string,
   ): Promise<*> {
     const product = await this._productRepository.getByIDOrSlug(
       productIDOrSlug,
@@ -221,7 +221,7 @@ class ProductsController extends Controller {
     );
 
     const existingFirmware = firmwareList.find(
-      firmware => firmware.version === version,
+      firmware => firmware.version === parseInt(version, 10),
     );
     if (!existingFirmware) {
       return this.bad(`Firmware version ${version} does not exist`);
@@ -322,7 +322,7 @@ class ProductsController extends Controller {
   @route('/v1/products/:productIDOrSlug/firmware/:version')
   async updateFirmware(
     productIDOrSlug: string,
-    version: number,
+    version: string,
     body: $Shape,
   ): Promise<*> {
     const { current, description, title } = body;
@@ -342,7 +342,7 @@ class ProductsController extends Controller {
     );
 
     const existingFirmware = firmwareList.find(
-      firmware => firmware.version === version,
+      firmware => firmware.version === parseInt(version, 10),
     );
     if (!existingFirmware) {
       return this.bad(`Firmware version ${version} does not exist`);
@@ -361,7 +361,7 @@ class ProductsController extends Controller {
 
   @httpVerb('delete')
   @route('/v1/products/:productIDOrSlug/firmware/:version')
-  async deleteFirmware(productIDOrSlug: string, version: number): Promise<*> {
+  async deleteFirmware(productIDOrSlug: string, version: string): Promise<*> {
     const product = await this._productRepository.getByIDOrSlug(
       productIDOrSlug,
     );
@@ -373,7 +373,7 @@ class ProductsController extends Controller {
     );
 
     const existingFirmware = firmwareList.find(
-      firmware => firmware.version === version,
+      firmware => firmware.version === parseInt(version, 10),
     );
     if (!existingFirmware) {
       return this.bad(`Firmware version ${version} does not exist`);
@@ -437,10 +437,7 @@ class ProductsController extends Controller {
 
   @httpVerb('get')
   @route('/v1/products/:productIDOrSlug/devices/:deviceID')
-  async getSingleDevices(
-    productIDOrSlug: string,
-    deviceID: string,
-  ): Promise<*> {
+  async getSingleDevice(productIDOrSlug: string, deviceID: string): Promise<*> {
     const product = await this._productRepository.getByIDOrSlug(
       productIDOrSlug,
     );

From 8162cdee763ec5a62a44a353fc7165838911b8d8 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 24 Aug 2017 02:20:03 +0200
Subject: [PATCH 492/504] formatDeviceAttributes, add product_id to returned
 product devices props

---
 src/controllers/ProductsController.js | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index a4d82bd4..8f1db2ae 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -22,6 +22,7 @@ import httpVerb from '../decorators/httpVerb';
 import nullthrows from 'nullthrows';
 import route from '../decorators/route';
 import HttpError from '../lib/HttpError';
+import formatDeviceAttributesToApi from '../lib/deviceToAPI';
 
 type ProductFirmwareUpload = {
   current: boolean,
@@ -414,17 +415,17 @@ class ProductsController extends Controller {
 
     const devices = (await this._deviceAttributeRepository.getManyFromIDs(
       deviceIDs,
-    )).map(device => {
-      const { denied, development, quarantined } = nullthrows(
+    )).map(deviceAttributes => {
+      const { denied, development, productID, quarantined } = nullthrows(
         productDevices.find(
-          productDevice => productDevice.deviceID === device.deviceID,
+          productDevice => productDevice.deviceID === deviceAttributes.deviceID,
         ),
       );
-
       return {
-        ...device,
+        ...formatDeviceAttributesToApi(deviceAttributes),
         denied,
         development,
+        product_id: product.product_id,
         quarantined,
       };
     });
@@ -464,9 +465,10 @@ class ProductsController extends Controller {
     const { denied, development, quarantined } = productDevice;
 
     return this.ok({
-      ...deviceAttributes,
+      ...formatDeviceAttributesToApi(deviceAttributes),
       denied,
       development,
+      product_id: product.product_id,
       quarantined,
     });
   }

From 36245078cad9dccf27b12cca615ab2d9e4c3611a Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 24 Aug 2017 03:11:40 +0200
Subject: [PATCH 493/504] build files

---
 dist/controllers/ProductsController.js       | 33 ++++++++++++--------
 dist/repository/ProductDatabaseRepository.js | 15 ++++++---
 2 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index bf551c0c..5d5a9dc1 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -85,6 +85,10 @@ var _HttpError = require('../lib/HttpError');
 
 var _HttpError2 = _interopRequireDefault(_HttpError);
 
+var _deviceToAPI = require('../lib/deviceToAPI');
+
+var _deviceToAPI2 = _interopRequireDefault(_deviceToAPI);
+
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
@@ -182,7 +186,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 2:
                 missingFields = ['description', 'hardware_version', 'name', 'platform_id', 'type'].filter(function (key) {
-                  return !model.product[key];
+                  return !model.product[key] && model.product[key] !== 0;
                 });
 
                 if (!missingFields.length) {
@@ -302,7 +306,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 2:
                 missingFields = ['config_id', 'description', 'hardware_version', 'id', 'name', 'organization', 'platform_id', 'type'].filter(function (key) {
-                  return !model.product[key];
+                  return !model.product[key] && model.product[key] !== 0;
                 });
 
                 if (!missingFields.length) {
@@ -512,7 +516,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
               case 7:
                 firmwareList = _context8.sent;
                 existingFirmware = firmwareList.find(function (firmware) {
-                  return firmware.version === version;
+                  return firmware.version === parseInt(version, 10);
                 });
 
                 if (existingFirmware) {
@@ -713,7 +717,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
               case 9:
                 firmwareList = _context10.sent;
                 existingFirmware = firmwareList.find(function (firmware) {
-                  return firmware.version === version;
+                  return firmware.version === parseInt(version, 10);
                 });
 
                 if (existingFirmware) {
@@ -775,7 +779,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
               case 7:
                 firmwareList = _context11.sent;
                 existingFirmware = firmwareList.find(function (firmware) {
-                  return firmware.version === version;
+                  return firmware.version === parseInt(version, 10);
                 });
 
                 if (existingFirmware) {
@@ -852,17 +856,19 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 return this._deviceAttributeRepository.getManyFromIDs(deviceIDs);
 
               case 16:
-                _context12.t0 = function (device) {
+                _context12.t0 = function (deviceAttributes) {
                   var _nullthrows = (0, _nullthrows3.default)(productDevices.find(function (productDevice) {
-                    return productDevice.deviceID === device.deviceID;
+                    return productDevice.deviceID === deviceAttributes.deviceID;
                   })),
                       denied = _nullthrows.denied,
                       development = _nullthrows.development,
+                      productID = _nullthrows.productID,
                       quarantined = _nullthrows.quarantined;
 
-                  return (0, _extends3.default)({}, device, {
+                  return (0, _extends3.default)({}, (0, _deviceToAPI2.default)(deviceAttributes), {
                     denied: denied,
                     development: development,
+                    product_id: product.product_id,
                     quarantined: quarantined
                   });
                 };
@@ -889,7 +895,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
       return getDevices;
     }()
   }, {
-    key: 'getSingleDevices',
+    key: 'getSingleDevice',
     value: function () {
       var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(productIDOrSlug, deviceID) {
         var product, deviceAttributes, productDevice, denied, development, quarantined;
@@ -940,9 +946,10 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 15:
                 denied = productDevice.denied, development = productDevice.development, quarantined = productDevice.quarantined;
-                return _context13.abrupt('return', this.ok((0, _extends3.default)({}, deviceAttributes, {
+                return _context13.abrupt('return', this.ok((0, _extends3.default)({}, (0, _deviceToAPI2.default)(deviceAttributes), {
                   denied: denied,
                   development: development,
+                  product_id: product.product_id,
                   quarantined: quarantined
                 })));
 
@@ -954,11 +961,11 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee13, this);
       }));
 
-      function getSingleDevices(_x19, _x20) {
+      function getSingleDevice(_x19, _x20) {
         return _ref14.apply(this, arguments);
       }
 
-      return getSingleDevices;
+      return getSingleDevice;
     }()
   }, {
     key: 'addDevice',
@@ -1386,6 +1393,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     }
   }]);
   return ProductsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProduct', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteProduct', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleFirmware', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addFirmware', [_dec17, _dec18, _dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateFirmware', [_dec20, _dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteFirmware', [_dec22, _dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec24, _dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleDevices', [_dec26, _dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addDevice', [_dec28, _dec29, _dec30], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDeviceProduct', [_dec31, _dec32], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDeviceProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec33, _dec34], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec35, _dec36], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec37, _dec38], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProduct', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteProduct', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleFirmware', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addFirmware', [_dec17, _dec18, _dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateFirmware', [_dec20, _dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteFirmware', [_dec22, _dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec24, _dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleDevice', [_dec26, _dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addDevice', [_dec28, _dec29, _dec30], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDeviceProduct', [_dec31, _dec32], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDeviceProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec33, _dec34], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec35, _dec36], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec37, _dec38], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
 exports.default = ProductsController;
 /* eslint-enable */
\ No newline at end of file
diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
index 0dc44489..53781570 100644
--- a/dist/repository/ProductDatabaseRepository.js
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -42,6 +42,11 @@ var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var getProductIDFromIDOrSlug = function getProductIDFromIDOrSlug(IDOrSlug) {
+  var numericStringValue = IDOrSlug.replace(/[^0-9]/g, '');
+  return numericStringValue.length === IDOrSlug.length ? parseInt(numericStringValue, 10) : null;
+};
+
 var ProductDatabaseRepository = function (_BaseRepository) {
   (0, _inherits3.default)(ProductDatabaseRepository, _BaseRepository);
 
@@ -75,7 +80,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
 
               case 10:
                 _context.t6 = _context.sent;
-                _context.t7 = (_context.t6 + 1).toString();
+                _context.t7 = _context.t6 + 1;
                 _context.t8 = {
                   created_at: _context.t5,
                   product_id: _context.t7
@@ -187,7 +192,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
               case 0:
                 _context5.next = 2;
                 return _this._database.findOne(_this._collectionName, {
-                  $or: [{ product_id: productIDOrSlug }, { slug: productIDOrSlug }]
+                  $or: [{ product_id: getProductIDFromIDOrSlug(productIDOrSlug) }, { slug: productIDOrSlug }]
                 });
 
               case 2:
@@ -207,14 +212,14 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.updateByID = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productID, product) {
+      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(id, product) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
                 _context6.t0 = _this._database;
                 _context6.t1 = _this._collectionName;
-                _context6.t2 = { _id: productID };
+                _context6.t2 = { _id: id };
                 _context6.t3 = _extends3.default;
                 _context6.t4 = {};
                 _context6.next = 7;
@@ -266,7 +271,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
               case 3:
                 existingProduct = _context7.sent;
 
-                if (!(existingProduct && existingProduct.product_id !== product.id)) {
+                if (!(existingProduct && existingProduct.id !== product.id)) {
                   _context7.next = 6;
                   break;
                 }

From 7d0f077a2e83aec679029593774714a6d315c567 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 24 Aug 2017 04:11:29 +0200
Subject: [PATCH 494/504] use isNaN in getByIDOrSlug

---
 dist/repository/ProductDatabaseRepository.js |  7 +------
 src/repository/ProductDatabaseRepository.js  | 13 +++++--------
 2 files changed, 6 insertions(+), 14 deletions(-)

diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
index 53781570..e36b617d 100644
--- a/dist/repository/ProductDatabaseRepository.js
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -42,11 +42,6 @@ var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var getProductIDFromIDOrSlug = function getProductIDFromIDOrSlug(IDOrSlug) {
-  var numericStringValue = IDOrSlug.replace(/[^0-9]/g, '');
-  return numericStringValue.length === IDOrSlug.length ? parseInt(numericStringValue, 10) : null;
-};
-
 var ProductDatabaseRepository = function (_BaseRepository) {
   (0, _inherits3.default)(ProductDatabaseRepository, _BaseRepository);
 
@@ -192,7 +187,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
               case 0:
                 _context5.next = 2;
                 return _this._database.findOne(_this._collectionName, {
-                  $or: [{ product_id: getProductIDFromIDOrSlug(productIDOrSlug) }, { slug: productIDOrSlug }]
+                  $or: [{ product_id: !isNaN(productIDOrSlug) ? parseInt(productIDOrSlug, 10) : null }, { slug: productIDOrSlug }]
                 });
 
               case 2:
diff --git a/src/repository/ProductDatabaseRepository.js b/src/repository/ProductDatabaseRepository.js
index 5ec7d56f..9d28f820 100644
--- a/src/repository/ProductDatabaseRepository.js
+++ b/src/repository/ProductDatabaseRepository.js
@@ -6,13 +6,6 @@ import type { IBaseDatabase, IProductRepository, Product } from '../types';
 import COLLECTION_NAMES from './collectionNames';
 import BaseRepository from './BaseRepository';
 
-const getProductIDFromIDOrSlug = (IDOrSlug: string): ?number => {
-  const numericStringValue = IDOrSlug.replace(/[^0-9]/g, '');
-  return numericStringValue.length === IDOrSlug.length
-    ? parseInt(numericStringValue, 10)
-    : null;
-};
-
 class ProductDatabaseRepository extends BaseRepository
   implements IProductRepository {
   _database: IBaseDatabase;
@@ -45,7 +38,11 @@ class ProductDatabaseRepository extends BaseRepository
   getByIDOrSlug = async (productIDOrSlug: string): Promise =>
     await this._database.findOne(this._collectionName, {
       $or: [
-        { product_id: getProductIDFromIDOrSlug(productIDOrSlug) },
+        {
+          product_id: !isNaN(productIDOrSlug)
+            ? parseInt(productIDOrSlug, 10)
+            : null,
+        },
         { slug: productIDOrSlug },
       ],
     });

From 4bdbcefc2737bbe425865108f7d750c2a387a332 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 26 Aug 2017 14:52:42 +0200
Subject: [PATCH 495/504] fix flashProductDevices method, fix csv parsing

---
 dist/controllers/ProductsController.js       | 72 ++++++++++++--------
 dist/managers/DeviceManager.js               |  6 +-
 dist/repository/ProductDatabaseRepository.js |  4 +-
 src/controllers/ProductsController.js        | 22 ++++--
 src/managers/DeviceManager.js                |  5 +-
 5 files changed, 68 insertions(+), 41 deletions(-)

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 5d5a9dc1..34797e2f 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -659,14 +659,10 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 31:
                 firmware = _context9.sent;
-
-
-                this._deviceManager.flashProductFirmware(product.product_id, body.binary);
-
                 data = firmware.data, id = firmware.id, output = (0, _objectWithoutProperties3.default)(firmware, ['data', 'id']);
                 return _context9.abrupt('return', this.ok(output));
 
-              case 35:
+              case 34:
               case 'end':
                 return _context9.stop();
             }
@@ -734,9 +730,14 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
               case 15:
                 firmware = _context10.sent;
                 data = firmware.data, id = firmware.id, output = (0, _objectWithoutProperties3.default)(firmware, ['data', 'id']);
+
+
+                if (current) {
+                  this._deviceManager.flashProductFirmware(product.id, firmware.data);
+                }
                 return _context10.abrupt('return', this.ok(output));
 
-              case 18:
+              case 19:
               case 'end':
                 return _context10.stop();
             }
@@ -996,7 +997,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 ids = null;
 
                 if (!(body.import_method === 'many')) {
-                  _context14.next = 21;
+                  _context14.next = 23;
                   break;
                 }
 
@@ -1020,48 +1021,59 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 return _context14.abrupt('return', this.bad('File must be csv or txt file.'));
 
               case 13:
-                records = _csv2.default.parse(_file.buffer.toString('utf8'));
+                _context14.next = 15;
+                return new _promise2.default(function (resolve, reject) {
+                  return _csv2.default.parse(_file.buffer.toString('utf8'), function (error, data) {
+                    if (error) {
+                      reject(error);
+                    }
+                    resolve(data);
+                  });
+                });
+
+              case 15:
+                records = _context14.sent;
 
                 if (records.length) {
-                  _context14.next = 16;
+                  _context14.next = 18;
                   break;
                 }
 
                 return _context14.abrupt('return', this.bad('File didn\'t have any ids'));
 
-              case 16:
+              case 18:
                 if (!records.some(function (record) {
                   return record.length !== 1;
                 })) {
-                  _context14.next = 18;
+                  _context14.next = 20;
                   break;
                 }
 
                 return _context14.abrupt('return', this.bad('File should only have a single column of device ids'));
 
-              case 18:
+              case 20:
 
                 ids = [].concat.apply([], records);
-                _context14.next = 24;
+                _context14.next = 26;
                 break;
 
-              case 21:
+              case 23:
                 if (body.id) {
-                  _context14.next = 23;
+                  _context14.next = 25;
                   break;
                 }
 
                 return _context14.abrupt('return', this.bad('You must pass an id for a device'));
 
-              case 23:
+              case 25:
 
                 ids = [body.id];
 
-              case 24:
-                _context14.next = 26;
+              case 26:
+                _context14.next = 28;
                 return this._deviceAttributeRepository.getManyFromIDs(ids, this.user.id);
 
-              case 26:
+              case 28:
                 deviceAttributes = _context14.sent;
                 incorrectPlatformDeviceIDs = deviceAttributes.filter(function (deviceAttribute) {
                   return deviceAttribute.particleProductId !== product.platform_id;
@@ -1071,10 +1083,10 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 deviceAttributeIDs = deviceAttributes.map(function (deviceAttribute) {
                   return deviceAttribute.deviceID;
                 });
-                _context14.next = 31;
+                _context14.next = 33;
                 return this._productDeviceRepository.getManyFromDeviceIDs(ids);
 
-              case 31:
+              case 33:
                 _context14.t0 = function (productDevice) {
                   return productDevice.deviceID;
                 };
@@ -1086,7 +1098,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 });
 
                 if (!invalidDeviceIds.length) {
-                  _context14.next = 37;
+                  _context14.next = 39;
                   break;
                 }
 
@@ -1099,11 +1111,11 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                   status: 400
                 });
 
-              case 37:
+              case 39:
                 idsToCreate = ids.filter(function (id) {
                   return !invalidDeviceIds.includes(id) && !existingProductDeviceIDs.includes(id);
                 });
-                _context14.next = 40;
+                _context14.next = 42;
                 return _promise2.default.all(idsToCreate.map(function (id) {
                   return _this2._productDeviceRepository.create({
                     denied: false,
@@ -1115,14 +1127,14 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                   });
                 }));
 
-              case 40:
+              case 42:
                 return _context14.abrupt('return', this.ok({
                   updated: idsToCreate.length,
                   nonmemberDeviceIds: nonmemberDeviceIds,
                   invalidDeviceIds: invalidDeviceIds
                 }));
 
-              case 41:
+              case 43:
               case 'end':
                 return _context14.stop();
             }
@@ -1137,7 +1149,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
       return addDevice;
     }()
   }, {
-    key: 'updateDeviceProduct',
+    key: 'updateProductDevice',
     value: function () {
       var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(productIDOrSlug, deviceID, _ref17) {
         var denied = _ref17.denied,
@@ -1257,11 +1269,11 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
         }, _callee15, this);
       }));
 
-      function updateDeviceProduct(_x23, _x24, _x25) {
+      function updateProductDevice(_x23, _x24, _x25) {
         return _ref16.apply(this, arguments);
       }
 
-      return updateDeviceProduct;
+      return updateProductDevice;
     }()
   }, {
     key: 'removeDeviceFromProduct',
@@ -1393,6 +1405,6 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
     }
   }]);
   return ProductsController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProduct', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteProduct', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleFirmware', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addFirmware', [_dec17, _dec18, _dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateFirmware', [_dec20, _dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteFirmware', [_dec22, _dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec24, _dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleDevice', [_dec26, _dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addDevice', [_dec28, _dec29, _dec30], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDeviceProduct', [_dec31, _dec32], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDeviceProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec33, _dec34], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec35, _dec36], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec37, _dec38], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'getProducts', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProducts'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'createProduct', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'createProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getProduct', [_dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProduct', [_dec7, _dec8], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteProduct', [_dec9, _dec10], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getConfig', [_dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getConfig'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getFirmware', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleFirmware', [_dec15, _dec16], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addFirmware', [_dec17, _dec18, _dec19], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateFirmware', [_dec20, _dec21], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'deleteFirmware', [_dec22, _dec23], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'deleteFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec24, _dec25], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getSingleDevice', [_dec26, _dec27], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getSingleDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'addDevice', [_dec28, _dec29, _dec30], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'addDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateProductDevice', [_dec31, _dec32], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateProductDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeDeviceFromProduct', [_dec33, _dec34], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeDeviceFromProduct'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec35, _dec36], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'removeTeamMember', [_dec37, _dec38], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'removeTeamMember'), _class.prototype)), _class));
 exports.default = ProductsController;
 /* eslint-enable */
\ No newline at end of file
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index c3c71d22..32e1b30a 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -491,9 +491,9 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     };
   }();
 
-  this.flashProductFirmware = function (productID, file) {
-    _this._eventPublisher.publish({
-      context: { fileBuffer: file.buffer, productID: productID },
+  this.flashProductFirmware = function (productID, fileBuffer) {
+    return _this._eventPublisher.publish({
+      context: { fileBuffer: fileBuffer, productID: productID },
       name: _sparkProtocol.SPARK_SERVER_EVENTS.FLASH_PRODUCT_FIRMWARE
     });
   };
diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
index e36b617d..6fb348d5 100644
--- a/dist/repository/ProductDatabaseRepository.js
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -187,7 +187,9 @@ var ProductDatabaseRepository = function (_BaseRepository) {
               case 0:
                 _context5.next = 2;
                 return _this._database.findOne(_this._collectionName, {
-                  $or: [{ product_id: !isNaN(productIDOrSlug) ? parseInt(productIDOrSlug, 10) : null }, { slug: productIDOrSlug }]
+                  $or: [{
+                    product_id: !isNaN(productIDOrSlug) ? parseInt(productIDOrSlug, 10) : null
+                  }, { slug: productIDOrSlug }]
                 });
 
               case 2:
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 8f1db2ae..cb389e40 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -313,8 +313,6 @@ class ProductsController extends Controller {
       version: version,
     });
 
-    this._deviceManager.flashProductFirmware(product.product_id, body.binary);
-
     const { data, id, ...output } = firmware;
     return this.ok(output);
   }
@@ -357,6 +355,10 @@ class ProductsController extends Controller {
       },
     );
     const { data, id, ...output } = firmware;
+
+    if (current) {
+      this._deviceManager.flashProductFirmware(product.id, firmware.data);
+    }
     return this.ok(output);
   }
 
@@ -499,7 +501,19 @@ class ProductsController extends Controller {
         return this.bad('File must be csv or txt file.');
       }
 
-      const records = csv.parse(file.buffer.toString('utf8'));
+      const records = await new Promise(
+        (resolve: (data: any) => void, reject: (error: Error) => void) =>
+          csv.parse(
+            file.buffer.toString('utf8'),
+            (error: ?Error, data: any) => {
+              if (error) {
+                reject(error);
+              }
+              resolve(data);
+            },
+          ),
+      );
+
       if (!records.length) {
         return this.bad(`File didn't have any ids`);
       }
@@ -581,7 +595,7 @@ class ProductsController extends Controller {
 
   @httpVerb('put')
   @route('/v1/products/:productIDOrSlug/devices/:deviceID')
-  async updateDeviceProduct(
+  async updateProductDevice(
     productIDOrSlug: string,
     deviceID: string,
     {
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index c25436d1..84999cef 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -240,12 +240,11 @@ class DeviceManager {
     return flashResponse;
   };
 
-  flashProductFirmware = (productID: string, file: File) => {
+  flashProductFirmware = (productID: string, fileBuffer: Buffer): void =>
     this._eventPublisher.publish({
-      context: { fileBuffer: file.buffer, productID },
+      context: { fileBuffer, productID },
       name: SPARK_SERVER_EVENTS.FLASH_PRODUCT_FIRMWARE,
     });
-  };
 
   provision = async (
     deviceID: string,

From abeb5c31ec387de1a922818427b4f9f4b07f813a Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 27 Aug 2017 01:18:09 +0200
Subject: [PATCH 496/504] save product_id instead product.id in productDevice

---
 dist/controllers/ProductsController.js |  8 ++++----
 src/controllers/ProductsController.js  | 11 +++++++----
 src/managers/DeviceManager.js          |  2 +-
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index 34797e2f..cac78708 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -733,7 +733,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
 
                 if (current) {
-                  this._deviceManager.flashProductFirmware(product.id, firmware.data);
+                  this._deviceManager.flashProductFirmware(product.product_id, firmware.data);
                 }
                 return _context10.abrupt('return', this.ok(output));
 
@@ -840,13 +840,13 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                 page = query.page, _query$per_page = query.per_page, per_page = _query$per_page === undefined ? 25 : _query$per_page;
                 _context12.next = 9;
                 return this._productDeviceRepository.count({
-                  productID: product.id
+                  productID: product.product_id
                 });
 
               case 9:
                 totalDevices = _context12.sent;
                 _context12.next = 12;
-                return this._productDeviceRepository.getAllByProductID(product.id, page, per_page);
+                return this._productDeviceRepository.getAllByProductID(product.product_id, page, per_page);
 
               case 12:
                 productDevices = _context12.sent;
@@ -1122,7 +1122,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
                     development: false,
                     deviceID: id,
                     lockedFirmwareVersion: null,
-                    productID: product.id,
+                    productID: product.product_id,
                     quarantined: nonmemberDeviceIds.includes(id)
                   });
                 }));
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index cb389e40..0f0d3848 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -357,7 +357,10 @@ class ProductsController extends Controller {
     const { data, id, ...output } = firmware;
 
     if (current) {
-      this._deviceManager.flashProductFirmware(product.id, firmware.data);
+      this._deviceManager.flashProductFirmware(
+        product.product_id,
+        firmware.data,
+      );
     }
     return this.ok(output);
   }
@@ -403,10 +406,10 @@ class ProductsController extends Controller {
     query.page = Math.max(1, query.page);
     const { page, per_page = 25 } = query;
     const totalDevices = await this._productDeviceRepository.count({
-      productID: product.id,
+      productID: product.product_id,
     });
     const productDevices = await this._productDeviceRepository.getAllByProductID(
-      product.id,
+      product.product_id,
       page,
       per_page,
     );
@@ -580,7 +583,7 @@ class ProductsController extends Controller {
           development: false,
           deviceID: id,
           lockedFirmwareVersion: null,
-          productID: product.id,
+          productID: product.product_id,
           quarantined: nonmemberDeviceIds.includes(id),
         }),
       ),
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 84999cef..23e5ac6c 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -240,7 +240,7 @@ class DeviceManager {
     return flashResponse;
   };
 
-  flashProductFirmware = (productID: string, fileBuffer: Buffer): void =>
+  flashProductFirmware = (productID: number, fileBuffer: Buffer): void =>
     this._eventPublisher.publish({
       context: { fileBuffer, productID },
       name: SPARK_SERVER_EVENTS.FLASH_PRODUCT_FIRMWARE,

From ee7eddc6649b58cffd8a3d1b7c3492cbb6416940 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 27 Aug 2017 01:20:56 +0200
Subject: [PATCH 497/504] cast productFirmware buffer when save to/ get from
 db.

---
 dist/lib/deviceToAPI.js                       |  1 +
 .../ProductFirmwareDatabaseRepository.js      | 48 +++++++++-----
 src/lib/deviceToAPI.js                        |  2 +
 .../ProductFirmwareDatabaseRepository.js      | 65 +++++++++++++++----
 4 files changed, 88 insertions(+), 28 deletions(-)

diff --git a/dist/lib/deviceToAPI.js b/dist/lib/deviceToAPI.js
index 71582cfc..75c2d370 100644
--- a/dist/lib/deviceToAPI.js
+++ b/dist/lib/deviceToAPI.js
@@ -17,6 +17,7 @@ var deviceToAPI = function deviceToAPI(device, result) {
     last_ip_address: device.ip,
     name: device.name,
     platform_id: device.particleProductId,
+    product_firmware_version: device.productFirmwareVersion,
     product_id: device.particleProductId,
     return_value: result,
     status: 'normal',
diff --git a/dist/repository/ProductFirmwareDatabaseRepository.js b/dist/repository/ProductFirmwareDatabaseRepository.js
index 640ac482..d27d0673 100644
--- a/dist/repository/ProductFirmwareDatabaseRepository.js
+++ b/dist/repository/ProductFirmwareDatabaseRepository.js
@@ -8,10 +8,6 @@ var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
 
-var _extends2 = require('babel-runtime/helpers/extends');
-
-var _extends3 = _interopRequireDefault(_extends2);
-
 var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
@@ -32,6 +28,10 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
+var _extends2 = require('babel-runtime/helpers/extends');
+
+var _extends3 = _interopRequireDefault(_extends2);
+
 var _collectionNames = require('./collectionNames');
 
 var _collectionNames2 = _interopRequireDefault(_collectionNames);
@@ -42,6 +42,12 @@ var _BaseRepository3 = _interopRequireDefault(_BaseRepository2);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
+var formatProductFirmwareFromDb = function formatProductFirmwareFromDb(productFirmware) {
+  return (0, _extends3.default)({}, productFirmware, {
+    data: Buffer.from(productFirmware.data)
+  });
+};
+
 var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
   (0, _inherits3.default)(ProductFirmwareDatabaseRepository, _BaseRepository);
 
@@ -62,6 +68,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
               case 0:
                 _context.next = 2;
                 return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
+                  data: model.data.toString(),
                   updated_at: new Date()
                 }));
 
@@ -120,9 +127,10 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
                 return _this._database.find(_this._collectionName, query);
 
               case 3:
-                return _context3.abrupt('return', _context3.sent);
+                _context3.t0 = formatProductFirmwareFromDb;
+                return _context3.abrupt('return', _context3.sent.map(_context3.t0));
 
-              case 4:
+              case 5:
               case 'end':
                 return _context3.stop();
             }
@@ -145,9 +153,10 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
                 return _this._database.find(_this._collectionName, { product_id: productID });
 
               case 2:
-                return _context4.abrupt('return', _context4.sent);
+                _context4.t0 = formatProductFirmwareFromDb;
+                return _context4.abrupt('return', _context4.sent.map(_context4.t0));
 
-              case 3:
+              case 4:
               case 'end':
                 return _context4.stop();
             }
@@ -162,6 +171,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
 
     _this.getByVersionForProduct = function () {
       var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productID, version) {
+        var productFirmware;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
@@ -173,9 +183,10 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
                 });
 
               case 2:
-                return _context5.abrupt('return', _context5.sent);
+                productFirmware = _context5.sent;
+                return _context5.abrupt('return', productFirmware ? formatProductFirmwareFromDb(productFirmware) : null);
 
-              case 3:
+              case 4:
               case 'end':
                 return _context5.stop();
             }
@@ -190,6 +201,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
 
     _this.getCurrentForProduct = function () {
       var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productID) {
+        var productFirmware;
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
@@ -201,9 +213,10 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
                 });
 
               case 2:
-                return _context6.abrupt('return', _context6.sent);
+                productFirmware = _context6.sent;
+                return _context6.abrupt('return', productFirmware ? formatProductFirmwareFromDb(productFirmware) : null);
 
-              case 3:
+              case 4:
               case 'end':
                 return _context6.stop();
             }
@@ -218,6 +231,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
 
     _this.getByID = function () {
       var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
+        var productFirmware;
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
@@ -226,9 +240,10 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
                 return _this._database.findOne(_this._collectionName, { _id: id });
 
               case 2:
-                return _context7.abrupt('return', _context7.sent);
+                productFirmware = _context7.sent;
+                return _context7.abrupt('return', productFirmware ? formatProductFirmwareFromDb(productFirmware) : null);
 
-              case 3:
+              case 4:
               case 'end':
                 return _context7.stop();
             }
@@ -248,7 +263,10 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
             switch (_context8.prev = _context8.next) {
               case 0:
                 _context8.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, { updated_at: new Date() }) });
+                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, productFirmware.data ? { data: productFirmware.data.toString() } : {}, {
+                    updated_at: new Date()
+                  })
+                }).then(formatProductFirmwareFromDb);
 
               case 2:
                 return _context8.abrupt('return', _context8.sent);
diff --git a/src/lib/deviceToAPI.js b/src/lib/deviceToAPI.js
index fe92e52b..b0d90826 100644
--- a/src/lib/deviceToAPI.js
+++ b/src/lib/deviceToAPI.js
@@ -15,6 +15,7 @@ export type DeviceAPIType = {|
   last_ip_address: ?string,
   name: string,
   platform_id: number,
+  product_firmware_version: number,
   product_id: number,
   return_value?: mixed,
   status: string,
@@ -34,6 +35,7 @@ const deviceToAPI = (device: Device, result?: mixed): DeviceAPIType => ({
   last_ip_address: device.ip,
   name: device.name,
   platform_id: device.particleProductId,
+  product_firmware_version: device.productFirmwareVersion,
   product_id: device.particleProductId,
   return_value: result,
   status: 'normal',
diff --git a/src/repository/ProductFirmwareDatabaseRepository.js b/src/repository/ProductFirmwareDatabaseRepository.js
index 024fe81e..6be7da46 100644
--- a/src/repository/ProductFirmwareDatabaseRepository.js
+++ b/src/repository/ProductFirmwareDatabaseRepository.js
@@ -10,6 +10,14 @@ import type {
 import COLLECTION_NAMES from './collectionNames';
 import BaseRepository from './BaseRepository';
 
+const formatProductFirmwareFromDb = (
+  productFirmware: Object,
+): ProductFirmware =>
+  ({
+    ...productFirmware,
+    data: Buffer.from(productFirmware.data),
+  }: any);
+
 class ProductFirmwareDatabaseRepository extends BaseRepository
   implements IProductFirmwareRepository {
   _database: IBaseDatabase;
@@ -23,6 +31,7 @@ class ProductFirmwareDatabaseRepository extends BaseRepository
   create = async (model: $Shape): Promise =>
     await this._database.insertOne(this._collectionName, {
       ...model,
+      data: model.data.toString(),
       updated_at: new Date(),
     });
 
@@ -32,41 +41,71 @@ class ProductFirmwareDatabaseRepository extends BaseRepository
   getAll = async (userID: ?string = null): Promise> => {
     // TODO - this should probably just query the organization
     const query = userID ? { ownerID: userID } : {};
-    return await this._database.find(this._collectionName, query);
+    return (await this._database.find(this._collectionName, query)).map(
+      formatProductFirmwareFromDb,
+    );
   };
 
   getAllByProductID = async (
     productID: string,
   ): Promise> =>
-    await this._database.find(this._collectionName, { product_id: productID });
+    (await this._database.find(this._collectionName, {
+      product_id: productID,
+    })).map(formatProductFirmwareFromDb);
 
   getByVersionForProduct = async (
     productID: string,
     version: number,
-  ): Promise =>
-    await this._database.findOne(this._collectionName, {
+  ): Promise => {
+    const productFirmware = await this._database.findOne(this._collectionName, {
       product_id: productID,
       version,
     });
+    return productFirmware
+      ? formatProductFirmwareFromDb(productFirmware)
+      : null;
+  };
 
-  getCurrentForProduct = async (productID: string): Promise =>
-    await this._database.findOne(this._collectionName, {
+  getCurrentForProduct = async (
+    productID: string,
+  ): Promise => {
+    const productFirmware = await this._database.findOne(this._collectionName, {
       current: true,
       product_id: productID,
     });
+    return productFirmware
+      ? formatProductFirmwareFromDb(productFirmware)
+      : null;
+  };
 
-  getByID = async (id: string): Promise =>
-    await this._database.findOne(this._collectionName, { _id: id });
+  getByID = async (id: string): Promise => {
+    const productFirmware = await this._database.findOne(this._collectionName, {
+      _id: id,
+    });
+    return productFirmware
+      ? formatProductFirmwareFromDb(productFirmware)
+      : null;
+  };
 
   updateByID = async (
     productFirmwareID: string,
     productFirmware: ProductFirmware,
   ): Promise =>
-    await this._database.findAndModify(
-      this._collectionName,
-      { _id: productFirmwareID },
-      { $set: { ...productFirmware, updated_at: new Date() } },
-    );
+    await this._database
+      .findAndModify(
+        this._collectionName,
+        { _id: productFirmwareID },
+        {
+          $set: {
+            ...productFirmware,
+            ...(productFirmware.data
+              ? { data: productFirmware.data.toString() }
+              : {}),
+            updated_at: new Date(),
+          },
+        },
+      )
+      .then(formatProductFirmwareFromDb);
 }
 
 export default ProductFirmwareDatabaseRepository;

From 09082d1b249d55b10f81c8bd3cc8321822a87450 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sun, 27 Aug 2017 23:39:47 +0200
Subject: [PATCH 498/504] update flow-bin, fix some product related flow
 errors, there are still some

---
 package-lock.json                             | 597 +++++++++---------
 package.json                                  |   2 +-
 src/controllers/ProductsController.js         |   2 +-
 .../ProductDeviceDatabaseRepository.js        |   2 +-
 .../ProductFirmwareDatabaseRepository.js      |   6 +-
 src/types.js                                  |  18 +-
 6 files changed, 317 insertions(+), 310 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index fdcf8e48..39e897fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,24 +9,24 @@
       "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-0.0.36.tgz",
       "integrity": "sha1-/dhlxY6OqvCtQBzQMtyGH1xXNCo=",
       "requires": {
-        "@types/node": "8.0.20"
+        "@types/node": "8.0.25"
       }
     },
     "@types/express": {
-      "version": "4.0.36",
-      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz",
-      "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A==",
+      "version": "4.0.37",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.37.tgz",
+      "integrity": "sha512-tIULTLzQpFFs5/PKnFIAFOsXQxss76glppbVKR3/jddPK26SBsD5HF5grn5G2jOGtpRWSBvYmDYoduVv+3wOXg==",
       "requires": {
-        "@types/express-serve-static-core": "4.0.49",
-        "@types/serve-static": "1.7.31"
+        "@types/express-serve-static-core": "4.0.50",
+        "@types/serve-static": "1.7.32"
       }
     },
     "@types/express-serve-static-core": {
-      "version": "4.0.49",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.49.tgz",
-      "integrity": "sha512-b7mVHoURu1xaP/V6xw1sYwyv9V0EZ7euyi+sdnbnTZxEkAh4/hzPsI6Eflq+ZzHQ/Tgl7l16Jz+0oz8F46MLnA==",
+      "version": "4.0.50",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.50.tgz",
+      "integrity": "sha512-0n1YgeUfZEIaMMu82LuOFIFDyMtFtcEP0yjQKihJlNjpCiygDVri7C26DC7jaUOwFXL6ZU2x4tGtNYNEgeO3tw==",
       "requires": {
-        "@types/node": "8.0.20"
+        "@types/node": "8.0.25"
       }
     },
     "@types/mime": {
@@ -35,16 +35,16 @@
       "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A=="
     },
     "@types/node": {
-      "version": "8.0.20",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.20.tgz",
-      "integrity": "sha512-MnB7YEpmLUyEWRVRhKpRs4swwqITnY8BcVFPoTuCl99SCplI/lLUiU5vcJ/OANDqwkpdIg0pDEM38K22KQT2RA=="
+      "version": "8.0.25",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.25.tgz",
+      "integrity": "sha512-zT+t9841g1HsjLtPMCYxmb1U4pcZ2TOegAKiomlmj6bIziuaEYHUavxLE9NRwdntY0vOCrgHho6OXjDX7fm/Kw=="
     },
     "@types/serve-static": {
-      "version": "1.7.31",
-      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz",
-      "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=",
+      "version": "1.7.32",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.32.tgz",
+      "integrity": "sha512-WpI0g7M1FiOmJ/a97Qrjafq2I938tjAZ3hZr9O7sXyA6oUhH3bqUNZIt7r1KZg8TQAKxcvxt6JjQ5XuLfIBFvg==",
       "requires": {
-        "@types/express-serve-static-core": "4.0.49",
+        "@types/express-serve-static-core": "4.0.50",
         "@types/mime": "1.3.1"
       }
     },
@@ -54,9 +54,9 @@
       "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8="
     },
     "accepts": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
-      "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz",
+      "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=",
       "requires": {
         "mime-types": "2.1.16",
         "negotiator": "0.6.1"
@@ -303,8 +303,8 @@
         "auto-bind": "0.1.0",
         "ava-files": "0.2.0",
         "ava-init": "0.1.6",
-        "babel-code-frame": "6.22.0",
-        "babel-core": "6.25.0",
+        "babel-code-frame": "6.26.0",
+        "babel-core": "6.26.0",
         "babel-plugin-ava-throws-helper": "0.1.0",
         "babel-plugin-detective": "2.0.0",
         "babel-plugin-espower": "2.3.2",
@@ -312,7 +312,7 @@
         "babel-preset-es2015": "6.24.1",
         "babel-preset-es2015-node4": "2.1.1",
         "babel-preset-stage-2": "6.24.1",
-        "babel-runtime": "6.25.0",
+        "babel-runtime": "6.26.0",
         "bluebird": "3.5.0",
         "caching-transform": "1.0.1",
         "chalk": "1.1.3",
@@ -364,7 +364,7 @@
         "resolve-cwd": "1.0.0",
         "semver": "5.4.1",
         "set-immediate-shim": "1.0.1",
-        "source-map-support": "0.4.15",
+        "source-map-support": "0.4.16",
         "stack-utils": "0.4.0",
         "strip-ansi": "3.0.1",
         "strip-bom": "2.0.0",
@@ -421,14 +421,14 @@
       "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
     },
     "babel-cli": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.24.1.tgz",
-      "integrity": "sha1-IHzXBbumFImy6kG1MSNBz2rKIoM=",
-      "requires": {
-        "babel-core": "6.25.0",
-        "babel-polyfill": "6.23.0",
-        "babel-register": "6.24.1",
-        "babel-runtime": "6.25.0",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz",
+      "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=",
+      "requires": {
+        "babel-core": "6.26.0",
+        "babel-polyfill": "6.26.0",
+        "babel-register": "6.26.0",
+        "babel-runtime": "6.26.0",
         "chokidar": "1.7.0",
         "commander": "2.11.0",
         "convert-source-map": "1.5.0",
@@ -438,14 +438,14 @@
         "output-file-sync": "1.1.2",
         "path-is-absolute": "1.0.1",
         "slash": "1.0.0",
-        "source-map": "0.5.6",
+        "source-map": "0.5.7",
         "v8flags": "2.1.1"
       }
     },
     "babel-code-frame": {
-      "version": "6.22.0",
-      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
-      "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
       "requires": {
         "chalk": "1.1.3",
         "esutils": "2.0.2",
@@ -453,20 +453,20 @@
       }
     },
     "babel-core": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz",
-      "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz",
+      "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=",
       "requires": {
-        "babel-code-frame": "6.22.0",
-        "babel-generator": "6.25.0",
+        "babel-code-frame": "6.26.0",
+        "babel-generator": "6.26.0",
         "babel-helpers": "6.24.1",
         "babel-messages": "6.23.0",
-        "babel-register": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0",
-        "babylon": "6.17.4",
+        "babel-register": "6.26.0",
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0",
+        "babylon": "6.18.0",
         "convert-source-map": "1.5.0",
         "debug": "2.6.8",
         "json5": "0.5.1",
@@ -475,7 +475,7 @@
         "path-is-absolute": "1.0.1",
         "private": "0.1.7",
         "slash": "1.0.0",
-        "source-map": "0.5.6"
+        "source-map": "0.5.7"
       }
     },
     "babel-eslint": {
@@ -484,24 +484,24 @@
       "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=",
       "dev": true,
       "requires": {
-        "babel-code-frame": "6.22.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0",
-        "babylon": "6.17.4"
+        "babel-code-frame": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0",
+        "babylon": "6.18.0"
       }
     },
     "babel-generator": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz",
-      "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz",
+      "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=",
       "requires": {
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0",
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0",
         "detect-indent": "4.0.0",
         "jsesc": "1.3.0",
         "lodash": "4.17.4",
-        "source-map": "0.5.6",
+        "source-map": "0.5.7",
         "trim-right": "1.0.1"
       }
     },
@@ -510,9 +510,9 @@
       "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
       "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=",
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-builder-binary-assignment-operator-visitor": {
@@ -522,8 +522,8 @@
       "dev": true,
       "requires": {
         "babel-helper-explode-assignable-expression": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-call-delegate": {
@@ -533,20 +533,20 @@
       "dev": true,
       "requires": {
         "babel-helper-hoist-variables": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-define-map": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz",
-      "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz",
+      "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=",
       "dev": true,
       "requires": {
         "babel-helper-function-name": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0",
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0",
         "lodash": "4.17.4"
       }
     },
@@ -556,9 +556,9 @@
       "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-explode-class": {
@@ -567,9 +567,9 @@
       "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=",
       "requires": {
         "babel-helper-bindify-decorators": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-function-name": {
@@ -579,10 +579,10 @@
       "dev": true,
       "requires": {
         "babel-helper-get-function-arity": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-get-function-arity": {
@@ -591,8 +591,8 @@
       "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-hoist-variables": {
@@ -601,8 +601,8 @@
       "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-optimise-call-expression": {
@@ -611,18 +611,18 @@
       "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-regex": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz",
-      "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz",
+      "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0",
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0",
         "lodash": "4.17.4"
       }
     },
@@ -633,10 +633,10 @@
       "dev": true,
       "requires": {
         "babel-helper-function-name": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helper-replace-supers": {
@@ -647,10 +647,10 @@
       "requires": {
         "babel-helper-optimise-call-expression": "6.24.1",
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-helpers": {
@@ -658,8 +658,8 @@
       "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
       "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-messages": {
@@ -667,7 +667,7 @@
       "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
       "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-ava-throws-helper": {
@@ -676,8 +676,8 @@
       "integrity": "sha1-lREHcIoSIIAmv4ykzvGKh7ybDP4=",
       "dev": true,
       "requires": {
-        "babel-template": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-template": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-check-es2015-constants": {
@@ -686,7 +686,7 @@
       "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-detective": {
@@ -701,8 +701,8 @@
       "integrity": "sha1-VRa4/NsmyfDh2BYHSfbkxl5xJx4=",
       "dev": true,
       "requires": {
-        "babel-generator": "6.25.0",
-        "babylon": "6.17.4",
+        "babel-generator": "6.26.0",
+        "babylon": "6.18.0",
         "call-matcher": "1.0.1",
         "core-js": "2.5.0",
         "espower-location-detector": "1.0.0",
@@ -795,7 +795,7 @@
       "requires": {
         "babel-helper-remap-async-to-generator": "6.24.1",
         "babel-plugin-syntax-async-generators": "6.13.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-async-to-generator": {
@@ -806,7 +806,7 @@
       "requires": {
         "babel-helper-remap-async-to-generator": "6.24.1",
         "babel-plugin-syntax-async-functions": "6.13.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-class-constructor-call": {
@@ -816,8 +816,8 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-class-constructor-call": "6.18.0",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-plugin-transform-class-properties": {
@@ -828,8 +828,8 @@
       "requires": {
         "babel-helper-function-name": "6.24.1",
         "babel-plugin-syntax-class-properties": "6.13.0",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-plugin-transform-decorators": {
@@ -839,9 +839,9 @@
       "requires": {
         "babel-helper-explode-class": "6.24.1",
         "babel-plugin-syntax-decorators": "6.13.0",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-decorators-legacy": {
@@ -851,8 +851,8 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-decorators": "6.13.0",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-plugin-transform-do-expressions": {
@@ -862,7 +862,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-do-expressions": "6.13.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-arrow-functions": {
@@ -871,7 +871,7 @@
       "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-block-scoped-functions": {
@@ -880,19 +880,19 @@
       "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-block-scoping": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz",
-      "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz",
+      "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0",
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0",
         "lodash": "4.17.4"
       }
     },
@@ -902,15 +902,15 @@
       "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
       "dev": true,
       "requires": {
-        "babel-helper-define-map": "6.24.1",
+        "babel-helper-define-map": "6.26.0",
         "babel-helper-function-name": "6.24.1",
         "babel-helper-optimise-call-expression": "6.24.1",
         "babel-helper-replace-supers": "6.24.1",
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-computed-properties": {
@@ -919,8 +919,8 @@
       "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-destructuring": {
@@ -929,7 +929,7 @@
       "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-duplicate-keys": {
@@ -938,8 +938,8 @@
       "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-for-of": {
@@ -948,7 +948,7 @@
       "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-function-name": {
@@ -958,8 +958,8 @@
       "dev": true,
       "requires": {
         "babel-helper-function-name": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-literals": {
@@ -968,7 +968,7 @@
       "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-modules-amd": {
@@ -977,21 +977,21 @@
       "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
       "dev": true,
       "requires": {
-        "babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-plugin-transform-es2015-modules-commonjs": "6.26.0",
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-modules-commonjs": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz",
-      "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz",
+      "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=",
       "dev": true,
       "requires": {
         "babel-plugin-transform-strict-mode": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-modules-systemjs": {
@@ -1001,8 +1001,8 @@
       "dev": true,
       "requires": {
         "babel-helper-hoist-variables": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-modules-umd": {
@@ -1012,8 +1012,8 @@
       "dev": true,
       "requires": {
         "babel-plugin-transform-es2015-modules-amd": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-object-super": {
@@ -1023,7 +1023,7 @@
       "dev": true,
       "requires": {
         "babel-helper-replace-supers": "6.24.1",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-parameters": {
@@ -1034,10 +1034,10 @@
       "requires": {
         "babel-helper-call-delegate": "6.24.1",
         "babel-helper-get-function-arity": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-template": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-template": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-shorthand-properties": {
@@ -1046,8 +1046,8 @@
       "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-spread": {
@@ -1056,7 +1056,7 @@
       "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-sticky-regex": {
@@ -1065,9 +1065,9 @@
       "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
       "dev": true,
       "requires": {
-        "babel-helper-regex": "6.24.1",
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-helper-regex": "6.26.0",
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-template-literals": {
@@ -1076,7 +1076,7 @@
       "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-typeof-symbol": {
@@ -1085,7 +1085,7 @@
       "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-es2015-unicode-regex": {
@@ -1094,8 +1094,8 @@
       "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
       "dev": true,
       "requires": {
-        "babel-helper-regex": "6.24.1",
-        "babel-runtime": "6.25.0",
+        "babel-helper-regex": "6.26.0",
+        "babel-runtime": "6.26.0",
         "regexpu-core": "2.0.0"
       }
     },
@@ -1107,7 +1107,7 @@
       "requires": {
         "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1",
         "babel-plugin-syntax-exponentiation-operator": "6.13.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-export-extensions": {
@@ -1117,7 +1117,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-export-extensions": "6.13.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-flow-strip-types": {
@@ -1127,7 +1127,7 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-flow": "6.18.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-function-bind": {
@@ -1137,26 +1137,26 @@
       "dev": true,
       "requires": {
         "babel-plugin-syntax-function-bind": "6.13.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-object-rest-spread": {
-      "version": "6.23.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz",
-      "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz",
+      "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=",
       "dev": true,
       "requires": {
         "babel-plugin-syntax-object-rest-spread": "6.13.0",
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-regenerator": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz",
-      "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz",
+      "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=",
       "dev": true,
       "requires": {
-        "regenerator-transform": "0.9.11"
+        "regenerator-transform": "0.10.1"
       }
     },
     "babel-plugin-transform-runtime": {
@@ -1165,7 +1165,7 @@
       "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0"
+        "babel-runtime": "6.26.0"
       }
     },
     "babel-plugin-transform-strict-mode": {
@@ -1174,18 +1174,25 @@
       "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0"
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0"
       }
     },
     "babel-polyfill": {
-      "version": "6.23.0",
-      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz",
-      "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
+      "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
       "requires": {
-        "babel-runtime": "6.25.0",
+        "babel-runtime": "6.26.0",
         "core-js": "2.5.0",
         "regenerator-runtime": "0.10.5"
+      },
+      "dependencies": {
+        "regenerator-runtime": {
+          "version": "0.10.5",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+          "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
+        }
       }
     },
     "babel-preset-es2015": {
@@ -1197,7 +1204,7 @@
         "babel-plugin-check-es2015-constants": "6.22.0",
         "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
         "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0",
-        "babel-plugin-transform-es2015-block-scoping": "6.24.1",
+        "babel-plugin-transform-es2015-block-scoping": "6.26.0",
         "babel-plugin-transform-es2015-classes": "6.24.1",
         "babel-plugin-transform-es2015-computed-properties": "6.24.1",
         "babel-plugin-transform-es2015-destructuring": "6.23.0",
@@ -1206,7 +1213,7 @@
         "babel-plugin-transform-es2015-function-name": "6.24.1",
         "babel-plugin-transform-es2015-literals": "6.22.0",
         "babel-plugin-transform-es2015-modules-amd": "6.24.1",
-        "babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
+        "babel-plugin-transform-es2015-modules-commonjs": "6.26.0",
         "babel-plugin-transform-es2015-modules-systemjs": "6.24.1",
         "babel-plugin-transform-es2015-modules-umd": "6.24.1",
         "babel-plugin-transform-es2015-object-super": "6.24.1",
@@ -1217,7 +1224,7 @@
         "babel-plugin-transform-es2015-template-literals": "6.22.0",
         "babel-plugin-transform-es2015-typeof-symbol": "6.23.0",
         "babel-plugin-transform-es2015-unicode-regex": "6.24.1",
-        "babel-plugin-transform-regenerator": "6.24.1"
+        "babel-plugin-transform-regenerator": "6.26.0"
       }
     },
     "babel-preset-es2015-node4": {
@@ -1228,7 +1235,7 @@
       "requires": {
         "babel-plugin-transform-es2015-destructuring": "6.23.0",
         "babel-plugin-transform-es2015-function-name": "6.24.1",
-        "babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
+        "babel-plugin-transform-es2015-modules-commonjs": "6.26.0",
         "babel-plugin-transform-es2015-parameters": "6.24.1",
         "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
         "babel-plugin-transform-es2015-spread": "6.22.0",
@@ -1310,54 +1317,54 @@
         "babel-plugin-transform-async-generator-functions": "6.24.1",
         "babel-plugin-transform-async-to-generator": "6.24.1",
         "babel-plugin-transform-exponentiation-operator": "6.24.1",
-        "babel-plugin-transform-object-rest-spread": "6.23.0"
+        "babel-plugin-transform-object-rest-spread": "6.26.0"
       }
     },
     "babel-register": {
-      "version": "6.24.1",
-      "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz",
-      "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz",
+      "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=",
       "requires": {
-        "babel-core": "6.25.0",
-        "babel-runtime": "6.25.0",
+        "babel-core": "6.26.0",
+        "babel-runtime": "6.26.0",
         "core-js": "2.5.0",
         "home-or-tmp": "2.0.0",
         "lodash": "4.17.4",
         "mkdirp": "0.5.1",
-        "source-map-support": "0.4.15"
+        "source-map-support": "0.4.16"
       }
     },
     "babel-runtime": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz",
-      "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+      "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
       "requires": {
         "core-js": "2.5.0",
-        "regenerator-runtime": "0.10.5"
+        "regenerator-runtime": "0.11.0"
       }
     },
     "babel-template": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz",
-      "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=",
-      "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-traverse": "6.25.0",
-        "babel-types": "6.25.0",
-        "babylon": "6.17.4",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+      "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+      "requires": {
+        "babel-runtime": "6.26.0",
+        "babel-traverse": "6.26.0",
+        "babel-types": "6.26.0",
+        "babylon": "6.18.0",
         "lodash": "4.17.4"
       }
     },
     "babel-traverse": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz",
-      "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+      "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
       "requires": {
-        "babel-code-frame": "6.22.0",
+        "babel-code-frame": "6.26.0",
         "babel-messages": "6.23.0",
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0",
-        "babylon": "6.17.4",
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0",
+        "babylon": "6.18.0",
         "debug": "2.6.8",
         "globals": "9.18.0",
         "invariant": "2.2.2",
@@ -1365,20 +1372,20 @@
       }
     },
     "babel-types": {
-      "version": "6.25.0",
-      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
-      "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=",
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+      "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
       "requires": {
-        "babel-runtime": "6.25.0",
+        "babel-runtime": "6.26.0",
         "esutils": "2.0.2",
         "lodash": "4.17.4",
         "to-fast-properties": "1.0.3"
       }
     },
     "babylon": {
-      "version": "6.17.4",
-      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz",
-      "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw=="
+      "version": "6.18.0",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+      "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
     },
     "balanced-match": {
       "version": "1.0.0",
@@ -1410,14 +1417,14 @@
       }
     },
     "binary-extensions": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.9.0.tgz",
-      "integrity": "sha1-ZlBsFs5vTWkopbPNajPKQelB43s="
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz",
+      "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA="
     },
     "binary-version-reader": {
-      "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/binary-version-reader/-/binary-version-reader-0.5.1.tgz",
-      "integrity": "sha1-1LC9MGFlJlsPCcjHHiBd0K4ffHs=",
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/binary-version-reader/-/binary-version-reader-0.5.2.tgz",
+      "integrity": "sha1-oHmgPlXp/haqbxKZbD2BaT91PgQ=",
       "requires": {
         "buffer-crc32": "0.2.13",
         "chai": "3.5.0",
@@ -1552,7 +1559,7 @@
       "integrity": "sha1-K6vEmGtHCsfq7303POAkjOqbXPA=",
       "requires": {
         "@types/bunyan": "0.0.36",
-        "@types/express": "4.0.36",
+        "@types/express": "4.0.37",
         "uuid": "3.1.0"
       }
     },
@@ -1759,9 +1766,9 @@
       }
     },
     "cli-width": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz",
-      "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
       "dev": true
     },
     "clone": {
@@ -2082,7 +2089,7 @@
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
       "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
       "requires": {
-        "es5-ext": "0.10.27"
+        "es5-ext": "0.10.30"
       }
     },
     "dashdash": {
@@ -2382,9 +2389,9 @@
       }
     },
     "es5-ext": {
-      "version": "0.10.27",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.27.tgz",
-      "integrity": "sha512-3KXJRYzKXTd7xfFy5uZsJCXue55fAYQ035PRjyYk2PicllxIwcW9l3AbM/eGaw3vgVAUW4tl4xg9AXDEI6yw0w==",
+      "version": "0.10.30",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.30.tgz",
+      "integrity": "sha1-cUGhaDZpfbq/qq7uQUlc4p9SyTk=",
       "requires": {
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
@@ -2396,7 +2403,7 @@
       "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.27",
+        "es5-ext": "0.10.30",
         "es6-symbol": "3.1.1"
       }
     },
@@ -2407,7 +2414,7 @@
       "dev": true,
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.27",
+        "es5-ext": "0.10.30",
         "es6-iterator": "2.0.1",
         "es6-set": "0.1.5",
         "es6-symbol": "3.1.1",
@@ -2426,7 +2433,7 @@
       "dev": true,
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.27",
+        "es5-ext": "0.10.30",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1",
         "event-emitter": "0.3.5"
@@ -2438,7 +2445,7 @@
       "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.27"
+        "es5-ext": "0.10.30"
       }
     },
     "es6-weak-map": {
@@ -2447,7 +2454,7 @@
       "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.27",
+        "es5-ext": "0.10.30",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
       }
@@ -2480,7 +2487,7 @@
       "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=",
       "dev": true,
       "requires": {
-        "babel-code-frame": "6.22.0",
+        "babel-code-frame": "6.26.0",
         "chalk": "1.1.3",
         "concat-stream": "1.6.0",
         "debug": "2.6.8",
@@ -2493,10 +2500,10 @@
         "file-entry-cache": "2.0.0",
         "glob": "7.1.2",
         "globals": "9.18.0",
-        "ignore": "3.3.3",
+        "ignore": "3.3.4",
         "imurmurhash": "0.1.4",
         "inquirer": "0.12.0",
-        "is-my-json-valid": "2.16.0",
+        "is-my-json-valid": "2.16.1",
         "is-resolvable": "1.0.0",
         "js-yaml": "3.9.1",
         "json-stable-stringify": "1.0.1",
@@ -2692,7 +2699,7 @@
       "requires": {
         "is-url": "1.2.2",
         "path-is-absolute": "1.0.1",
-        "source-map": "0.5.6",
+        "source-map": "0.5.7",
         "xtend": "4.0.1"
       }
     },
@@ -2769,7 +2776,7 @@
       "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.27"
+        "es5-ext": "0.10.30"
       }
     },
     "event-stream": {
@@ -2852,7 +2859,7 @@
       "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz",
       "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=",
       "requires": {
-        "accepts": "1.3.3",
+        "accepts": "1.3.4",
         "array-flatten": "1.1.1",
         "content-disposition": "0.5.2",
         "content-type": "1.0.2",
@@ -3037,9 +3044,9 @@
       }
     },
     "flow-bin": {
-      "version": "0.37.4",
-      "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.37.4.tgz",
-      "integrity": "sha1-PY2i73RugOcw0WbgkED0GYlpt2s=",
+      "version": "0.53.1",
+      "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.53.1.tgz",
+      "integrity": "sha1-myK2OiPJl2OuUz67qwf4jIjJfYQ=",
       "dev": true
     },
     "fn-name": {
@@ -3407,9 +3414,9 @@
       "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es="
     },
     "ignore": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz",
-      "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=",
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.4.tgz",
+      "integrity": "sha512-KjHyHxUgicfgFiTJaIA9DoeY3TIQz5thaKqm35re7RTVVB7zjF1fTMIDMXM4GUUBipR4FW8BvGnA115pZ/AxQQ==",
       "dev": true
     },
     "ignore-by-default": {
@@ -3483,7 +3490,7 @@
         "ansi-regex": "2.1.1",
         "chalk": "1.1.3",
         "cli-cursor": "1.0.2",
-        "cli-width": "2.1.0",
+        "cli-width": "2.2.0",
         "figures": "1.7.0",
         "lodash": "4.17.4",
         "readline2": "1.0.1",
@@ -3535,7 +3542,7 @@
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
       "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
       "requires": {
-        "binary-extensions": "1.9.0"
+        "binary-extensions": "1.10.0"
       }
     },
     "is-buffer": {
@@ -3627,9 +3634,9 @@
       }
     },
     "is-my-json-valid": {
-      "version": "2.16.0",
-      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
-      "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz",
+      "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==",
       "dev": true,
       "requires": {
         "generate-function": "2.0.0",
@@ -3958,9 +3965,9 @@
       }
     },
     "lint-staged": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.0.3.tgz",
-      "integrity": "sha1-HOVVkbwsg6eBqQtpoKDIqg/GNws=",
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.0.4.tgz",
+      "integrity": "sha1-nKaWizDfv+gTZbenY81PSZKJZVM=",
       "dev": true,
       "requires": {
         "app-root-path": "2.0.1",
@@ -4279,7 +4286,7 @@
       "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
       "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
       "requires": {
-        "es5-ext": "0.10.27"
+        "es5-ext": "0.10.30"
       }
     },
     "map-obj": {
@@ -4330,12 +4337,12 @@
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
     },
     "memoizee": {
-      "version": "0.4.5",
-      "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.5.tgz",
-      "integrity": "sha1-G8PqHkvgVt1HXVIZede+PV5bIcg=",
+      "version": "0.4.6",
+      "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.6.tgz",
+      "integrity": "sha1-BDk+RUE3OSGognT/yG0ITSiGFLs=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.27",
+        "es5-ext": "0.10.30",
         "es6-weak-map": "2.0.2",
         "event-emitter": "0.3.5",
         "is-promise": "2.1.0",
@@ -4801,7 +4808,7 @@
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
       "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
       "requires": {
-        "remove-trailing-separator": "1.0.2"
+        "remove-trailing-separator": "1.1.0"
       }
     },
     "npm-path": {
@@ -5557,7 +5564,7 @@
         "ast-types": "0.8.15",
         "esprima-fb": "15001.1001.0-dev-harmony-fb",
         "private": "0.1.7",
-        "source-map": "0.5.6"
+        "source-map": "0.5.7"
       },
       "dependencies": {
         "esprima-fb": {
@@ -5593,18 +5600,18 @@
       "dev": true
     },
     "regenerator-runtime": {
-      "version": "0.10.5",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
-      "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
+      "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A=="
     },
     "regenerator-transform": {
-      "version": "0.9.11",
-      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz",
-      "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=",
+      "version": "0.10.1",
+      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
+      "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.25.0",
-        "babel-types": "6.25.0",
+        "babel-runtime": "6.26.0",
+        "babel-types": "6.26.0",
         "private": "0.1.7"
       }
     },
@@ -5671,9 +5678,9 @@
       }
     },
     "remove-trailing-separator": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz",
-      "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE="
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+      "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
     },
     "repeat-element": {
       "version": "1.1.2",
@@ -6007,23 +6014,23 @@
       }
     },
     "source-map": {
-      "version": "0.5.6",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
-      "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI="
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
     },
     "source-map-support": {
-      "version": "0.4.15",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz",
-      "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=",
+      "version": "0.4.16",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.16.tgz",
+      "integrity": "sha512-A6vlydY7H/ljr4L2UOhDSajQdZQ6dMD7cLH0pzwcmwLyc9u8PNI4WGtnfDDzX7uzGL6c/T+ORL97Zlh+S4iOrg==",
       "requires": {
-        "source-map": "0.5.6"
+        "source-map": "0.5.7"
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#6e83a4613f953485d4491d91bf5907adc5fe2d5d",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#e2d93933f180d0128dae929024c18224c1527045",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
-        "binary-version-reader": "0.5.1",
+        "binary-version-reader": "0.5.2",
         "buffer-crc32": "0.2.13",
         "bunyan": "1.8.12",
         "chalk": "1.1.3",
@@ -6033,7 +6040,7 @@
         "ec-key": "0.0.2",
         "github": "8.2.1",
         "hogan.js": "3.0.2",
-        "memoizee": "0.4.5",
+        "memoizee": "0.4.6",
         "mkdirp": "0.5.1",
         "moment": "2.18.1",
         "moniker": "0.1.2",
@@ -6503,7 +6510,7 @@
       "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
       "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=",
       "requires": {
-        "es5-ext": "0.10.27",
+        "es5-ext": "0.10.30",
         "next-tick": "1.0.0"
       }
     },
diff --git a/package.json b/package.json
index 0aec70c4..d30606ed 100644
--- a/package.json
+++ b/package.json
@@ -128,7 +128,7 @@
     "eslint-plugin-flowtype": "^2.28.2",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-sorting": "^0.3.0",
-    "flow-bin": "^0.37.4",
+    "flow-bin": "^0.53.1",
     "lint-staged": "^4.0.0",
     "nodemon": "^1.11.0",
     "pre-commit": "^1.2.2",
diff --git a/src/controllers/ProductsController.js b/src/controllers/ProductsController.js
index 0f0d3848..81ba56e9 100644
--- a/src/controllers/ProductsController.js
+++ b/src/controllers/ProductsController.js
@@ -182,7 +182,7 @@ class ProductsController extends Controller {
     }
 
     const config = await this._productConfigRepository.getByProductID(
-      product.id,
+      product.product_id,
     );
 
     return this.ok({ product_configuration: config });
diff --git a/src/repository/ProductDeviceDatabaseRepository.js b/src/repository/ProductDeviceDatabaseRepository.js
index 50ecfb1f..7aacadb8 100644
--- a/src/repository/ProductDeviceDatabaseRepository.js
+++ b/src/repository/ProductDeviceDatabaseRepository.js
@@ -35,7 +35,7 @@ class ProductDeviceDatabaseRepository extends BaseRepository
   };
 
   getAllByProductID = async (
-    productID: string,
+    productID: number,
     page: number,
     pageSize: number,
   ): Promise> =>
diff --git a/src/repository/ProductFirmwareDatabaseRepository.js b/src/repository/ProductFirmwareDatabaseRepository.js
index 6be7da46..b2d77aca 100644
--- a/src/repository/ProductFirmwareDatabaseRepository.js
+++ b/src/repository/ProductFirmwareDatabaseRepository.js
@@ -47,14 +47,14 @@ class ProductFirmwareDatabaseRepository extends BaseRepository
   };
 
   getAllByProductID = async (
-    productID: string,
+    productID: number,
   ): Promise> =>
     (await this._database.find(this._collectionName, {
       product_id: productID,
     })).map(formatProductFirmwareFromDb);
 
   getByVersionForProduct = async (
-    productID: string,
+    productID: number,
     version: number,
   ): Promise => {
     const productFirmware = await this._database.findOne(this._collectionName, {
@@ -67,7 +67,7 @@ class ProductFirmwareDatabaseRepository extends BaseRepository
   };
 
   getCurrentForProduct = async (
-    productID: string,
+    productID: number,
   ): Promise => {
     const productFirmware = await this._database.findOne(this._collectionName, {
       current: true,
diff --git a/src/types.js b/src/types.js
index bec307af..62314d81 100644
--- a/src/types.js
+++ b/src/types.js
@@ -171,7 +171,7 @@ export type Settings = {
 export type RequestOptions = {
   auth?: { password: string, username: string },
   body: ?Object,
-  form: ?Object,
+  form: ?Object | ?string,
   headers: ?Object,
   json: boolean,
   method: RequestType,
@@ -196,7 +196,7 @@ export type Product = {|
   name: string,
   organization: string,
   platform_id: PlatformType,
-  product_id: string,
+  product_id: number,
   slug: string,
   type: 'Consumer' | 'Hobbyist' | 'Industrial',
 |};
@@ -208,7 +208,7 @@ export type ProductFirmware = {|
   device_count: number,
   id: string,
   name: string,
-  product_id: string,
+  product_id: number,
   size: number,
   title: string,
   updated_at: Date,
@@ -234,7 +234,7 @@ export type ProductDevice = {|
   id: string,
   lockedFirmwareVersion: ?number,
   notes: string,
-  productID: string,
+  productID: number,
   quarantined: boolean,
 |};
 
@@ -255,13 +255,13 @@ export interface IProductRepository extends IBaseRepository {
 
 export interface IProductConfigRepository
   extends IBaseRepository {
-  getByProductID(productID: string): Promise,
+  getByProductID(productID: number): Promise,
 }
 
 export interface IProductDeviceRepository
   extends IBaseRepository {
   getAllByProductID(
-    productID: string,
+    productID: number,
     page: number,
     perPage: number,
   ): Promise>,
@@ -271,12 +271,12 @@ export interface IProductDeviceRepository
 
 export interface IProductFirmwareRepository
   extends IBaseRepository {
-  getAllByProductID(productID: string): Promise>,
+  getAllByProductID(productID: number): Promise>,
   getByVersionForProduct(
-    productID: string,
+    productID: number,
     version: number,
   ): Promise,
-  getCurrentForProduct(productID: string): Promise,
+  getCurrentForProduct(productID: number): Promise,
 }
 
 export interface IOrganizationRepository extends IBaseRepository {

From d6c42e0ad1ba24d0abe838546918437a5622fee5 Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 31 Aug 2017 00:32:26 +0200
Subject: [PATCH 499/504] fix bugs, add temporary hack for getting right buffer
 from different dbs

---
 dist/repository/MongoDb.js                    | 35 +++++++++---------
 dist/repository/ProductDatabaseRepository.js  | 16 ++++-----
 .../ProductFirmwareDatabaseRepository.js      | 36 ++++++++++++-------
 src/repository/MongoDb.js                     |  5 ++-
 src/repository/ProductDatabaseRepository.js   |  2 +-
 .../ProductFirmwareDatabaseRepository.js      |  9 +++--
 6 files changed, 58 insertions(+), 45 deletions(-)

diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index fce7cd22..97cc6dbc 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -69,7 +69,7 @@ var MongoDb = function (_BaseMongoDb) {
 
     _initialiseProps.call(_this);
 
-    (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+    (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -100,14 +100,15 @@ var _initialiseProps = function _initialiseProps() {
   this._statusEventEmitter = new _events2.default();
 
   this.count = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collectionName, query) {
+    var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(collectionName) {
+      var query = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
             case 0:
               _context3.next = 2;
               return _this3.__runForCollection(collectionName, function () {
-                var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collection) {
+                var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(collection) {
                   return _regenerator2.default.wrap(function _callee2$(_context2) {
                     while (1) {
                       switch (_context2.prev = _context2.next) {
@@ -154,20 +155,20 @@ var _initialiseProps = function _initialiseProps() {
       }, _callee3, _this3);
     }));
 
-    return function (_x2, _x3) {
+    return function (_x2) {
       return _ref2.apply(this, arguments);
     };
   }();
 
   this.insertOne = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collectionName, entity) {
+    var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(collectionName, entity) {
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
               _context5.next = 2;
               return _this3.__runForCollection(collectionName, function () {
-                var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collection) {
+                var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(collection) {
                   var insertResult;
                   return _regenerator2.default.wrap(function _callee4$(_context4) {
                     while (1) {
@@ -210,14 +211,14 @@ var _initialiseProps = function _initialiseProps() {
   }();
 
   this.find = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collectionName, query) {
+    var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(collectionName, query) {
       return _regenerator2.default.wrap(function _callee7$(_context7) {
         while (1) {
           switch (_context7.prev = _context7.next) {
             case 0:
               _context7.next = 2;
               return _this3.__runForCollection(collectionName, function () {
-                var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collection) {
+                var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(collection) {
                   var page, _query$pageSize, pageSize, otherQuery, result, resultItems;
 
                   return _regenerator2.default.wrap(function _callee6$(_context6) {
@@ -271,14 +272,14 @@ var _initialiseProps = function _initialiseProps() {
   }();
 
   this.findAndModify = function () {
-    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collectionName, query, updateQuery) {
+    var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(collectionName, query, updateQuery) {
       return _regenerator2.default.wrap(function _callee9$(_context9) {
         while (1) {
           switch (_context9.prev = _context9.next) {
             case 0:
               _context9.next = 2;
               return _this3.__runForCollection(collectionName, function () {
-                var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collection) {
+                var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(collection) {
                   var modifyResult;
                   return _regenerator2.default.wrap(function _callee8$(_context8) {
                     while (1) {
@@ -321,14 +322,14 @@ var _initialiseProps = function _initialiseProps() {
   }();
 
   this.findOne = function () {
-    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collectionName, query) {
+    var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(collectionName, query) {
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
             case 0:
               _context11.next = 2;
               return _this3.__runForCollection(collectionName, function () {
-                var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collection) {
+                var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(collection) {
                   var resultItem;
                   return _regenerator2.default.wrap(function _callee10$(_context10) {
                     while (1) {
@@ -371,14 +372,14 @@ var _initialiseProps = function _initialiseProps() {
   }();
 
   this.remove = function () {
-    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(collectionName, query) {
+    var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee13(collectionName, query) {
       return _regenerator2.default.wrap(function _callee13$(_context13) {
         while (1) {
           switch (_context13.prev = _context13.next) {
             case 0:
               _context13.next = 2;
               return _this3.__runForCollection(collectionName, function () {
-                var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(collection) {
+                var _ref13 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(collection) {
                   return _regenerator2.default.wrap(function _callee12$(_context12) {
                     while (1) {
                       switch (_context12.prev = _context12.next) {
@@ -419,7 +420,7 @@ var _initialiseProps = function _initialiseProps() {
   }();
 
   this.__runForCollection = function () {
-    var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14(collectionName, callback) {
+    var _ref14 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee14(collectionName, callback) {
       return _regenerator2.default.wrap(function _callee14$(_context14) {
         while (1) {
           switch (_context14.prev = _context14.next) {
@@ -454,7 +455,7 @@ var _initialiseProps = function _initialiseProps() {
   }();
 
   this._init = function () {
-    var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(url, options) {
+    var _ref15 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee15(url, options) {
       var database;
       return _regenerator2.default.wrap(function _callee15$(_context15) {
         while (1) {
@@ -495,7 +496,7 @@ var _initialiseProps = function _initialiseProps() {
     };
   }();
 
-  this._isDbReady = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16() {
+  this._isDbReady = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee16() {
     return _regenerator2.default.wrap(function _callee16$(_context16) {
       while (1) {
         switch (_context16.prev = _context16.next) {
diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
index 6fb348d5..80e0247a 100644
--- a/dist/repository/ProductDatabaseRepository.js
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -55,7 +55,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.PRODUCTS;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
@@ -101,7 +101,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(id) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
@@ -126,7 +126,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAll = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var query;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -155,7 +155,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(id) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -180,7 +180,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByIDOrSlug = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIDOrSlug) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(productIDOrSlug) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
@@ -209,7 +209,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.updateByID = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(id, product) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(id, product) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
@@ -248,7 +248,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this._formatProduct = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(product) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(product) {
         var slug, existingProduct;
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
@@ -268,7 +268,7 @@ var ProductDatabaseRepository = function (_BaseRepository) {
               case 3:
                 existingProduct = _context7.sent;
 
-                if (!(existingProduct && existingProduct.id !== product.id)) {
+                if (!(existingProduct && existingProduct.product_id !== product.product_id)) {
                   _context7.next = 6;
                   break;
                 }
diff --git a/dist/repository/ProductFirmwareDatabaseRepository.js b/dist/repository/ProductFirmwareDatabaseRepository.js
index d27d0673..3148b7db 100644
--- a/dist/repository/ProductFirmwareDatabaseRepository.js
+++ b/dist/repository/ProductFirmwareDatabaseRepository.js
@@ -28,6 +28,10 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
+var _values = require('babel-runtime/core-js/object/values');
+
+var _values2 = _interopRequireDefault(_values);
+
 var _extends2 = require('babel-runtime/helpers/extends');
 
 var _extends3 = _interopRequireDefault(_extends2);
@@ -44,7 +48,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 var formatProductFirmwareFromDb = function formatProductFirmwareFromDb(productFirmware) {
   return (0, _extends3.default)({}, productFirmware, {
-    data: Buffer.from(productFirmware.data)
+    // todo right now its hack for getting right buffer from different dbs
+    data: productFirmware.data.buffer ? productFirmware.data.buffer // for mongo
+    : Buffer.from((0, _values2.default)(productFirmware.data)) // for nedb,
   });
 };
 
@@ -61,14 +67,13 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.PRODUCT_FIRMWARE;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
                 _context.next = 2;
                 return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
-                  data: model.data.toString(),
                   updated_at: new Date()
                 }));
 
@@ -89,7 +94,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(id) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
@@ -114,7 +119,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAll = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var query;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -144,13 +149,15 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAllByProductID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(productID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
                 _context4.next = 2;
-                return _this._database.find(_this._collectionName, { product_id: productID });
+                return _this._database.find(_this._collectionName, {
+                  product_id: productID
+                });
 
               case 2:
                 _context4.t0 = formatProductFirmwareFromDb;
@@ -170,7 +177,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByVersionForProduct = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productID, version) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(productID, version) {
         var productFirmware;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
@@ -200,7 +207,7 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getCurrentForProduct = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productID) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(productID) {
         var productFirmware;
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
@@ -230,14 +237,16 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(id) {
         var productFirmware;
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
               case 0:
                 _context7.next = 2;
-                return _this._database.findOne(_this._collectionName, { _id: id });
+                return _this._database.findOne(_this._collectionName, {
+                  _id: id
+                });
 
               case 2:
                 productFirmware = _context7.sent;
@@ -257,13 +266,14 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.updateByID = function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productFirmwareID, productFirmware) {
+      var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(productFirmwareID, productFirmware) {
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
             switch (_context8.prev = _context8.next) {
               case 0:
                 _context8.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, { $set: (0, _extends3.default)({}, productFirmware, productFirmware.data ? { data: productFirmware.data.toString() } : {}, {
+                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, {
+                  $set: (0, _extends3.default)({}, productFirmware, {
                     updated_at: new Date()
                   })
                 }).then(formatProductFirmwareFromDb);
diff --git a/src/repository/MongoDb.js b/src/repository/MongoDb.js
index 8aa03b08..9da5112a 100644
--- a/src/repository/MongoDb.js
+++ b/src/repository/MongoDb.js
@@ -20,7 +20,10 @@ class MongoDb extends BaseMongoDb implements IBaseDatabase {
     (async (): Promise => await this._init(url, options))();
   }
 
-  count = async (collectionName: string, query: Object): Promise =>
+  count = async (
+    collectionName: string,
+    query?: Object = {},
+  ): Promise =>
     (await this.__runForCollection(
       collectionName,
       async (collection: Object): Promise =>
diff --git a/src/repository/ProductDatabaseRepository.js b/src/repository/ProductDatabaseRepository.js
index 9d28f820..63118acd 100644
--- a/src/repository/ProductDatabaseRepository.js
+++ b/src/repository/ProductDatabaseRepository.js
@@ -69,7 +69,7 @@ class ProductDatabaseRepository extends BaseRepository
       slug,
     });
 
-    if (existingProduct && existingProduct.id !== product.id) {
+    if (existingProduct && existingProduct.product_id !== product.product_id) {
       throw new Error('Product name or version already in use');
     }
 
diff --git a/src/repository/ProductFirmwareDatabaseRepository.js b/src/repository/ProductFirmwareDatabaseRepository.js
index b2d77aca..2b8e7fb2 100644
--- a/src/repository/ProductFirmwareDatabaseRepository.js
+++ b/src/repository/ProductFirmwareDatabaseRepository.js
@@ -15,7 +15,10 @@ const formatProductFirmwareFromDb = (
 ): ProductFirmware =>
   ({
     ...productFirmware,
-    data: Buffer.from(productFirmware.data),
+    // todo right now its hack for getting right buffer from different dbs
+    data: productFirmware.data.buffer
+      ? productFirmware.data.buffer // for mongo
+      : Buffer.from(Object.values(productFirmware.data)), // for nedb,
   }: any);
 
 class ProductFirmwareDatabaseRepository extends BaseRepository
@@ -31,7 +34,6 @@ class ProductFirmwareDatabaseRepository extends BaseRepository
   create = async (model: $Shape): Promise =>
     await this._database.insertOne(this._collectionName, {
       ...model,
-      data: model.data.toString(),
       updated_at: new Date(),
     });
 
@@ -98,9 +100,6 @@ class ProductFirmwareDatabaseRepository extends BaseRepository
         {
           $set: {
             ...productFirmware,
-            ...(productFirmware.data
-              ? { data: productFirmware.data.toString() }
-              : {}),
             updated_at: new Date(),
           },
         },

From 04dd5df34588389cdbfa54ab37b8e9edb21730bf Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Thu, 31 Aug 2017 01:57:46 +0200
Subject: [PATCH 500/504] add devicePing endpoint

---
 dist/controllers/DevicesController.js |  54 +++++++---
 dist/managers/DeviceManager.js        | 150 ++++++++++++++++----------
 src/controllers/DevicesController.js  |   6 ++
 src/managers/DeviceManager.js         |  11 ++
 4 files changed, 150 insertions(+), 71 deletions(-)

diff --git a/dist/controllers/DevicesController.js b/dist/controllers/DevicesController.js
index 43dc55d2..4faa3a2d 100644
--- a/dist/controllers/DevicesController.js
+++ b/dist/controllers/DevicesController.js
@@ -40,7 +40,7 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
 
 var _inherits3 = _interopRequireDefault(_inherits2);
 
-var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _desc, _value, _class;
+var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _dec20, _dec21, _dec22, _desc, _value, _class;
 
 var _nullthrows = require('nullthrows');
 
@@ -111,7 +111,7 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
 
 var logger = _logger2.default.createModuleLogger(module);
 
-var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), (_class = function (_Controller) {
+var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/devices'), _dec3 = (0, _httpVerb2.default)('get'), _dec4 = (0, _route2.default)('/v1/binaries/:binaryID'), _dec5 = (0, _httpVerb2.default)('post'), _dec6 = (0, _route2.default)('/v1/binaries'), _dec7 = (0, _allowUpload2.default)(), _dec8 = (0, _httpVerb2.default)('delete'), _dec9 = (0, _route2.default)('/v1/devices/:deviceID'), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices'), _dec12 = (0, _httpVerb2.default)('get'), _dec13 = (0, _route2.default)('/v1/devices/:deviceID'), _dec14 = (0, _httpVerb2.default)('get'), _dec15 = (0, _route2.default)('/v1/devices/:deviceID/:varName/'), _dec16 = (0, _httpVerb2.default)('put'), _dec17 = (0, _route2.default)('/v1/devices/:deviceID'), _dec18 = (0, _allowUpload2.default)('file', 1), _dec19 = (0, _httpVerb2.default)('post'), _dec20 = (0, _route2.default)('/v1/devices/:deviceID/:functionName'), _dec21 = (0, _httpVerb2.default)('put'), _dec22 = (0, _route2.default)('/v1/devices/:deviceID/ping'), (_class = function (_Controller) {
   (0, _inherits3.default)(DevicesController, _Controller);
 
   function DevicesController(deviceManager) {
@@ -126,7 +126,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   (0, _createClass3.default)(DevicesController, [{
     key: 'claimDevice',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(postBody) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(postBody) {
         var deviceID;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
@@ -156,7 +156,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'getAppFirmware',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(binaryID) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(binaryID) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
@@ -180,7 +180,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'compileSources',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(postBody) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(postBody) {
         var response;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
@@ -222,7 +222,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'unclaimDevice',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(deviceID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -250,7 +250,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'getDevices',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() {
         var devices;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
@@ -292,7 +292,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'getDevice',
     value: function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(deviceID) {
         var device;
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
@@ -322,7 +322,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'getVariableValue',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, varName) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(deviceID, varName) {
         var varValue, errorMessage;
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
@@ -368,7 +368,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'updateDevice',
     value: function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, postBody) {
+      var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(deviceID, postBody) {
         var updatedAttributes, flashResult, file, _flashResult;
 
         return _regenerator2.default.wrap(function _callee8$(_context8) {
@@ -465,7 +465,7 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
   }, {
     key: 'callDeviceFunction',
     value: function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) {
+      var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(deviceID, functionName, postBody) {
         var result, device, errorMessage;
         return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
@@ -513,7 +513,37 @@ var DevicesController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _ro
 
       return callDeviceFunction;
     }()
+  }, {
+    key: 'pingDevice',
+    value: function () {
+      var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(deviceID) {
+        return _regenerator2.default.wrap(function _callee10$(_context10) {
+          while (1) {
+            switch (_context10.prev = _context10.next) {
+              case 0:
+                _context10.t0 = this;
+                _context10.next = 3;
+                return this._deviceManager.ping(deviceID);
+
+              case 3:
+                _context10.t1 = _context10.sent;
+                return _context10.abrupt('return', _context10.t0.ok.call(_context10.t0, _context10.t1));
+
+              case 5:
+              case 'end':
+                return _context10.stop();
+            }
+          }
+        }, _callee10, this);
+      }));
+
+      function pingDevice(_x13) {
+        return _ref10.apply(this, arguments);
+      }
+
+      return pingDevice;
+    }()
   }]);
   return DevicesController;
-}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype)), _class));
+}(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'claimDevice', [_dec, _dec2], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'claimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getAppFirmware', [_dec3, _dec4], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getAppFirmware'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'compileSources', [_dec5, _dec6, _dec7], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'compileSources'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'unclaimDevice', [_dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'unclaimDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevices', [_dec10, _dec11], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevices'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDevice', [_dec12, _dec13], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getVariableValue', [_dec14, _dec15], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getVariableValue'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'updateDevice', [_dec16, _dec17, _dec18], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'updateDevice'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'callDeviceFunction', [_dec19, _dec20], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'callDeviceFunction'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'pingDevice', [_dec21, _dec22], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'pingDevice'), _class.prototype)), _class));
 exports.default = DevicesController;
\ No newline at end of file
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 32e1b30a..499bd5f7 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -50,7 +50,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   (0, _classCallCheck3.default)(this, DeviceManager);
 
   this.claimDevice = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(deviceID, userID) {
+    var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(deviceID, userID) {
       var attributes;
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
@@ -123,7 +123,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.unclaimDevice = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
+    var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(deviceID) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
@@ -161,7 +161,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.getAttributesByID = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(deviceID) {
+    var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(deviceID) {
       var _ref4, connected, attributes;
 
       return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -191,7 +191,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.getByID = function () {
-    var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+    var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(deviceID) {
       var connectedDeviceAttributes, attributes;
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
@@ -251,7 +251,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     };
   }();
 
-  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+  this.getAll = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() {
     var devicesAttributes, devicePromises;
     return _regenerator2.default.wrap(function _callee6$(_context6) {
       while (1) {
@@ -263,7 +263,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
           case 2:
             devicesAttributes = _context6.sent;
             devicePromises = devicesAttributes.map(function () {
-              var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(attributes) {
+              var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(attributes) {
                 var pingResponse;
                 return _regenerator2.default.wrap(function _callee5$(_context5) {
                   while (1) {
@@ -306,7 +306,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }));
 
   this.callFunction = function () {
-    var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceID, functionName, functionArguments) {
+    var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(deviceID, functionName, functionArguments) {
       var callFunctionResponse, error;
       return _regenerator2.default.wrap(function _callee7$(_context7) {
         while (1) {
@@ -350,7 +350,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.getVariableValue = function () {
-    var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(deviceID, variableName) {
+    var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(deviceID, variableName) {
       var getVariableResponse, error, result;
       return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
@@ -394,7 +394,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.flashBinary = function () {
-    var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(deviceID, file) {
+    var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(deviceID, file) {
       var flashResponse, error;
       return _regenerator2.default.wrap(function _callee9$(_context9) {
         while (1) {
@@ -438,7 +438,7 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
   }();
 
   this.flashKnownApp = function () {
-    var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(deviceID, appName) {
+    var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(deviceID, appName) {
       var knownFirmware, flashResponse, error;
       return _regenerator2.default.wrap(function _callee10$(_context10) {
         while (1) {
@@ -498,63 +498,95 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
     });
   };
 
-  this.provision = function () {
-    var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(deviceID, userID, publicKey, algorithm) {
-      var eccKey, createdKey;
+  this.ping = function () {
+    var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(deviceID) {
       return _regenerator2.default.wrap(function _callee11$(_context11) {
         while (1) {
           switch (_context11.prev = _context11.next) {
+            case 0:
+              _context11.next = 2;
+              return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
+
+            case 2:
+              _context11.next = 4;
+              return _this._eventPublisher.publishAndListenForResponse({
+                context: { deviceID: deviceID },
+                name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE
+              });
+
+            case 4:
+              return _context11.abrupt('return', _context11.sent);
+
+            case 5:
+            case 'end':
+              return _context11.stop();
+          }
+        }
+      }, _callee11, _this);
+    }));
+
+    return function (_x16) {
+      return _ref12.apply(this, arguments);
+    };
+  }();
+
+  this.provision = function () {
+    var _ref13 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(deviceID, userID, publicKey, algorithm) {
+      var eccKey, createdKey;
+      return _regenerator2.default.wrap(function _callee12$(_context12) {
+        while (1) {
+          switch (_context12.prev = _context12.next) {
             case 0:
               if (!(algorithm === 'ecc')) {
-                _context11.next = 12;
+                _context12.next = 12;
                 break;
               }
 
-              _context11.prev = 1;
+              _context12.prev = 1;
               eccKey = new _ecKey2.default(publicKey, 'pem');
 
               if (!eccKey.isPrivateECKey) {
-                _context11.next = 5;
+                _context12.next = 5;
                 break;
               }
 
               throw new _HttpError2.default('Not a public key');
 
             case 5:
-              _context11.next = 10;
+              _context12.next = 10;
               break;
 
             case 7:
-              _context11.prev = 7;
-              _context11.t0 = _context11['catch'](1);
-              throw new _HttpError2.default('Key error ' + _context11.t0);
+              _context12.prev = 7;
+              _context12.t0 = _context12['catch'](1);
+              throw new _HttpError2.default('Key error ' + _context12.t0);
 
             case 10:
-              _context11.next = 21;
+              _context12.next = 21;
               break;
 
             case 12:
-              _context11.prev = 12;
+              _context12.prev = 12;
               createdKey = new _nodeRsa2.default(publicKey);
 
               if (createdKey.isPublic()) {
-                _context11.next = 16;
+                _context12.next = 16;
                 break;
               }
 
               throw new _HttpError2.default('Not a public key');
 
             case 16:
-              _context11.next = 21;
+              _context12.next = 21;
               break;
 
             case 18:
-              _context11.prev = 18;
-              _context11.t1 = _context11['catch'](12);
-              throw new _HttpError2.default('Key error ' + _context11.t1);
+              _context12.prev = 18;
+              _context12.t1 = _context12['catch'](12);
+              throw new _HttpError2.default('Key error ' + _context12.t1);
 
             case 21:
-              _context11.next = 23;
+              _context12.next = 23;
               return _this._deviceKeyRepository.updateByID(deviceID, {
                 algorithm: algorithm,
                 deviceID: deviceID,
@@ -562,111 +594,111 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               });
 
             case 23:
-              _context11.next = 25;
+              _context12.next = 25;
               return _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: userID,
                 registrar: userID
               });
 
             case 25:
-              _context11.next = 27;
+              _context12.next = 27;
               return _this.getByID(deviceID);
 
             case 27:
-              return _context11.abrupt('return', _context11.sent);
+              return _context12.abrupt('return', _context12.sent);
 
             case 28:
             case 'end':
-              return _context11.stop();
+              return _context12.stop();
           }
         }
-      }, _callee11, _this, [[1, 7], [12, 18]]);
+      }, _callee12, _this, [[1, 7], [12, 18]]);
     }));
 
-    return function (_x16, _x17, _x18, _x19) {
-      return _ref12.apply(this, arguments);
+    return function (_x17, _x18, _x19, _x20) {
+      return _ref13.apply(this, arguments);
     };
   }();
 
   this.raiseYourHand = function () {
-    var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(deviceID, shouldShowSignal) {
+    var _ref14 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee13(deviceID, shouldShowSignal) {
       var raiseYourHandResponse, error;
-      return _regenerator2.default.wrap(function _callee12$(_context12) {
+      return _regenerator2.default.wrap(function _callee13$(_context13) {
         while (1) {
-          switch (_context12.prev = _context12.next) {
+          switch (_context13.prev = _context13.next) {
             case 0:
-              _context12.next = 2;
+              _context13.next = 2;
               return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              _context12.next = 4;
+              _context13.next = 4;
               return _this._eventPublisher.publishAndListenForResponse({
                 context: { deviceID: deviceID, shouldShowSignal: shouldShowSignal },
                 name: _sparkProtocol.SPARK_SERVER_EVENTS.RAISE_YOUR_HAND
               });
 
             case 4:
-              raiseYourHandResponse = _context12.sent;
+              raiseYourHandResponse = _context13.sent;
               error = raiseYourHandResponse.error;
 
               if (!error) {
-                _context12.next = 8;
+                _context13.next = 8;
                 break;
               }
 
               throw new _HttpError2.default(error);
 
             case 8:
-              return _context12.abrupt('return', raiseYourHandResponse);
+              return _context13.abrupt('return', raiseYourHandResponse);
 
             case 9:
             case 'end':
-              return _context12.stop();
+              return _context13.stop();
           }
         }
-      }, _callee12, _this);
+      }, _callee13, _this);
     }));
 
-    return function (_x20, _x21) {
-      return _ref13.apply(this, arguments);
+    return function (_x21, _x22) {
+      return _ref14.apply(this, arguments);
     };
   }();
 
   this.renameDevice = function () {
-    var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(deviceID, name) {
+    var _ref15 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee14(deviceID, name) {
       var attributes;
-      return _regenerator2.default.wrap(function _callee13$(_context13) {
+      return _regenerator2.default.wrap(function _callee14$(_context14) {
         while (1) {
-          switch (_context13.prev = _context13.next) {
+          switch (_context14.prev = _context14.next) {
             case 0:
-              _context13.next = 2;
+              _context14.next = 2;
               return _this.getAttributesByID(deviceID);
 
             case 2:
-              attributes = _context13.sent;
-              _context13.next = 5;
+              attributes = _context14.sent;
+              _context14.next = 5;
               return _this._eventPublisher.publishAndListenForResponse({
                 context: { attributes: { name: name }, deviceID: deviceID },
                 name: _sparkProtocol.SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES
               });
 
             case 5:
-              _context13.next = 7;
+              _context14.next = 7;
               return _this._deviceAttributeRepository.updateByID(deviceID, { name: name });
 
             case 7:
-              return _context13.abrupt('return', _context13.sent);
+              return _context14.abrupt('return', _context14.sent);
 
             case 8:
             case 'end':
-              return _context13.stop();
+              return _context14.stop();
           }
         }
-      }, _callee13, _this);
+      }, _callee14, _this);
     }));
 
-    return function (_x22, _x23) {
-      return _ref14.apply(this, arguments);
+    return function (_x23, _x24) {
+      return _ref15.apply(this, arguments);
     };
   }();
 
diff --git a/src/controllers/DevicesController.js b/src/controllers/DevicesController.js
index cb8410b6..d2f181f8 100644
--- a/src/controllers/DevicesController.js
+++ b/src/controllers/DevicesController.js
@@ -204,6 +204,12 @@ class DevicesController extends Controller {
       throw error;
     }
   }
+
+  @httpVerb('put')
+  @route('/v1/devices/:deviceID/ping')
+  async pingDevice(deviceID: string): Promise<*> {
+    return this.ok(await this._deviceManager.ping(deviceID));
+  }
 }
 
 export default DevicesController;
diff --git a/src/managers/DeviceManager.js b/src/managers/DeviceManager.js
index 23e5ac6c..56ca1d95 100644
--- a/src/managers/DeviceManager.js
+++ b/src/managers/DeviceManager.js
@@ -246,6 +246,17 @@ class DeviceManager {
       name: SPARK_SERVER_EVENTS.FLASH_PRODUCT_FIRMWARE,
     });
 
+  ping = async (deviceID: string): void => {
+    await this._permissionManager.checkPermissionsForEntityByID(
+      'deviceAttributes',
+      deviceID,
+    );
+    return await this._eventPublisher.publishAndListenForResponse({
+      context: { deviceID },
+      name: SPARK_SERVER_EVENTS.PING_DEVICE,
+    });
+  };
+
   provision = async (
     deviceID: string,
     userID: string,

From fba69edd5b47207eada48c7ba0d923d0aa685fae Mon Sep 17 00:00:00 2001
From: straccio 
Date: Tue, 5 Sep 2017 11:20:19 +0200
Subject: [PATCH 501/504] Create http server with timeout 0

Usefull for wathcing subscriptions (events)
---
 src/main.js | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/main.js b/src/main.js
index 8044733b..9aba7a9f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -65,7 +65,9 @@ if (useSSL) {
     .createServer(options, (app: any))
     .listen(NODE_PORT, onServerStartListen);
 } else {
-  http.createServer((app: any)).listen(NODE_PORT, onServerStartListen);
+  http
+    .createServer((app: any))
+    .listen(NODE_PORT, onServerStartListen).timeout = 0;
 }
 
 const addresses = arrayFlatten(
@@ -73,9 +75,9 @@ const addresses = arrayFlatten(
     // eslint-disable-next-line no-unused-vars
     ([name, nic]: [string, mixed]): Array =>
       (nic: any)
-       .filter((address: Object): boolean =>
-          address.family === 'IPv4' &&
-          address.address !== '127.0.0.1',
+        .filter(
+          (address: Object): boolean =>
+            address.family === 'IPv4' && address.address !== '127.0.0.1',
         )
         .map((address: Object): boolean => address.address),
   ),

From dda8817d70cad7f6516ca98529f16876db6fda7b Mon Sep 17 00:00:00 2001
From: straccio 
Date: Tue, 5 Sep 2017 23:01:15 +0200
Subject: [PATCH 502/504] Build

---
 dist/OAuthModel.js                            | 12 +--
 dist/RouteConfig.js                           |  2 +-
 dist/controllers/DeviceClaimsController.js    |  2 +-
 dist/controllers/EventsController.js          | 10 +-
 dist/controllers/OauthClientsController.js    |  6 +-
 dist/controllers/ProductsController.js        | 38 ++++----
 dist/controllers/ProvisioningController.js    |  2 +-
 dist/controllers/UsersController.js           |  6 +-
 dist/controllers/WebhooksController.js        |  8 +-
 dist/main.js                                  |  2 +-
 dist/managers/DeviceManager.js                | 80 ++++++----------
 dist/managers/FirmwareCompilationManager.js   |  2 +-
 dist/managers/PermissionManager.js            | 30 +++---
 dist/managers/WebhookManager.js               | 30 +++---
 dist/repository/BaseRepository.js             | 10 +-
 .../DeviceAttributeDatabaseRepository.js      | 28 ++----
 .../repository/DeviceKeyDatabaseRepository.js | 44 +++------
 dist/repository/MongoDb.js                    | 76 +++++----------
 dist/repository/NeDb.js                       | 92 +++++++------------
 .../OrganizationDatabaseRepository.js         | 54 ++++-------
 .../ProductConfigDatabaseRepository.js        | 54 ++++-------
 dist/repository/ProductDatabaseRepository.js  | 50 +++-------
 .../ProductDeviceDatabaseRepository.js        | 88 ++++++------------
 .../ProductFirmwareDatabaseRepository.js      | 28 ++----
 dist/repository/UserDatabaseRepository.js     | 80 ++++++----------
 dist/repository/UserFileRepository.js         | 71 +++++---------
 dist/repository/WebhookDatabaseRepository.js  | 44 +++------
 dist/repository/WebhookFileRepository.js      | 31 ++-----
 dist/scripts/migrateFilesToDatabase.js        | 18 ++--
 29 files changed, 336 insertions(+), 662 deletions(-)

diff --git a/dist/OAuthModel.js b/dist/OAuthModel.js
index 045c8ddb..446f2fb9 100644
--- a/dist/OAuthModel.js
+++ b/dist/OAuthModel.js
@@ -34,7 +34,7 @@ var OauthModel = function OauthModel(userRepository) {
   (0, _classCallCheck3.default)(this, OauthModel);
 
   this.getAccessToken = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(bearerToken) {
+    var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(bearerToken) {
       var user, userTokenObject;
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
@@ -90,18 +90,14 @@ var OauthModel = function OauthModel(userRepository) {
   };
 
   this.getUser = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(username, password) {
+    var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(username, password) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
-              _context2.next = 2;
-              return _this._userRepository.validateLogin(username, password);
+              return _context2.abrupt('return', _this._userRepository.validateLogin(username, password));
 
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
-
-            case 3:
+            case 1:
             case 'end':
               return _context2.stop();
           }
diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js
index 5482cfea..022edfa7 100644
--- a/dist/RouteConfig.js
+++ b/dist/RouteConfig.js
@@ -116,7 +116,7 @@ exports.default = function (app, container, controllers, settings) {
         return;
       }
       app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(container), maybe(filesMiddleware(allowedUploads), allowedUploads), function () {
-        var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) {
+        var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(request, response) {
           var argumentNames, values, controllerInstance, _request$body, access_token, body, functionResult, result, httpError;
 
           return _regenerator2.default.wrap(function _callee$(_context) {
diff --git a/dist/controllers/DeviceClaimsController.js b/dist/controllers/DeviceClaimsController.js
index bf93e676..d31423fe 100644
--- a/dist/controllers/DeviceClaimsController.js
+++ b/dist/controllers/DeviceClaimsController.js
@@ -97,7 +97,7 @@ var DeviceClaimsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
   (0, _createClass3.default)(DeviceClaimsController, [{
     key: 'createClaimCode',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
         var claimCode, devices, deviceIDs;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js
index d789b73b..b4c46016 100644
--- a/dist/controllers/EventsController.js
+++ b/dist/controllers/EventsController.js
@@ -154,7 +154,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'ping',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(payload) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(payload) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
@@ -180,7 +180,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'getEvents',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(eventNamePrefix) {
         var subscriptionID;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
@@ -210,7 +210,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'getMyEvents',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(eventNamePrefix) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(eventNamePrefix) {
         var subscriptionID;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
@@ -243,7 +243,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'getDeviceEvents',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, eventNamePrefix) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(deviceID, eventNamePrefix) {
         var subscriptionID;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
@@ -276,7 +276,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'publish',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(postBody) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(postBody) {
         var eventData;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
diff --git a/dist/controllers/OauthClientsController.js b/dist/controllers/OauthClientsController.js
index 145943b9..5ced9bc8 100644
--- a/dist/controllers/OauthClientsController.js
+++ b/dist/controllers/OauthClientsController.js
@@ -96,7 +96,7 @@ var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
   (0, _createClass3.default)(OauthClientsController, [{
     key: 'createClient',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
@@ -120,7 +120,7 @@ var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
   }, {
     key: 'editClient',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
@@ -144,7 +144,7 @@ var OauthClientsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
   }, {
     key: 'deleteClient',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
             switch (_context3.prev = _context3.next) {
diff --git a/dist/controllers/ProductsController.js b/dist/controllers/ProductsController.js
index cac78708..99864243 100644
--- a/dist/controllers/ProductsController.js
+++ b/dist/controllers/ProductsController.js
@@ -141,7 +141,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   (0, _createClass3.default)(ProductsController, [{
     key: 'getProducts',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
         var products;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
@@ -171,7 +171,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'createProduct',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(model) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(model) {
         var missingFields, organizations, organizationID, product, config;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
@@ -252,7 +252,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getProduct',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(productIDOrSlug) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(productIDOrSlug) {
         var product;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
@@ -291,7 +291,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'updateProduct',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productIDOrSlug, model) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(productIDOrSlug, model) {
         var missingFields, product;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
@@ -355,7 +355,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'deleteProduct',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(productIDOrSlug) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(productIDOrSlug) {
         var product;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
@@ -398,7 +398,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getConfig',
     value: function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(productIDOrSlug) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(productIDOrSlug) {
         var product, config;
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
@@ -419,7 +419,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
 
               case 5:
                 _context6.next = 7;
-                return this._productConfigRepository.getByProductID(product.id);
+                return this._productConfigRepository.getByProductID(product.product_id);
 
               case 7:
                 config = _context6.sent;
@@ -442,7 +442,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getFirmware',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(productIDOrSlug) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(productIDOrSlug) {
         var product, firmwares;
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
@@ -490,7 +490,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getSingleFirmware',
     value: function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(productIDOrSlug, version) {
+      var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(productIDOrSlug, version) {
         var product, firmwareList, existingFirmware, data, id, output;
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
@@ -547,7 +547,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'addFirmware',
     value: function () {
-      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productIDOrSlug, body) {
+      var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(productIDOrSlug, body) {
         var missingFields, product, parser, moduleInfo, firmwarePlatformID, _moduleInfo$suffixInf, productId, productVersion, version, firmwareList, maxExistingFirmwareVersion, firmware, data, id, output;
 
         return _regenerator2.default.wrap(function _callee9$(_context9) {
@@ -679,7 +679,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'updateFirmware',
     value: function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(productIDOrSlug, version, body) {
+      var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(productIDOrSlug, version, body) {
         var _body, current, description, title, product, firmwareList, existingFirmware, firmware, data, id, output;
 
         return _regenerator2.default.wrap(function _callee10$(_context10) {
@@ -754,7 +754,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'deleteFirmware',
     value: function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(productIDOrSlug, version) {
+      var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(productIDOrSlug, version) {
         var product, firmwareList, existingFirmware;
         return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
@@ -814,7 +814,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getDevices',
     value: function () {
-      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(productIDOrSlug, query) {
+      var _ref13 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(productIDOrSlug, query) {
         var product, page, _query$per_page, per_page, totalDevices, productDevices, deviceIDs, devices;
 
         return _regenerator2.default.wrap(function _callee12$(_context12) {
@@ -898,7 +898,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getSingleDevice',
     value: function () {
-      var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(productIDOrSlug, deviceID) {
+      var _ref14 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee13(productIDOrSlug, deviceID) {
         var product, deviceAttributes, productDevice, denied, development, quarantined;
         return _regenerator2.default.wrap(function _callee13$(_context13) {
           while (1) {
@@ -971,7 +971,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'addDevice',
     value: function () {
-      var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14(productIDOrSlug, body) {
+      var _ref15 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee14(productIDOrSlug, body) {
         var _this2 = this;
 
         var product, ids, _file, originalname, records, deviceAttributes, incorrectPlatformDeviceIDs, deviceAttributeIDs, existingProductDeviceIDs, invalidDeviceIds, nonmemberDeviceIds, idsToCreate;
@@ -1151,7 +1151,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'updateProductDevice',
     value: function () {
-      var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(productIDOrSlug, deviceID, _ref17) {
+      var _ref16 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee15(productIDOrSlug, deviceID, _ref17) {
         var denied = _ref17.denied,
             desired_firmware_version = _ref17.desired_firmware_version,
             development = _ref17.development,
@@ -1278,7 +1278,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'removeDeviceFromProduct',
     value: function () {
-      var _ref18 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee16(productIDOrSlug, deviceID) {
+      var _ref18 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee16(productIDOrSlug, deviceID) {
         var product, deviceAttributes, productDevice;
         return _regenerator2.default.wrap(function _callee16$(_context16) {
           while (1) {
@@ -1349,7 +1349,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getEvents',
     value: function () {
-      var _ref19 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee17(productIdOrSlug, eventName) {
+      var _ref19 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee17(productIdOrSlug, eventName) {
         return _regenerator2.default.wrap(function _callee17$(_context17) {
           while (1) {
             switch (_context17.prev = _context17.next) {
@@ -1373,7 +1373,7 @@ var ProductsController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'removeTeamMember',
     value: function () {
-      var _ref20 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee18(productIdOrSlug, username) {
+      var _ref20 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee18(productIdOrSlug, username) {
         return _regenerator2.default.wrap(function _callee18$(_context18) {
           while (1) {
             switch (_context18.prev = _context18.next) {
diff --git a/dist/controllers/ProvisioningController.js b/dist/controllers/ProvisioningController.js
index e5f7665b..a8ba4af7 100644
--- a/dist/controllers/ProvisioningController.js
+++ b/dist/controllers/ProvisioningController.js
@@ -104,7 +104,7 @@ var ProvisioningController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0
   (0, _createClass3.default)(ProvisioningController, [{
     key: 'provision',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(coreID, postBody) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(coreID, postBody) {
         var device;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
diff --git a/dist/controllers/UsersController.js b/dist/controllers/UsersController.js
index 6b9179d4..a0d70dca 100644
--- a/dist/controllers/UsersController.js
+++ b/dist/controllers/UsersController.js
@@ -108,7 +108,7 @@ var UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rout
   (0, _createClass3.default)(UsersController, [{
     key: 'createUser',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(userCredentials) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(userCredentials) {
         var isUserNameInUse;
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
@@ -157,7 +157,7 @@ var UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rout
   }, {
     key: 'deleteAccessToken',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(token) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(token) {
         var _basicAuthParser, username, password, user;
 
         return _regenerator2.default.wrap(function _callee2$(_context2) {
@@ -193,7 +193,7 @@ var UsersController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rout
   }, {
     key: 'getAccessTokens',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var _basicAuthParser2, username, password, user;
 
         return _regenerator2.default.wrap(function _callee3$(_context3) {
diff --git a/dist/controllers/WebhooksController.js b/dist/controllers/WebhooksController.js
index d04540cc..59e738b0 100644
--- a/dist/controllers/WebhooksController.js
+++ b/dist/controllers/WebhooksController.js
@@ -118,7 +118,7 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   (0, _createClass3.default)(WebhooksController, [{
     key: 'getAll',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
@@ -148,7 +148,7 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'getByID',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(webhookID) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
@@ -178,7 +178,7 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'create',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(model) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(model) {
         var validateError, newWebhook;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
@@ -226,7 +226,7 @@ var WebhooksController = (_dec = (0, _httpVerb2.default)('get'), _dec2 = (0, _ro
   }, {
     key: 'deleteByID',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(webhookID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
diff --git a/dist/main.js b/dist/main.js
index 38d02615..85b89e1b 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -107,7 +107,7 @@ if (useSSL) {
   }, expressConfig);
   _https2.default.createServer(options, app).listen(NODE_PORT, onServerStartListen);
 } else {
-  _http2.default.createServer(app).listen(NODE_PORT, onServerStartListen);
+  _http2.default.createServer(app).listen(NODE_PORT, onServerStartListen).timeout = 0;
 }
 
 var addresses = (0, _arrayFlatten2.default)((0, _entries2.default)(_os2.default.networkInterfaces()).map(
diff --git a/dist/managers/DeviceManager.js b/dist/managers/DeviceManager.js
index 499bd5f7..4ddf9ea6 100644
--- a/dist/managers/DeviceManager.js
+++ b/dist/managers/DeviceManager.js
@@ -63,53 +63,45 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               attributes = _context.sent;
 
               if (attributes) {
-                _context.next = 7;
+                _context.next = 5;
                 break;
               }
 
-              _context.next = 6;
-              return _this._deviceAttributeRepository.updateByID(deviceID, {
+              return _context.abrupt('return', _this._deviceAttributeRepository.updateByID(deviceID, {
                 deviceID: deviceID,
                 ownerID: userID,
                 registrar: userID
-              });
-
-            case 6:
-              return _context.abrupt('return', _context.sent);
+              }));
 
-            case 7:
+            case 5:
               if (!(attributes.ownerID && attributes.ownerID !== userID)) {
-                _context.next = 9;
+                _context.next = 7;
                 break;
               }
 
               throw new _HttpError2.default('The device belongs to someone else.');
 
-            case 9:
+            case 7:
               if (!(attributes.ownerID && attributes.ownerID === userID)) {
-                _context.next = 11;
+                _context.next = 9;
                 break;
               }
 
               throw new _HttpError2.default('The device is already claimed.');
 
-            case 11:
-              _context.next = 13;
+            case 9:
+              _context.next = 11;
               return _this._eventPublisher.publishAndListenForResponse({
                 context: { attributes: { ownerID: userID }, deviceID: deviceID },
                 name: _sparkProtocol.SPARK_SERVER_EVENTS.UPDATE_DEVICE_ATTRIBUTES
               });
 
-            case 13:
-              _context.next = 15;
-              return _this._deviceAttributeRepository.updateByID(deviceID, {
+            case 11:
+              return _context.abrupt('return', _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: userID
-              });
-
-            case 15:
-              return _context.abrupt('return', _context.sent);
+              }));
 
-            case 16:
+            case 12:
             case 'end':
               return _context.stop();
           }
@@ -139,15 +131,11 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               });
 
             case 4:
-              _context2.next = 6;
-              return _this._deviceAttributeRepository.updateByID(deviceID, {
+              return _context2.abrupt('return', _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: null
-              });
-
-            case 6:
-              return _context2.abrupt('return', _context2.sent);
+              }));
 
-            case 7:
+            case 5:
             case 'end':
               return _context2.stop();
           }
@@ -508,16 +496,12 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               return _this._permissionManager.checkPermissionsForEntityByID('deviceAttributes', deviceID);
 
             case 2:
-              _context11.next = 4;
-              return _this._eventPublisher.publishAndListenForResponse({
+              return _context11.abrupt('return', _this._eventPublisher.publishAndListenForResponse({
                 context: { deviceID: deviceID },
                 name: _sparkProtocol.SPARK_SERVER_EVENTS.PING_DEVICE
-              });
-
-            case 4:
-              return _context11.abrupt('return', _context11.sent);
+              }));
 
-            case 5:
+            case 3:
             case 'end':
               return _context11.stop();
           }
@@ -586,28 +570,20 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               throw new _HttpError2.default('Key error ' + _context12.t1);
 
             case 21:
-              _context12.next = 23;
-              return _this._deviceKeyRepository.updateByID(deviceID, {
+
+              _this._deviceKeyRepository.updateByID(deviceID, {
                 algorithm: algorithm,
                 deviceID: deviceID,
                 key: publicKey
               });
 
-            case 23:
-              _context12.next = 25;
-              return _this._deviceAttributeRepository.updateByID(deviceID, {
+              _this._deviceAttributeRepository.updateByID(deviceID, {
                 ownerID: userID,
                 registrar: userID
               });
+              return _context12.abrupt('return', _this.getByID(deviceID));
 
-            case 25:
-              _context12.next = 27;
-              return _this.getByID(deviceID);
-
-            case 27:
-              return _context12.abrupt('return', _context12.sent);
-
-            case 28:
+            case 24:
             case 'end':
               return _context12.stop();
           }
@@ -683,13 +659,9 @@ var DeviceManager = function DeviceManager(deviceAttributeRepository, deviceFirm
               });
 
             case 5:
-              _context14.next = 7;
-              return _this._deviceAttributeRepository.updateByID(deviceID, { name: name });
-
-            case 7:
-              return _context14.abrupt('return', _context14.sent);
+              return _context14.abrupt('return', _this._deviceAttributeRepository.updateByID(deviceID, { name: name }));
 
-            case 8:
+            case 6:
             case 'end':
               return _context14.stop();
           }
diff --git a/dist/managers/FirmwareCompilationManager.js b/dist/managers/FirmwareCompilationManager.js
index ee9adc18..4d5aafda 100644
--- a/dist/managers/FirmwareCompilationManager.js
+++ b/dist/managers/FirmwareCompilationManager.js
@@ -114,7 +114,7 @@ FirmwareCompilationManager.getBinaryForID = function (id) {
 };
 
 FirmwareCompilationManager.compileSource = function () {
-  var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(platformID, files) {
+  var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(platformID, files) {
     var platformName, appFolder, appPath, id, binPath, makeProcess, errors, sizeInfo, date, config;
     return _regenerator2.default.wrap(function _callee$(_context) {
       while (1) {
diff --git a/dist/managers/PermissionManager.js b/dist/managers/PermissionManager.js
index 4d3d9895..df2abd58 100644
--- a/dist/managers/PermissionManager.js
+++ b/dist/managers/PermissionManager.js
@@ -49,7 +49,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
   this._repositoriesByEntityName = new _map2.default();
 
   this.checkPermissionsForEntityByID = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(entityName, id) {
+    var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(entityName, id) {
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -74,20 +74,16 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
   }();
 
   this.getAllEntitiesForCurrentUser = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(entityName) {
+    var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(entityName) {
       var currentUser;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
               currentUser = _this._userRepository.getCurrentUser();
-              _context2.next = 3;
-              return (0, _nullthrows2.default)(_this._repositoriesByEntityName.get(entityName)).getAll(currentUser.id);
+              return _context2.abrupt('return', (0, _nullthrows2.default)(_this._repositoriesByEntityName.get(entityName)).getAll(currentUser.id));
 
-            case 3:
-              return _context2.abrupt('return', _context2.sent);
-
-            case 4:
+            case 2:
             case 'end':
               return _context2.stop();
           }
@@ -101,7 +97,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
   }();
 
   this.getEntityByID = function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(entityName, id) {
+    var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(entityName, id) {
       var entity;
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
@@ -144,7 +140,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
     };
   }();
 
-  this._createDefaultAdminUser = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4() {
+  this._createDefaultAdminUser = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4() {
     var token;
     return _regenerator2.default.wrap(function _callee4$(_context4) {
       while (1) {
@@ -190,7 +186,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
     return currentUser.role === 'administrator' || currentUser.id === ownerID;
   };
 
-  this._generateAdminToken = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+  this._generateAdminToken = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() {
     var request, response, tokenPayload;
     return _regenerator2.default.wrap(function _callee5$(_context5) {
       while (1) {
@@ -229,7 +225,7 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
       }
     }, _callee5, _this);
   }));
-  this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+  this._init = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() {
     var defaultAdminUser, organizations;
     return _regenerator2.default.wrap(function _callee6$(_context6) {
       while (1) {
@@ -293,18 +289,14 @@ var PermissionManager = function PermissionManager(deviceAttributeRepository, or
   this._repositoriesByEntityName.set('webhook', webhookRepository);
   this._oauthServer = oauthServer;
 
-  (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
+  (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7() {
     return _regenerator2.default.wrap(function _callee7$(_context7) {
       while (1) {
         switch (_context7.prev = _context7.next) {
           case 0:
-            _context7.next = 2;
-            return _this._init();
+            return _context7.abrupt('return', _this._init());
 
-          case 2:
-            return _context7.abrupt('return', _context7.sent);
-
-          case 3:
+          case 1:
           case 'end':
             return _context7.stop();
         }
diff --git a/dist/managers/WebhookManager.js b/dist/managers/WebhookManager.js
index a2bf6865..6c1b8c71 100644
--- a/dist/managers/WebhookManager.js
+++ b/dist/managers/WebhookManager.js
@@ -112,7 +112,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
   this._errorsCountByWebhookID = new _map2.default();
 
   this.create = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+    var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
       var webhook;
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
@@ -141,7 +141,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
   }();
 
   this.deleteByID = function () {
-    var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(webhookID) {
+    var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(webhookID) {
       var webhook;
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
@@ -180,18 +180,14 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
     };
   }();
 
-  this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+  this.getAll = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
     return _regenerator2.default.wrap(function _callee3$(_context3) {
       while (1) {
         switch (_context3.prev = _context3.next) {
           case 0:
-            _context3.next = 2;
-            return _this._permissonManager.getAllEntitiesForCurrentUser('webhook');
+            return _context3.abrupt('return', _this._permissonManager.getAllEntitiesForCurrentUser('webhook'));
 
-          case 2:
-            return _context3.abrupt('return', _context3.sent);
-
-          case 3:
+          case 1:
           case 'end':
             return _context3.stop();
         }
@@ -200,7 +196,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
   }));
 
   this.getByID = function () {
-    var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(webhookID) {
+    var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(webhookID) {
       var webhook;
       return _regenerator2.default.wrap(function _callee4$(_context4) {
         while (1) {
@@ -235,7 +231,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
     };
   }();
 
-  this._init = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+  this._init = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() {
     var allWebhooks;
     return _regenerator2.default.wrap(function _callee5$(_context5) {
       while (1) {
@@ -306,7 +302,7 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
   };
 
   this.runWebhook = function () {
-    var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(webhook, event) {
+    var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(webhook, event) {
       var webhookVariablesObject, requestAuth, requestJson, requestFormData, requestHeaders, requestUrl, requestQuery, responseTopic, requestType, isGetRequest, requestOptions, _responseBody, isResponseBodyAnObject, responseTemplate, responseEventData, chunks;
 
       return _regenerator2.default.wrap(function _callee6$(_context6) {
@@ -497,18 +493,14 @@ var WebhookManager = function WebhookManager(eventPublisher, permissionManager,
   this._webhookLogger = webhookLogger;
   this._webhookRepository = webhookRepository;
 
-  (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
+  (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7() {
     return _regenerator2.default.wrap(function _callee7$(_context7) {
       while (1) {
         switch (_context7.prev = _context7.next) {
           case 0:
-            _context7.next = 2;
-            return _this._init();
-
-          case 2:
-            return _context7.abrupt('return', _context7.sent);
+            return _context7.abrupt('return', _this._init());
 
-          case 3:
+          case 1:
           case 'end':
             return _context7.stop();
         }
diff --git a/dist/repository/BaseRepository.js b/dist/repository/BaseRepository.js
index b4c365b0..2536c624 100644
--- a/dist/repository/BaseRepository.js
+++ b/dist/repository/BaseRepository.js
@@ -28,7 +28,7 @@ var BaseRepository = function BaseRepository(database, collectionName) {
   (0, _classCallCheck3.default)(this, BaseRepository);
 
   this.count = function () {
-    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+    var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
       var _database;
 
       for (var _len = arguments.length, filters = Array(_len), _key = 0; _key < _len; _key++) {
@@ -39,13 +39,9 @@ var BaseRepository = function BaseRepository(database, collectionName) {
         while (1) {
           switch (_context.prev = _context.next) {
             case 0:
-              _context.next = 2;
-              return (_database = _this._database).count.apply(_database, [_this._collectionName].concat((0, _toConsumableArray3.default)(filters.length ? filters : [{}])));
+              return _context.abrupt('return', (_database = _this._database).count.apply(_database, [_this._collectionName].concat((0, _toConsumableArray3.default)(filters.length ? filters : [{}]))));
 
-            case 2:
-              return _context.abrupt('return', _context.sent);
-
-            case 3:
+            case 1:
             case 'end':
               return _context.stop();
           }
diff --git a/dist/repository/DeviceAttributeDatabaseRepository.js b/dist/repository/DeviceAttributeDatabaseRepository.js
index a20ed378..f9c05aad 100644
--- a/dist/repository/DeviceAttributeDatabaseRepository.js
+++ b/dist/repository/DeviceAttributeDatabaseRepository.js
@@ -62,7 +62,7 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
     var _this = (0, _possibleConstructorReturn3.default)(this, (DeviceAttributeDatabaseRepository.__proto__ || (0, _getPrototypeOf2.default)(DeviceAttributeDatabaseRepository)).call(this, database, _collectionNames2.default.DEVICE_ATTRIBUTES));
 
     _this._collectionName = _collectionNames2.default.DEVICE_ATTRIBUTES;
-    _this.create = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+    _this.create = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -78,18 +78,14 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
     }));
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(deviceID) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { deviceID: deviceID });
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { deviceID: deviceID }));
 
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -103,7 +99,7 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAll = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var query;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -132,7 +128,7 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(deviceID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -159,7 +155,7 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getManyFromIDs = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceIDs, ownerID) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(deviceIDs, ownerID) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
@@ -187,7 +183,7 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.updateByID = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID, _ref7) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(deviceID, _ref7) {
         var variables = _ref7.variables,
             props = (0, _objectWithoutProperties3.default)(_ref7, ['variables']);
         var attributesToSave;
@@ -196,13 +192,9 @@ var DeviceAttributeDatabaseRepository = function (_BaseRepository) {
             switch (_context6.prev = _context6.next) {
               case 0:
                 attributesToSave = (0, _extends3.default)({}, props, variables ? { variables: (0, _stringify2.default)(variables) } : {});
-                _context6.next = 3;
-                return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, attributesToSave, { timestamp: new Date() }) });
+                return _context6.abrupt('return', _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, attributesToSave, { timestamp: new Date() }) }));
 
-              case 3:
-                return _context6.abrupt('return', _context6.sent);
-
-              case 4:
+              case 2:
               case 'end':
                 return _context6.stop();
             }
diff --git a/dist/repository/DeviceKeyDatabaseRepository.js b/dist/repository/DeviceKeyDatabaseRepository.js
index 5747b222..02b89498 100644
--- a/dist/repository/DeviceKeyDatabaseRepository.js
+++ b/dist/repository/DeviceKeyDatabaseRepository.js
@@ -56,20 +56,16 @@ var DeviceKeyDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.DEVICE_KEYS;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
-                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({
+                return _context.abrupt('return', _this._database.insertOne(_this._collectionName, (0, _extends3.default)({
                   _id: model.deviceID
-                }, model));
+                }, model)));
 
-              case 2:
-                return _context.abrupt('return', _context.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -83,18 +79,14 @@ var DeviceKeyDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(deviceID) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(deviceID) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { deviceID: deviceID });
-
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { deviceID: deviceID }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -107,7 +99,7 @@ var DeviceKeyDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+    _this.getAll = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
@@ -123,18 +115,14 @@ var DeviceKeyDatabaseRepository = function (_BaseRepository) {
     }));
 
     _this.getByID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(deviceID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this._database.findOne(_this._collectionName, { deviceID: deviceID });
+                return _context4.abrupt('return', _this._database.findOne(_this._collectionName, { deviceID: deviceID }));
 
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -148,18 +136,14 @@ var DeviceKeyDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.updateByID = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(deviceID, props) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(deviceID, props) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                _context5.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, props) });
-
-              case 2:
-                return _context5.abrupt('return', _context5.sent);
+                return _context5.abrupt('return', _this._database.findAndModify(_this._collectionName, { deviceID: deviceID }, { $set: (0, _extends3.default)({}, props) }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context5.stop();
             }
diff --git a/dist/repository/MongoDb.js b/dist/repository/MongoDb.js
index 97cc6dbc..21113b69 100644
--- a/dist/repository/MongoDb.js
+++ b/dist/repository/MongoDb.js
@@ -74,13 +74,9 @@ var MongoDb = function (_BaseMongoDb) {
         while (1) {
           switch (_context.prev = _context.next) {
             case 0:
-              _context.next = 2;
-              return _this._init(url, options);
+              return _context.abrupt('return', _this._init(url, options));
 
-            case 2:
-              return _context.abrupt('return', _context.sent);
-
-            case 3:
+            case 1:
             case 'end':
               return _context.stop();
           }
@@ -113,15 +109,11 @@ var _initialiseProps = function _initialiseProps() {
                     while (1) {
                       switch (_context2.prev = _context2.next) {
                         case 0:
-                          _context2.next = 2;
-                          return collection.count(_this3.__translateQuery(query), {
+                          return _context2.abrupt('return', collection.count(_this3.__translateQuery(query), {
                             timeout: false
-                          });
+                          }));
 
-                        case 2:
-                          return _context2.abrupt('return', _context2.sent);
-
-                        case 3:
+                        case 1:
                         case 'end':
                           return _context2.stop();
                       }
@@ -166,8 +158,7 @@ var _initialiseProps = function _initialiseProps() {
         while (1) {
           switch (_context5.prev = _context5.next) {
             case 0:
-              _context5.next = 2;
-              return _this3.__runForCollection(collectionName, function () {
+              return _context5.abrupt('return', _this3.__runForCollection(collectionName, function () {
                 var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(collection) {
                   var insertResult;
                   return _regenerator2.default.wrap(function _callee4$(_context4) {
@@ -192,12 +183,9 @@ var _initialiseProps = function _initialiseProps() {
                 return function (_x7) {
                   return _ref5.apply(this, arguments);
                 };
-              }());
+              }()));
 
-            case 2:
-              return _context5.abrupt('return', _context5.sent);
-
-            case 3:
+            case 1:
             case 'end':
               return _context5.stop();
           }
@@ -216,8 +204,7 @@ var _initialiseProps = function _initialiseProps() {
         while (1) {
           switch (_context7.prev = _context7.next) {
             case 0:
-              _context7.next = 2;
-              return _this3.__runForCollection(collectionName, function () {
+              return _context7.abrupt('return', _this3.__runForCollection(collectionName, function () {
                 var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(collection) {
                   var page, _query$pageSize, pageSize, otherQuery, result, resultItems;
 
@@ -253,12 +240,9 @@ var _initialiseProps = function _initialiseProps() {
                 return function (_x10) {
                   return _ref7.apply(this, arguments);
                 };
-              }());
+              }()));
 
-            case 2:
-              return _context7.abrupt('return', _context7.sent);
-
-            case 3:
+            case 1:
             case 'end':
               return _context7.stop();
           }
@@ -277,8 +261,7 @@ var _initialiseProps = function _initialiseProps() {
         while (1) {
           switch (_context9.prev = _context9.next) {
             case 0:
-              _context9.next = 2;
-              return _this3.__runForCollection(collectionName, function () {
+              return _context9.abrupt('return', _this3.__runForCollection(collectionName, function () {
                 var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(collection) {
                   var modifyResult;
                   return _regenerator2.default.wrap(function _callee8$(_context8) {
@@ -303,12 +286,9 @@ var _initialiseProps = function _initialiseProps() {
                 return function (_x14) {
                   return _ref9.apply(this, arguments);
                 };
-              }());
-
-            case 2:
-              return _context9.abrupt('return', _context9.sent);
+              }()));
 
-            case 3:
+            case 1:
             case 'end':
               return _context9.stop();
           }
@@ -327,8 +307,7 @@ var _initialiseProps = function _initialiseProps() {
         while (1) {
           switch (_context11.prev = _context11.next) {
             case 0:
-              _context11.next = 2;
-              return _this3.__runForCollection(collectionName, function () {
+              return _context11.abrupt('return', _this3.__runForCollection(collectionName, function () {
                 var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(collection) {
                   var resultItem;
                   return _regenerator2.default.wrap(function _callee10$(_context10) {
@@ -353,12 +332,9 @@ var _initialiseProps = function _initialiseProps() {
                 return function (_x17) {
                   return _ref11.apply(this, arguments);
                 };
-              }());
-
-            case 2:
-              return _context11.abrupt('return', _context11.sent);
+              }()));
 
-            case 3:
+            case 1:
             case 'end':
               return _context11.stop();
           }
@@ -377,20 +353,15 @@ var _initialiseProps = function _initialiseProps() {
         while (1) {
           switch (_context13.prev = _context13.next) {
             case 0:
-              _context13.next = 2;
-              return _this3.__runForCollection(collectionName, function () {
+              return _context13.abrupt('return', _this3.__runForCollection(collectionName, function () {
                 var _ref13 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(collection) {
                   return _regenerator2.default.wrap(function _callee12$(_context12) {
                     while (1) {
                       switch (_context12.prev = _context12.next) {
                         case 0:
-                          _context12.next = 2;
-                          return collection.remove(_this3.__translateQuery(query));
-
-                        case 2:
-                          return _context12.abrupt('return', _context12.sent);
+                          return _context12.abrupt('return', collection.remove(_this3.__translateQuery(query)));
 
-                        case 3:
+                        case 1:
                         case 'end':
                           return _context12.stop();
                       }
@@ -401,12 +372,9 @@ var _initialiseProps = function _initialiseProps() {
                 return function (_x20) {
                   return _ref13.apply(this, arguments);
                 };
-              }());
-
-            case 2:
-              return _context13.abrupt('return', _context13.sent);
+              }()));
 
-            case 3:
+            case 1:
             case 'end':
               return _context13.stop();
           }
diff --git a/dist/repository/NeDb.js b/dist/repository/NeDb.js
index e2b7083d..b357a5d7 100644
--- a/dist/repository/NeDb.js
+++ b/dist/repository/NeDb.js
@@ -75,25 +75,21 @@ var NeDb = function (_BaseMongoDb) {
     var _this = (0, _possibleConstructorReturn3.default)(this, (NeDb.__proto__ || (0, _getPrototypeOf2.default)(NeDb)).call(this));
 
     _this.count = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(collectionName, query) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(collectionName, query) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
                 _context2.next = 2;
                 return _this.__runForCollection(collectionName, function () {
-                  var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(collection) {
+                  var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(collection) {
                     return _regenerator2.default.wrap(function _callee$(_context) {
                       while (1) {
                         switch (_context.prev = _context.next) {
                           case 0:
-                            _context.next = 2;
-                            return (0, _promisify.promisify)(collection, 'count', query);
+                            return _context.abrupt('return', (0, _promisify.promisify)(collection, 'count', query));
 
-                          case 2:
-                            return _context.abrupt('return', _context.sent);
-
-                          case 3:
+                          case 1:
                           case 'end':
                             return _context.stop();
                         }
@@ -133,14 +129,13 @@ var NeDb = function (_BaseMongoDb) {
     }();
 
     _this.insertOne = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(collectionName, entity) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(collectionName, entity) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(collection) {
+                return _context4.abrupt('return', _this.__runForCollection(collectionName, function () {
+                  var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(collection) {
                     var insertResult;
                     return _regenerator2.default.wrap(function _callee3$(_context3) {
                       while (1) {
@@ -164,12 +159,9 @@ var NeDb = function (_BaseMongoDb) {
                   return function (_x6) {
                     return _ref4.apply(this, arguments);
                   };
-                }());
+                }()));
 
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -183,14 +175,13 @@ var NeDb = function (_BaseMongoDb) {
     }();
 
     _this.find = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(collectionName, query) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(collectionName, query) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
-                _context6.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(collection) {
+                return _context6.abrupt('return', _this.__runForCollection(collectionName, function () {
+                  var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(collection) {
                     var page, _query$pageSize, pageSize, otherQuery, boundFunction, resultItems;
 
                     return _regenerator2.default.wrap(function _callee5$(_context5) {
@@ -221,12 +212,9 @@ var NeDb = function (_BaseMongoDb) {
                   return function (_x9) {
                     return _ref6.apply(this, arguments);
                   };
-                }());
-
-              case 2:
-                return _context6.abrupt('return', _context6.sent);
+                }()));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context6.stop();
             }
@@ -240,14 +228,13 @@ var NeDb = function (_BaseMongoDb) {
     }();
 
     _this.findAndModify = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(collectionName, query, updateQuery) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(collectionName, query, updateQuery) {
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
             switch (_context8.prev = _context8.next) {
               case 0:
-                _context8.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(collection) {
+                return _context8.abrupt('return', _this.__runForCollection(collectionName, function () {
+                  var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(collection) {
                     var _ref9, _ref10, count, resultItem;
 
                     return _regenerator2.default.wrap(function _callee7$(_context7) {
@@ -279,12 +266,9 @@ var NeDb = function (_BaseMongoDb) {
                   return function (_x13) {
                     return _ref8.apply(this, arguments);
                   };
-                }());
+                }()));
 
-              case 2:
-                return _context8.abrupt('return', _context8.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context8.stop();
             }
@@ -298,14 +282,13 @@ var NeDb = function (_BaseMongoDb) {
     }();
 
     _this.findOne = function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(collectionName, query) {
+      var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(collectionName, query) {
         return _regenerator2.default.wrap(function _callee10$(_context10) {
           while (1) {
             switch (_context10.prev = _context10.next) {
               case 0:
-                _context10.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(collection) {
+                return _context10.abrupt('return', _this.__runForCollection(collectionName, function () {
+                  var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(collection) {
                     var resultItem;
                     return _regenerator2.default.wrap(function _callee9$(_context9) {
                       while (1) {
@@ -329,12 +312,9 @@ var NeDb = function (_BaseMongoDb) {
                   return function (_x16) {
                     return _ref12.apply(this, arguments);
                   };
-                }());
+                }()));
 
-              case 2:
-                return _context10.abrupt('return', _context10.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context10.stop();
             }
@@ -348,25 +328,20 @@ var NeDb = function (_BaseMongoDb) {
     }();
 
     _this.remove = function () {
-      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(collectionName, query) {
+      var _ref13 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(collectionName, query) {
         return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
             switch (_context12.prev = _context12.next) {
               case 0:
-                _context12.next = 2;
-                return _this.__runForCollection(collectionName, function () {
-                  var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(collection) {
+                return _context12.abrupt('return', _this.__runForCollection(collectionName, function () {
+                  var _ref14 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(collection) {
                     return _regenerator2.default.wrap(function _callee11$(_context11) {
                       while (1) {
                         switch (_context11.prev = _context11.next) {
                           case 0:
-                            _context11.next = 2;
-                            return (0, _promisify.promisify)(collection, 'remove', query);
-
-                          case 2:
-                            return _context11.abrupt('return', _context11.sent);
+                            return _context11.abrupt('return', (0, _promisify.promisify)(collection, 'remove', query));
 
-                          case 3:
+                          case 1:
                           case 'end':
                             return _context11.stop();
                         }
@@ -377,12 +352,9 @@ var NeDb = function (_BaseMongoDb) {
                   return function (_x19) {
                     return _ref14.apply(this, arguments);
                   };
-                }());
+                }()));
 
-              case 2:
-                return _context12.abrupt('return', _context12.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context12.stop();
             }
@@ -396,7 +368,7 @@ var NeDb = function (_BaseMongoDb) {
     }();
 
     _this.__runForCollection = function () {
-      var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(collectionName, callback) {
+      var _ref15 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee13(collectionName, callback) {
         return _regenerator2.default.wrap(function _callee13$(_context13) {
           while (1) {
             switch (_context13.prev = _context13.next) {
diff --git a/dist/repository/OrganizationDatabaseRepository.js b/dist/repository/OrganizationDatabaseRepository.js
index 8686b2bd..21f3625a 100644
--- a/dist/repository/OrganizationDatabaseRepository.js
+++ b/dist/repository/OrganizationDatabaseRepository.js
@@ -55,18 +55,14 @@ var OrganizationDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.ORGANIZATIONS;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
-                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+                return _context.abrupt('return', _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model)));
 
-              case 2:
-                return _context.abrupt('return', _context.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -80,18 +76,14 @@ var OrganizationDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(id) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { _id: id });
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { _id: id }));
 
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -105,7 +97,7 @@ var OrganizationDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAll = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var query;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -114,13 +106,9 @@ var OrganizationDatabaseRepository = function (_BaseRepository) {
               case 0:
                 // TODO - this should probably just query the organization
                 query = userID ? { ownerID: userID } : {};
-                _context3.next = 3;
-                return _this._database.find(_this._collectionName, query);
-
-              case 3:
-                return _context3.abrupt('return', _context3.sent);
+                return _context3.abrupt('return', _this._database.find(_this._collectionName, query));
 
-              case 4:
+              case 2:
               case 'end':
                 return _context3.stop();
             }
@@ -134,20 +122,16 @@ var OrganizationDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByUserID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(userID) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(userID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this._database.find(_this._collectionName, {
+                return _context4.abrupt('return', _this._database.find(_this._collectionName, {
                   user_ids: userID
-                });
-
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
+                }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -161,18 +145,14 @@ var OrganizationDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(id) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                _context5.next = 2;
-                return _this._database.findOne(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context5.abrupt('return', _context5.sent);
+                return _context5.abrupt('return', _this._database.findOne(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context5.stop();
             }
@@ -185,7 +165,7 @@ var OrganizationDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+    _this.updateByID = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() {
       return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
           switch (_context6.prev = _context6.next) {
diff --git a/dist/repository/ProductConfigDatabaseRepository.js b/dist/repository/ProductConfigDatabaseRepository.js
index fb38681c..e153cd10 100644
--- a/dist/repository/ProductConfigDatabaseRepository.js
+++ b/dist/repository/ProductConfigDatabaseRepository.js
@@ -55,18 +55,14 @@ var ProductConfigDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.PRODUCT_CONFIGS;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
-                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+                return _context.abrupt('return', _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model)));
 
-              case 2:
-                return _context.abrupt('return', _context.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -80,18 +76,14 @@ var ProductConfigDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(id) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { _id: id });
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { _id: id }));
 
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -105,7 +97,7 @@ var ProductConfigDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAll = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var query;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -114,13 +106,9 @@ var ProductConfigDatabaseRepository = function (_BaseRepository) {
               case 0:
                 // TODO - this should probably just query the organization
                 query = userID ? { ownerID: userID } : {};
-                _context3.next = 3;
-                return _this._database.find(_this._collectionName, query);
-
-              case 3:
-                return _context3.abrupt('return', _context3.sent);
+                return _context3.abrupt('return', _this._database.find(_this._collectionName, query));
 
-              case 4:
+              case 2:
               case 'end':
                 return _context3.stop();
             }
@@ -134,20 +122,16 @@ var ProductConfigDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByProductID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(productID) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this._database.findOne(_this._collectionName, {
+                return _context4.abrupt('return', _this._database.findOne(_this._collectionName, {
                   product_id: productID
-                });
-
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
+                }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -161,18 +145,14 @@ var ProductConfigDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(id) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                _context5.next = 2;
-                return _this._database.findOne(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context5.abrupt('return', _context5.sent);
+                return _context5.abrupt('return', _this._database.findOne(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context5.stop();
             }
@@ -185,7 +165,7 @@ var ProductConfigDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6() {
+    _this.updateByID = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() {
       return _regenerator2.default.wrap(function _callee6$(_context6) {
         while (1) {
           switch (_context6.prev = _context6.next) {
diff --git a/dist/repository/ProductDatabaseRepository.js b/dist/repository/ProductDatabaseRepository.js
index 80e0247a..b726fa26 100644
--- a/dist/repository/ProductDatabaseRepository.js
+++ b/dist/repository/ProductDatabaseRepository.js
@@ -81,13 +81,9 @@ var ProductDatabaseRepository = function (_BaseRepository) {
                   product_id: _context.t7
                 };
                 _context.t9 = (0, _context.t2)(_context.t3, _context.t4, _context.t8);
-                _context.next = 16;
-                return _context.t0.insertOne.call(_context.t0, _context.t1, _context.t9);
+                return _context.abrupt('return', _context.t0.insertOne.call(_context.t0, _context.t1, _context.t9));
 
-              case 16:
-                return _context.abrupt('return', _context.sent);
-
-              case 17:
+              case 15:
               case 'end':
                 return _context.stop();
             }
@@ -106,13 +102,9 @@ var ProductDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -135,13 +127,9 @@ var ProductDatabaseRepository = function (_BaseRepository) {
               case 0:
                 // TODO - this should probably just query the organization
                 query = userID ? { ownerID: userID } : {};
-                _context3.next = 3;
-                return _this._database.find(_this._collectionName, query);
+                return _context3.abrupt('return', _this._database.find(_this._collectionName, query));
 
-              case 3:
-                return _context3.abrupt('return', _context3.sent);
-
-              case 4:
+              case 2:
               case 'end':
                 return _context3.stop();
             }
@@ -160,13 +148,9 @@ var ProductDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this._database.findOne(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
+                return _context4.abrupt('return', _this._database.findOne(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -185,17 +169,13 @@ var ProductDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                _context5.next = 2;
-                return _this._database.findOne(_this._collectionName, {
+                return _context5.abrupt('return', _this._database.findOne(_this._collectionName, {
                   $or: [{
                     product_id: !isNaN(productIDOrSlug) ? parseInt(productIDOrSlug, 10) : null
                   }, { slug: productIDOrSlug }]
-                });
-
-              case 2:
-                return _context5.abrupt('return', _context5.sent);
+                }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context5.stop();
             }
@@ -228,13 +208,9 @@ var ProductDatabaseRepository = function (_BaseRepository) {
                 _context6.t7 = {
                   $set: _context6.t6
                 };
-                _context6.next = 12;
-                return _context6.t0.findAndModify.call(_context6.t0, _context6.t1, _context6.t2, _context6.t7);
-
-              case 12:
-                return _context6.abrupt('return', _context6.sent);
+                return _context6.abrupt('return', _context6.t0.findAndModify.call(_context6.t0, _context6.t1, _context6.t2, _context6.t7));
 
-              case 13:
+              case 11:
               case 'end':
                 return _context6.stop();
             }
diff --git a/dist/repository/ProductDeviceDatabaseRepository.js b/dist/repository/ProductDeviceDatabaseRepository.js
index 323ed485..3b7529aa 100644
--- a/dist/repository/ProductDeviceDatabaseRepository.js
+++ b/dist/repository/ProductDeviceDatabaseRepository.js
@@ -55,18 +55,14 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.PRODUCT_DEVICES;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
-                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model));
+                return _context.abrupt('return', _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model)));
 
-              case 2:
-                return _context.abrupt('return', _context.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -80,18 +76,14 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(id) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -105,7 +97,7 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAll = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var query;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -114,13 +106,9 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
               case 0:
                 // TODO - this should probably just query the organization
                 query = userID ? { ownerID: userID } : {};
-                _context3.next = 3;
-                return _this._database.find(_this._collectionName, query);
+                return _context3.abrupt('return', _this._database.find(_this._collectionName, query));
 
-              case 3:
-                return _context3.abrupt('return', _context3.sent);
-
-              case 4:
+              case 2:
               case 'end':
                 return _context3.stop();
             }
@@ -134,22 +122,18 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAllByProductID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(productID, page, pageSize) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(productID, page, pageSize) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this._database.find(_this._collectionName, {
+                return _context4.abrupt('return', _this._database.find(_this._collectionName, {
                   page: page,
                   pageSize: pageSize,
                   productID: productID
-                });
-
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
+                }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -163,18 +147,14 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(id) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                _context5.next = 2;
-                return _this._database.findOne(_this._collectionName, { _id: id });
+                return _context5.abrupt('return', _this._database.findOne(_this._collectionName, { _id: id }));
 
-              case 2:
-                return _context5.abrupt('return', _context5.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context5.stop();
             }
@@ -188,20 +168,16 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getFromDeviceID = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(deviceID) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(deviceID) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
               case 0:
-                _context6.next = 2;
-                return _this._database.findOne(_this._collectionName, {
+                return _context6.abrupt('return', _this._database.findOne(_this._collectionName, {
                   deviceID: deviceID
-                });
-
-              case 2:
-                return _context6.abrupt('return', _context6.sent);
+                }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context6.stop();
             }
@@ -215,20 +191,16 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getManyFromDeviceIDs = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(deviceIDs) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(deviceIDs) {
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
               case 0:
-                _context7.next = 2;
-                return _this._database.find(_this._collectionName, {
+                return _context7.abrupt('return', _this._database.find(_this._collectionName, {
                   deviceID: { $in: deviceIDs }
-                });
+                }));
 
-              case 2:
-                return _context7.abrupt('return', _context7.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context7.stop();
             }
@@ -241,7 +213,7 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8() {
+    _this.updateByID = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8() {
       return _regenerator2.default.wrap(function _callee8$(_context8) {
         while (1) {
           switch (_context8.prev = _context8.next) {
@@ -257,18 +229,14 @@ var ProductDeviceDatabaseRepository = function (_BaseRepository) {
     }));
 
     _this.updateByID = function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(productDeviceID, productDevice) {
+      var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(productDeviceID, productDevice) {
         return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
             switch (_context9.prev = _context9.next) {
               case 0:
-                _context9.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: productDeviceID }, { $set: (0, _extends3.default)({}, productDevice) });
-
-              case 2:
-                return _context9.abrupt('return', _context9.sent);
+                return _context9.abrupt('return', _this._database.findAndModify(_this._collectionName, { _id: productDeviceID }, { $set: (0, _extends3.default)({}, productDevice) }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context9.stop();
             }
diff --git a/dist/repository/ProductFirmwareDatabaseRepository.js b/dist/repository/ProductFirmwareDatabaseRepository.js
index 3148b7db..c2673429 100644
--- a/dist/repository/ProductFirmwareDatabaseRepository.js
+++ b/dist/repository/ProductFirmwareDatabaseRepository.js
@@ -72,15 +72,11 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
-                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
+                return _context.abrupt('return', _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
                   updated_at: new Date()
-                }));
+                })));
 
-              case 2:
-                return _context.abrupt('return', _context.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -99,13 +95,9 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -271,17 +263,13 @@ var ProductFirmwareDatabaseRepository = function (_BaseRepository) {
           while (1) {
             switch (_context8.prev = _context8.next) {
               case 0:
-                _context8.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, {
+                return _context8.abrupt('return', _this._database.findAndModify(_this._collectionName, { _id: productFirmwareID }, {
                   $set: (0, _extends3.default)({}, productFirmware, {
                     updated_at: new Date()
                   })
-                }).then(formatProductFirmwareFromDb);
-
-              case 2:
-                return _context8.abrupt('return', _context8.sent);
+                }).then(formatProductFirmwareFromDb));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context8.stop();
             }
diff --git a/dist/repository/UserDatabaseRepository.js b/dist/repository/UserDatabaseRepository.js
index 0ec04631..fe691b13 100644
--- a/dist/repository/UserDatabaseRepository.js
+++ b/dist/repository/UserDatabaseRepository.js
@@ -63,18 +63,14 @@ var UserDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.USERS;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(user) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(user) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
-                return _this._database.insertOne(_this._collectionName, user);
+                return _context.abrupt('return', _this._database.insertOne(_this._collectionName, user));
 
-              case 2:
-                return _context.abrupt('return', _context.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -88,7 +84,7 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.createWithCredentials = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(userCredentials) {
         var userRole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
         var username, password, salt, passwordHash, modelToSave;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
@@ -114,13 +110,9 @@ var UserDatabaseRepository = function (_BaseRepository) {
                   salt: salt,
                   username: username
                 };
-                _context2.next = 10;
-                return _this._database.insertOne(_this._collectionName, modelToSave);
+                return _context2.abrupt('return', _this._database.insertOne(_this._collectionName, modelToSave));
 
-              case 10:
-                return _context2.abrupt('return', _context2.sent);
-
-              case 11:
+              case 9:
               case 'end':
                 return _context2.stop();
             }
@@ -134,18 +126,14 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteAccessToken = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID, accessToken) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(userID, accessToken) {
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
             switch (_context3.prev = _context3.next) {
               case 0:
-                _context3.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $pull: { accessTokens: { accessToken: accessToken } } });
+                return _context3.abrupt('return', _this._database.findAndModify(_this._collectionName, { _id: userID }, { $pull: { accessTokens: { accessToken: accessToken } } }));
 
-              case 2:
-                return _context3.abrupt('return', _context3.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context3.stop();
             }
@@ -159,18 +147,14 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(id) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this._database.remove(_this._collectionName, { _id: id });
+                return _context4.abrupt('return', _this._database.remove(_this._collectionName, { _id: id }));
 
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -183,7 +167,7 @@ var UserDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.getAll = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+    _this.getAll = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() {
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
@@ -199,7 +183,7 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }));
 
     _this.getByAccessToken = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(accessToken) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(accessToken) {
         var user;
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
@@ -243,7 +227,7 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(id) {
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
@@ -264,18 +248,14 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByUsername = function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(username) {
+      var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(username) {
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
             switch (_context8.prev = _context8.next) {
               case 0:
-                _context8.next = 2;
-                return _this._database.findOne(_this._collectionName, { username: username });
-
-              case 2:
-                return _context8.abrupt('return', _context8.sent);
+                return _context8.abrupt('return', _this._database.findOne(_this._collectionName, { username: username }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context8.stop();
             }
@@ -293,7 +273,7 @@ var UserDatabaseRepository = function (_BaseRepository) {
     };
 
     _this.isUserNameInUse = function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(username) {
+      var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(username) {
         return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
             switch (_context9.prev = _context9.next) {
@@ -318,18 +298,14 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.saveAccessToken = function () {
-      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(userID, tokenObject) {
+      var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(userID, tokenObject) {
         return _regenerator2.default.wrap(function _callee10$(_context10) {
           while (1) {
             switch (_context10.prev = _context10.next) {
               case 0:
-                _context10.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: userID }, { $push: { accessTokens: tokenObject } });
-
-              case 2:
-                return _context10.abrupt('return', _context10.sent);
+                return _context10.abrupt('return', _this._database.findAndModify(_this._collectionName, { _id: userID }, { $push: { accessTokens: tokenObject } }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context10.stop();
             }
@@ -347,18 +323,14 @@ var UserDatabaseRepository = function (_BaseRepository) {
     };
 
     _this.updateByID = function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id, props) {
+      var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(id, props) {
         return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
             switch (_context11.prev = _context11.next) {
               case 0:
-                _context11.next = 2;
-                return _this._database.findAndModify(_this._collectionName, { _id: id }, { $set: (0, _extends3.default)({}, props) });
-
-              case 2:
-                return _context11.abrupt('return', _context11.sent);
+                return _context11.abrupt('return', _this._database.findAndModify(_this._collectionName, { _id: id }, { $set: (0, _extends3.default)({}, props) }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context11.stop();
             }
@@ -372,7 +344,7 @@ var UserDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.validateLogin = function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username, password) {
+      var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(username, password) {
         var user, hash;
         return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
diff --git a/dist/repository/UserFileRepository.js b/dist/repository/UserFileRepository.js
index 1f954f6f..68b67360 100644
--- a/dist/repository/UserFileRepository.js
+++ b/dist/repository/UserFileRepository.js
@@ -84,7 +84,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     var _this = this;
 
     (0, _classCallCheck3.default)(this, UserFileRepository);
-    this.count = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+    this.count = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -100,7 +100,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     }));
 
     this.createWithCredentials = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(userCredentials) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(userCredentials) {
         var userRole = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
         var username, password, salt, passwordHash, modelToSave;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
@@ -125,13 +125,9 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                   salt: salt,
                   username: username
                 };
-                _context2.next = 10;
-                return _this.create(modelToSave);
+                return _context2.abrupt('return', _this.create(modelToSave));
 
-              case 10:
-                return _context2.abrupt('return', _context2.sent);
-
-              case 11:
+              case 9:
               case 'end':
                 return _context2.stop();
             }
@@ -145,7 +141,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     }();
 
     this.deleteAccessToken = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(userID, token) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(userID, token) {
         var user;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
@@ -165,17 +161,13 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                 throw new Error("User doesn't exist");
 
               case 5:
-                _context3.next = 7;
-                return _this.updateByID(userID, {
+                return _context3.abrupt('return', _this.updateByID(userID, {
                   accessTokens: user.accessTokens.filter(function (tokenObject) {
                     return tokenObject.accessToken !== token;
                   })
-                });
+                }));
 
-              case 7:
-                return _context3.abrupt('return', _context3.sent);
-
-              case 8:
+              case 6:
               case 'end':
                 return _context3.stop();
             }
@@ -189,7 +181,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     }();
 
     this.getByAccessToken = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(accessToken) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(accessToken) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -224,7 +216,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     };
 
     this.saveAccessToken = function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(userID, tokenObject) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(userID, tokenObject) {
         var user;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
@@ -244,15 +236,11 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                 throw new _HttpError2.default('Could not find user for user ID');
 
               case 5:
-                _context5.next = 7;
-                return _this.updateByID(userID, {
+                return _context5.abrupt('return', _this.updateByID(userID, {
                   accessTokens: [].concat((0, _toConsumableArray3.default)(user.accessTokens), [tokenObject])
-                });
+                }));
 
-              case 7:
-                return _context5.abrupt('return', _context5.sent);
-
-              case 8:
+              case 6:
               case 'end':
                 return _context5.stop();
             }
@@ -270,7 +258,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
     };
 
     this.validateLogin = function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(username, password) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(username, password) {
         var user, hash;
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
@@ -331,7 +319,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   (0, _createClass3.default)(UserFileRepository, [{
     key: 'create',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(user) {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7(user) {
         var id, modelToSave;
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
@@ -339,21 +327,10 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
               case 0:
                 id = (0, _uuid2.default)();
 
-              case 1:
-                _context7.next = 3;
-                return this._fileManager.hasFile(id + '.json');
-
-              case 3:
-                if (!_context7.sent) {
-                  _context7.next = 7;
-                  break;
+                while (this._fileManager.hasFile(id + '.json')) {
+                  id = (0, _uuid2.default)();
                 }
 
-                id = (0, _uuid2.default)();
-                _context7.next = 1;
-                break;
-
-              case 7:
                 modelToSave = (0, _extends3.default)({}, user, {
                   created_at: new Date(),
                   created_by: null,
@@ -364,7 +341,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
                 this._fileManager.createFile(modelToSave.id + '.json', modelToSave);
                 return _context7.abrupt('return', modelToSave);
 
-              case 10:
+              case 5:
               case 'end':
                 return _context7.stop();
             }
@@ -381,7 +358,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'deleteByID',
     value: function () {
-      var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(id) {
+      var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8(id) {
         return _regenerator2.default.wrap(function _callee8$(_context8) {
           while (1) {
             switch (_context8.prev = _context8.next) {
@@ -405,7 +382,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'getAll',
     value: function () {
-      var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9() {
+      var _ref9 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9() {
         return _regenerator2.default.wrap(function _callee9$(_context9) {
           while (1) {
             switch (_context9.prev = _context9.next) {
@@ -433,7 +410,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'getByID',
     value: function () {
-      var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(id) {
+      var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee10(id) {
         return _regenerator2.default.wrap(function _callee10$(_context10) {
           while (1) {
             switch (_context10.prev = _context10.next) {
@@ -457,7 +434,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'getByUsername',
     value: function () {
-      var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(username) {
+      var _ref11 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee11(username) {
         return _regenerator2.default.wrap(function _callee11$(_context11) {
           while (1) {
             switch (_context11.prev = _context11.next) {
@@ -489,7 +466,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'isUserNameInUse',
     value: function () {
-      var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(username) {
+      var _ref12 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee12(username) {
         return _regenerator2.default.wrap(function _callee12$(_context12) {
           while (1) {
             switch (_context12.prev = _context12.next) {
@@ -521,7 +498,7 @@ var UserFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0, _
   }, {
     key: 'updateByID',
     value: function () {
-      var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(id, props) {
+      var _ref13 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee13(id, props) {
         var user, modelToSave;
         return _regenerator2.default.wrap(function _callee13$(_context13) {
           while (1) {
diff --git a/dist/repository/WebhookDatabaseRepository.js b/dist/repository/WebhookDatabaseRepository.js
index 4daf7395..c0ebbf23 100644
--- a/dist/repository/WebhookDatabaseRepository.js
+++ b/dist/repository/WebhookDatabaseRepository.js
@@ -55,20 +55,16 @@ var WebhookDatabaseRepository = function (_BaseRepository) {
     _this._collectionName = _collectionNames2.default.WEBHOOKS;
 
     _this.create = function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(model) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(model) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _context.next = 2;
-                return _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
+                return _context.abrupt('return', _this._database.insertOne(_this._collectionName, (0, _extends3.default)({}, model, {
                   created_at: new Date()
-                }));
+                })));
 
-              case 2:
-                return _context.abrupt('return', _context.sent);
-
-              case 3:
+              case 1:
               case 'end':
                 return _context.stop();
             }
@@ -82,18 +78,14 @@ var WebhookDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.deleteByID = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(id) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(id) {
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                _context2.next = 2;
-                return _this._database.remove(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context2.abrupt('return', _context2.sent);
+                return _context2.abrupt('return', _this._database.remove(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context2.stop();
             }
@@ -107,7 +99,7 @@ var WebhookDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getAll = function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var query;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
@@ -115,13 +107,9 @@ var WebhookDatabaseRepository = function (_BaseRepository) {
             switch (_context3.prev = _context3.next) {
               case 0:
                 query = userID ? { ownerID: userID } : {};
-                _context3.next = 3;
-                return _this._database.find(_this._collectionName, query);
+                return _context3.abrupt('return', _this._database.find(_this._collectionName, query));
 
-              case 3:
-                return _context3.abrupt('return', _context3.sent);
-
-              case 4:
+              case 2:
               case 'end':
                 return _context3.stop();
             }
@@ -135,18 +123,14 @@ var WebhookDatabaseRepository = function (_BaseRepository) {
     }();
 
     _this.getByID = function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(id) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(id) {
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                _context4.next = 2;
-                return _this._database.findOne(_this._collectionName, { _id: id });
-
-              case 2:
-                return _context4.abrupt('return', _context4.sent);
+                return _context4.abrupt('return', _this._database.findOne(_this._collectionName, { _id: id }));
 
-              case 3:
+              case 1:
               case 'end':
                 return _context4.stop();
             }
@@ -159,7 +143,7 @@ var WebhookDatabaseRepository = function (_BaseRepository) {
       };
     }();
 
-    _this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+    _this.updateByID = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() {
       return _regenerator2.default.wrap(function _callee5$(_context5) {
         while (1) {
           switch (_context5.prev = _context5.next) {
diff --git a/dist/repository/WebhookFileRepository.js b/dist/repository/WebhookFileRepository.js
index 24d15d46..493d9ce0 100644
--- a/dist/repository/WebhookFileRepository.js
+++ b/dist/repository/WebhookFileRepository.js
@@ -76,7 +76,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
     var _this = this;
 
     (0, _classCallCheck3.default)(this, WebhookFileRepository);
-    this.count = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+    this.count = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
       return _regenerator2.default.wrap(function _callee$(_context) {
         while (1) {
           switch (_context.prev = _context.next) {
@@ -92,7 +92,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
     }));
 
     this.getAll = function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2() {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() {
         var userID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
         var allData;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
@@ -130,7 +130,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
       };
     }();
 
-    this.updateByID = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3() {
+    this.updateByID = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
       return _regenerator2.default.wrap(function _callee3$(_context3) {
         while (1) {
           switch (_context3.prev = _context3.next) {
@@ -151,7 +151,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   (0, _createClass3.default)(WebhookFileRepository, [{
     key: 'create',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(model) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(model) {
         var id, modelToSave;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
@@ -159,21 +159,10 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
               case 0:
                 id = (0, _uuid2.default)();
 
-              case 1:
-                _context4.next = 3;
-                return this._fileManager.hasFile(id + '.json');
-
-              case 3:
-                if (!_context4.sent) {
-                  _context4.next = 7;
-                  break;
+                while (this._fileManager.hasFile(id + '.json')) {
+                  id = (0, _uuid2.default)();
                 }
 
-                id = (0, _uuid2.default)();
-                _context4.next = 1;
-                break;
-
-              case 7:
                 modelToSave = (0, _extends3.default)({}, model, {
                   created_at: new Date(),
                   id: id
@@ -183,7 +172,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
                 this._fileManager.createFile(modelToSave.id + '.json', modelToSave);
                 return _context4.abrupt('return', modelToSave);
 
-              case 10:
+              case 5:
               case 'end':
                 return _context4.stop();
             }
@@ -200,7 +189,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   }, {
     key: 'deleteByID',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(id) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(id) {
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
@@ -224,7 +213,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   }, {
     key: 'getByID',
     value: function () {
-      var _ref6 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(id) {
+      var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6(id) {
         return _regenerator2.default.wrap(function _callee6$(_context6) {
           while (1) {
             switch (_context6.prev = _context6.next) {
@@ -251,7 +240,7 @@ var WebhookFileRepository = (_dec = (0, _sparkProtocol.memoizeSet)(), _dec2 = (0
   }, {
     key: '_getAll',
     value: function () {
-      var _ref7 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7() {
+      var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7() {
         return _regenerator2.default.wrap(function _callee7$(_context7) {
           while (1) {
             switch (_context7.prev = _context7.next) {
diff --git a/dist/scripts/migrateFilesToDatabase.js b/dist/scripts/migrateFilesToDatabase.js
index 05eebbce..89fdc749 100644
--- a/dist/scripts/migrateFilesToDatabase.js
+++ b/dist/scripts/migrateFilesToDatabase.js
@@ -51,7 +51,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 var DATABASE_TYPE = process.argv[2];
 
 var setupDatabase = function () {
-  var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
+  var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
     return _regenerator2.default.wrap(function _callee$(_context) {
       while (1) {
         switch (_context.prev = _context.next) {
@@ -159,18 +159,14 @@ var deepDateCast = function deepDateCast(node) {
 
 var insertItem = function insertItem(database, collectionName) {
   return function () {
-    var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(item) {
+    var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(item) {
       return _regenerator2.default.wrap(function _callee2$(_context2) {
         while (1) {
           switch (_context2.prev = _context2.next) {
             case 0:
-              _context2.next = 2;
-              return database.insertOne(collectionName, item);
+              return _context2.abrupt('return', database.insertOne(collectionName, item));
 
-            case 2:
-              return _context2.abrupt('return', _context2.sent);
-
-            case 3:
+            case 1:
             case 'end':
               return _context2.stop();
           }
@@ -185,7 +181,7 @@ var insertItem = function insertItem(database, collectionName) {
 };
 
 var insertUsers = function () {
-  var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(database, users) {
+  var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(database, users) {
     var userIDsMap;
     return _regenerator2.default.wrap(function _callee4$(_context4) {
       while (1) {
@@ -194,7 +190,7 @@ var insertUsers = function () {
             userIDsMap = new _map2.default();
             _context4.next = 3;
             return _promise2.default.all(users.map(deepDateCast).map(function () {
-              var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(user) {
+              var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(user) {
                 var insertedUser;
                 return _regenerator2.default.wrap(function _callee3$(_context3) {
                   while (1) {
@@ -237,7 +233,7 @@ var insertUsers = function () {
   };
 }();
 
-(0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() {
+(0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5() {
   var database, users, userIDsMap;
   return _regenerator2.default.wrap(function _callee5$(_context5) {
     while (1) {

From a873d9be4b615c02aeae9b0aaa44535590d746ba Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Fri, 8 Sep 2017 03:13:53 +0200
Subject: [PATCH 503/504] disable userFilter for admin in eventsController

---
 dist/controllers/EventsController.js | 44 ++++++++++++++++------------
 package-lock.json                    |  2 +-
 src/controllers/EventsController.js  | 12 +++++---
 3 files changed, 35 insertions(+), 23 deletions(-)

diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js
index d789b73b..5deec62b 100644
--- a/dist/controllers/EventsController.js
+++ b/dist/controllers/EventsController.js
@@ -8,6 +8,10 @@ var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-pr
 
 var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
 
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
 var _regenerator = require('babel-runtime/regenerator');
 
 var _regenerator2 = _interopRequireDefault(_regenerator);
@@ -154,7 +158,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'ping',
     value: function () {
-      var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(payload) {
+      var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(payload) {
         return _regenerator2.default.wrap(function _callee$(_context) {
           while (1) {
             switch (_context.prev = _context.next) {
@@ -180,13 +184,15 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'getEvents',
     value: function () {
-      var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(eventNamePrefix) {
+      var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(eventNamePrefix) {
+        var _eventManager;
+
         var subscriptionID;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
-                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), { userID: this.user.id });
+                subscriptionID = (_eventManager = this._eventManager).subscribe.apply(_eventManager, [eventNamePrefix, this._pipeEvent.bind(this)].concat((0, _toConsumableArray3.default)(this._getUserFilter())));
                 _context2.next = 3;
                 return this._closeStream(subscriptionID);
 
@@ -210,16 +216,15 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'getMyEvents',
     value: function () {
-      var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(eventNamePrefix) {
+      var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(eventNamePrefix) {
         var subscriptionID;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
             switch (_context3.prev = _context3.next) {
               case 0:
-                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), {
-                  mydevices: true,
-                  userID: this.user.id
-                });
+                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), (0, _extends3.default)({
+                  mydevices: true
+                }, this._getUserFilter()));
                 _context3.next = 3;
                 return this._closeStream(subscriptionID);
 
@@ -243,16 +248,15 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'getDeviceEvents',
     value: function () {
-      var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(deviceID, eventNamePrefix) {
+      var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(deviceID, eventNamePrefix) {
         var subscriptionID;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
               case 0:
-                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), {
-                  deviceID: deviceID,
-                  userID: this.user.id
-                });
+                subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), (0, _extends3.default)({
+                  deviceID: deviceID
+                }, this._getUserFilter()));
                 _context4.next = 3;
                 return this._closeStream(subscriptionID);
 
@@ -276,19 +280,18 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
   }, {
     key: 'publish',
     value: function () {
-      var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(postBody) {
+      var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(postBody) {
         var eventData;
         return _regenerator2.default.wrap(function _callee5$(_context5) {
           while (1) {
             switch (_context5.prev = _context5.next) {
               case 0:
-                eventData = {
+                eventData = (0, _extends3.default)({
                   data: postBody.data,
                   isPublic: !postBody.private,
                   name: postBody.name,
-                  ttl: postBody.ttl,
-                  userID: this.user.id
-                };
+                  ttl: postBody.ttl
+                }, this._getUserFilter());
 
 
                 this._eventManager.publish(eventData);
@@ -308,6 +311,11 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
 
       return publish;
     }()
+  }, {
+    key: '_getUserFilter',
+    value: function _getUserFilter() {
+      return this.user.role === 'administrator' ? {} : { userID: this.user.id };
+    }
   }]);
   return EventsController;
 }(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'ping', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'ping'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec10, _dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class));
diff --git a/package-lock.json b/package-lock.json
index 39e897fa..f6b9d29b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6027,7 +6027,7 @@
       }
     },
     "spark-protocol": {
-      "version": "git+https://github.com/Brewskey/spark-protocol.git#e2d93933f180d0128dae929024c18224c1527045",
+      "version": "git+https://github.com/Brewskey/spark-protocol.git#f69b9ba7dac36c7a674d62258fc63f76f204a62a",
       "requires": {
         "babel-plugin-transform-decorators": "6.24.1",
         "binary-version-reader": "0.5.2",
diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js
index 611c41a5..636b5cd6 100644
--- a/src/controllers/EventsController.js
+++ b/src/controllers/EventsController.js
@@ -62,7 +62,7 @@ class EventsController extends Controller {
     const subscriptionID = this._eventManager.subscribe(
       eventNamePrefix,
       this._pipeEvent.bind(this),
-      { userID: this.user.id },
+      ...this._getUserFilter(),
     );
 
     await this._closeStream(subscriptionID);
@@ -78,7 +78,7 @@ class EventsController extends Controller {
       this._pipeEvent.bind(this),
       {
         mydevices: true,
-        userID: this.user.id,
+        ...this._getUserFilter(),
       },
     );
 
@@ -98,7 +98,7 @@ class EventsController extends Controller {
       this._pipeEvent.bind(this),
       {
         deviceID,
-        userID: this.user.id,
+        ...this._getUserFilter(),
       },
     );
 
@@ -119,12 +119,16 @@ class EventsController extends Controller {
       isPublic: !postBody.private,
       name: postBody.name,
       ttl: postBody.ttl,
-      userID: this.user.id,
+      ...this._getUserFilter(),
     };
 
     this._eventManager.publish(eventData);
     return this.ok({ ok: true });
   }
+
+  _getUserFilter(): Object {
+    return this.user.role === 'administrator' ? {} : { userID: this.user.id };
+  }
 }
 
 export default EventsController;

From d2a64aa858612d7690fd2c2540312d9b4602e9da Mon Sep 17 00:00:00 2001
From: Anton puko 
Date: Sat, 9 Sep 2017 01:24:34 +0200
Subject: [PATCH 504/504] return keep alive for event stream

---
 dist/RouteConfig.js                  |   6 +-
 dist/controllers/EventsController.js | 130 ++++++++++++++++-----------
 src/RouteConfig.js                   |   4 -
 src/controllers/EventsController.js  |  79 ++++++++++------
 4 files changed, 132 insertions(+), 87 deletions(-)

diff --git a/dist/RouteConfig.js b/dist/RouteConfig.js
index 5482cfea..4c88e9a3 100644
--- a/dist/RouteConfig.js
+++ b/dist/RouteConfig.js
@@ -74,10 +74,6 @@ var injectUserMiddleware = function injectUserMiddleware(container) {
   };
 };
 
-// in old codebase there was _keepAlive() function in controllers , which
-// prevents of closing server-sent-events stream if there aren't events for
-// a long time, but according to the docs sse keep connection alive automatically.
-// if there will be related issues in the future, we can return _keepAlive() back.
 var serverSentEventsMiddleware = function serverSentEventsMiddleware() {
   return function (request, response, next) {
     request.socket.setNoDelay();
@@ -116,7 +112,7 @@ exports.default = function (app, container, controllers, settings) {
         return;
       }
       app[httpVerb](route, maybe(oauth.authenticate(), !anonymous), maybe(serverSentEventsMiddleware(), serverSentEvents), injectUserMiddleware(container), maybe(filesMiddleware(allowedUploads), allowedUploads), function () {
-        var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(request, response) {
+        var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(request, response) {
           var argumentNames, values, controllerInstance, _request$body, access_token, body, functionResult, result, httpError;
 
           return _regenerator2.default.wrap(function _callee$(_context) {
diff --git a/dist/controllers/EventsController.js b/dist/controllers/EventsController.js
index 5deec62b..fe22b278 100644
--- a/dist/controllers/EventsController.js
+++ b/dist/controllers/EventsController.js
@@ -8,6 +8,14 @@ var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-pr
 
 var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
 
+var _stringify = require('babel-runtime/core-js/json/stringify');
+
+var _stringify2 = _interopRequireDefault(_stringify);
+
+var _promise = require('babel-runtime/core-js/promise');
+
+var _promise2 = _interopRequireDefault(_promise);
+
 var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
 
 var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
@@ -24,14 +32,6 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
 
 var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
 
-var _stringify = require('babel-runtime/core-js/json/stringify');
-
-var _stringify2 = _interopRequireDefault(_stringify);
-
-var _promise = require('babel-runtime/core-js/promise');
-
-var _promise2 = _interopRequireDefault(_promise);
-
 var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
 
 var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
@@ -115,6 +115,8 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
 
 var logger = _logger2.default.createModuleLogger(module);
 
+var KEEP_ALIVE_INTERVAL = 9000;
+
 var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _route2.default)('/v1/ping'), _dec3 = (0, _anonymous2.default)(), _dec4 = (0, _httpVerb2.default)('get'), _dec5 = (0, _route2.default)('/v1/events/:eventNamePrefix?*'), _dec6 = (0, _serverSentEvents2.default)(), _dec7 = (0, _httpVerb2.default)('get'), _dec8 = (0, _route2.default)('/v1/devices/events/:eventNamePrefix?*'), _dec9 = (0, _serverSentEvents2.default)(), _dec10 = (0, _httpVerb2.default)('get'), _dec11 = (0, _route2.default)('/v1/devices/:deviceID/events/:eventNamePrefix?*'), _dec12 = (0, _serverSentEvents2.default)(), _dec13 = (0, _httpVerb2.default)('post'), _dec14 = (0, _route2.default)('/v1/devices/events'), (_class = function (_Controller) {
   (0, _inherits3.default)(EventsController, _Controller);
 
@@ -123,39 +125,15 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
 
     var _this = (0, _possibleConstructorReturn3.default)(this, (EventsController.__proto__ || (0, _getPrototypeOf2.default)(EventsController)).call(this));
 
+    _this._keepAliveIntervalID = null;
+    _this._lastEventDate = new Date();
+
+
     _this._eventManager = eventManager;
     return _this;
   }
 
   (0, _createClass3.default)(EventsController, [{
-    key: '_closeStream',
-    value: function _closeStream(subscriptionID) {
-      var _this2 = this;
-
-      return new _promise2.default(function (resolve) {
-        var closeStreamHandler = function closeStreamHandler() {
-          _this2._eventManager.unsubscribe(subscriptionID);
-          resolve();
-        };
-
-        _this2.request.on('close', closeStreamHandler);
-        _this2.request.on('end', closeStreamHandler);
-        _this2.response.on('finish', closeStreamHandler);
-        _this2.response.on('end', closeStreamHandler);
-      });
-    }
-  }, {
-    key: '_pipeEvent',
-    value: function _pipeEvent(event) {
-      try {
-        this.response.write('event: ' + event.name + '\n');
-        this.response.write('data: ' + (0, _stringify2.default)((0, _eventToApi2.default)(event)) + '\n\n');
-      } catch (error) {
-        logger.error({ err: error }, 'pipeEvents - write error');
-        throw error;
-      }
-    }
-  }, {
     key: 'ping',
     value: function () {
       var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(payload) {
@@ -187,19 +165,20 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
       var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(eventNamePrefix) {
         var _eventManager;
 
-        var subscriptionID;
+        var subscriptionID, keepAliveIntervalID;
         return _regenerator2.default.wrap(function _callee2$(_context2) {
           while (1) {
             switch (_context2.prev = _context2.next) {
               case 0:
                 subscriptionID = (_eventManager = this._eventManager).subscribe.apply(_eventManager, [eventNamePrefix, this._pipeEvent.bind(this)].concat((0, _toConsumableArray3.default)(this._getUserFilter())));
-                _context2.next = 3;
-                return this._closeStream(subscriptionID);
+                keepAliveIntervalID = this._startKeepAlive();
+                _context2.next = 4;
+                return this._closeStream(subscriptionID, keepAliveIntervalID);
 
-              case 3:
+              case 4:
                 return _context2.abrupt('return', this.ok());
 
-              case 4:
+              case 5:
               case 'end':
                 return _context2.stop();
             }
@@ -217,7 +196,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
     key: 'getMyEvents',
     value: function () {
       var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(eventNamePrefix) {
-        var subscriptionID;
+        var subscriptionID, keepAliveIntervalID;
         return _regenerator2.default.wrap(function _callee3$(_context3) {
           while (1) {
             switch (_context3.prev = _context3.next) {
@@ -225,13 +204,14 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
                 subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), (0, _extends3.default)({
                   mydevices: true
                 }, this._getUserFilter()));
-                _context3.next = 3;
-                return this._closeStream(subscriptionID);
+                keepAliveIntervalID = this._startKeepAlive();
+                _context3.next = 4;
+                return this._closeStream(subscriptionID, keepAliveIntervalID);
 
-              case 3:
+              case 4:
                 return _context3.abrupt('return', this.ok());
 
-              case 4:
+              case 5:
               case 'end':
                 return _context3.stop();
             }
@@ -249,7 +229,7 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
     key: 'getDeviceEvents',
     value: function () {
       var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(deviceID, eventNamePrefix) {
-        var subscriptionID;
+        var subscriptionID, keepAliveIntervalID;
         return _regenerator2.default.wrap(function _callee4$(_context4) {
           while (1) {
             switch (_context4.prev = _context4.next) {
@@ -257,13 +237,14 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
                 subscriptionID = this._eventManager.subscribe(eventNamePrefix, this._pipeEvent.bind(this), (0, _extends3.default)({
                   deviceID: deviceID
                 }, this._getUserFilter()));
-                _context4.next = 3;
-                return this._closeStream(subscriptionID);
+                keepAliveIntervalID = this._startKeepAlive();
+                _context4.next = 4;
+                return this._closeStream(subscriptionID, keepAliveIntervalID);
 
-              case 3:
+              case 4:
                 return _context4.abrupt('return', this.ok());
 
-              case 4:
+              case 5:
               case 'end':
                 return _context4.stop();
             }
@@ -311,11 +292,58 @@ var EventsController = (_dec = (0, _httpVerb2.default)('post'), _dec2 = (0, _rou
 
       return publish;
     }()
+  }, {
+    key: '_closeStream',
+    value: function _closeStream(subscriptionID, keepAliveIntervalID) {
+      var _this2 = this;
+
+      return new _promise2.default(function (resolve) {
+        var closeStreamHandler = function closeStreamHandler() {
+          _this2._eventManager.unsubscribe(subscriptionID);
+          clearInterval(keepAliveIntervalID);
+          resolve();
+        };
+
+        _this2.request.on('close', closeStreamHandler);
+        _this2.request.on('end', closeStreamHandler);
+        _this2.response.on('finish', closeStreamHandler);
+        _this2.response.on('end', closeStreamHandler);
+      });
+    }
   }, {
     key: '_getUserFilter',
     value: function _getUserFilter() {
       return this.user.role === 'administrator' ? {} : { userID: this.user.id };
     }
+  }, {
+    key: '_startKeepAlive',
+    value: function _startKeepAlive() {
+      var _this3 = this;
+
+      return setInterval(function () {
+        if (new Date() - _this3._lastEventDate >= KEEP_ALIVE_INTERVAL) {
+          _this3.response.write('\n');
+          _this3._updateLastEventDate();
+        }
+      }, KEEP_ALIVE_INTERVAL);
+    }
+  }, {
+    key: '_pipeEvent',
+    value: function _pipeEvent(event) {
+      try {
+        this.response.write('event: ' + event.name + '\n');
+        this.response.write('data: ' + (0, _stringify2.default)((0, _eventToApi2.default)(event)) + '\n\n');
+        this._updateLastEventDate();
+      } catch (error) {
+        logger.error({ err: error }, 'pipeEvents - write error');
+        throw error;
+      }
+    }
+  }, {
+    key: '_updateLastEventDate',
+    value: function _updateLastEventDate() {
+      this._lastEventDate = new Date();
+    }
   }]);
   return EventsController;
 }(_Controller3.default), (_applyDecoratedDescriptor(_class.prototype, 'ping', [_dec, _dec2, _dec3], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'ping'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getEvents', [_dec4, _dec5, _dec6], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getMyEvents', [_dec7, _dec8, _dec9], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getMyEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'getDeviceEvents', [_dec10, _dec11, _dec12], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'getDeviceEvents'), _class.prototype), _applyDecoratedDescriptor(_class.prototype, 'publish', [_dec13, _dec14], (0, _getOwnPropertyDescriptor2.default)(_class.prototype, 'publish'), _class.prototype)), _class));
diff --git a/src/RouteConfig.js b/src/RouteConfig.js
index 14c7e9fb..918fe447 100644
--- a/src/RouteConfig.js
+++ b/src/RouteConfig.js
@@ -42,10 +42,6 @@ const injectUserMiddleware = (container: Container): Middleware => (
   next();
 };
 
-// in old codebase there was _keepAlive() function in controllers , which
-// prevents of closing server-sent-events stream if there aren't events for
-// a long time, but according to the docs sse keep connection alive automatically.
-// if there will be related issues in the future, we can return _keepAlive() back.
 const serverSentEventsMiddleware = (): Middleware => (
   request: $Request,
   response: $Response,
diff --git a/src/controllers/EventsController.js b/src/controllers/EventsController.js
index 636b5cd6..e993baab 100644
--- a/src/controllers/EventsController.js
+++ b/src/controllers/EventsController.js
@@ -12,8 +12,12 @@ import eventToApi from '../lib/eventToApi';
 import Logger from '../lib/logger';
 const logger = Logger.createModuleLogger(module);
 
+const KEEP_ALIVE_INTERVAL = 9000;
+
 class EventsController extends Controller {
   _eventManager: EventManager;
+  _keepAliveIntervalID: ?string = null;
+  _lastEventDate: Date = new Date();
 
   constructor(eventManager: EventManager) {
     super();
@@ -21,30 +25,6 @@ class EventsController extends Controller {
     this._eventManager = eventManager;
   }
 
-  _closeStream(subscriptionID: string): Promise {
-    return new Promise((resolve: () => void) => {
-      const closeStreamHandler = () => {
-        this._eventManager.unsubscribe(subscriptionID);
-        resolve();
-      };
-
-      this.request.on('close', closeStreamHandler);
-      this.request.on('end', closeStreamHandler);
-      this.response.on('finish', closeStreamHandler);
-      this.response.on('end', closeStreamHandler);
-    });
-  }
-
-  _pipeEvent(event: Event) {
-    try {
-      this.response.write(`event: ${event.name}\n`);
-      this.response.write(`data: ${JSON.stringify(eventToApi(event))}\n\n`);
-    } catch (error) {
-      logger.error({ err: error }, 'pipeEvents - write error');
-      throw error;
-    }
-  }
-
   @httpVerb('post')
   @route('/v1/ping')
   @anonymous()
@@ -64,8 +44,9 @@ class EventsController extends Controller {
       this._pipeEvent.bind(this),
       ...this._getUserFilter(),
     );
+    const keepAliveIntervalID = this._startKeepAlive();
 
-    await this._closeStream(subscriptionID);
+    await this._closeStream(subscriptionID, keepAliveIntervalID);
     return this.ok();
   }
 
@@ -81,8 +62,9 @@ class EventsController extends Controller {
         ...this._getUserFilter(),
       },
     );
+    const keepAliveIntervalID = this._startKeepAlive();
 
-    await this._closeStream(subscriptionID);
+    await this._closeStream(subscriptionID, keepAliveIntervalID);
     return this.ok();
   }
 
@@ -101,8 +83,9 @@ class EventsController extends Controller {
         ...this._getUserFilter(),
       },
     );
+    const keepAliveIntervalID = this._startKeepAlive();
 
-    await this._closeStream(subscriptionID);
+    await this._closeStream(subscriptionID, keepAliveIntervalID);
     return this.ok();
   }
 
@@ -126,9 +109,51 @@ class EventsController extends Controller {
     return this.ok({ ok: true });
   }
 
+  _closeStream(
+    subscriptionID: string,
+    keepAliveIntervalID: Object,
+  ): Promise {
+    return new Promise((resolve: () => void) => {
+      const closeStreamHandler = () => {
+        this._eventManager.unsubscribe(subscriptionID);
+        clearInterval(keepAliveIntervalID);
+        resolve();
+      };
+
+      this.request.on('close', closeStreamHandler);
+      this.request.on('end', closeStreamHandler);
+      this.response.on('finish', closeStreamHandler);
+      this.response.on('end', closeStreamHandler);
+    });
+  }
+
   _getUserFilter(): Object {
     return this.user.role === 'administrator' ? {} : { userID: this.user.id };
   }
+
+  _startKeepAlive(): Object {
+    return setInterval(() => {
+      if (new Date() - this._lastEventDate >= KEEP_ALIVE_INTERVAL) {
+        this.response.write('\n');
+        this._updateLastEventDate();
+      }
+    }, KEEP_ALIVE_INTERVAL);
+  }
+
+  _pipeEvent(event: Event) {
+    try {
+      this.response.write(`event: ${event.name}\n`);
+      this.response.write(`data: ${JSON.stringify(eventToApi(event))}\n\n`);
+      this._updateLastEventDate();
+    } catch (error) {
+      logger.error({ err: error }, 'pipeEvents - write error');
+      throw error;
+    }
+  }
+
+  _updateLastEventDate() {
+    this._lastEventDate = new Date();
+  }
 }
 
 export default EventsController;