diff --git a/client/api/energy_data.js b/client/api/energy_data.js index 871d581..4da4cd5 100644 --- a/client/api/energy_data.js +++ b/client/api/energy_data.js @@ -1,18 +1,25 @@ -const ENDPOINT = '/data/v1/energy'; import extend from 'extend'; +const ENDPOINT = '/data/v1/energy'; + +// send all date parameters as unix timestamps; class EnergyDataApi { static index(params){ params = extend({ - }, params); + if (params.dates){ + params.dates = params.dates.map((date_range)=>{ + if (date_range[0]) date_range[0] = date_range[0].unix(); + if (date_range[1]) date_range[1] = date_range[1].unix(); + return [date_range[0], date_range[1]]; + }) + } return jQuery.ajax({ - url: `${ENDPOINT}`, + url: ENDPOINT + '?' + jQuery.param(params), type: 'GET', - params: params, dataType: 'json' - }).success((res)=>{ + }).then((res)=>{ return res.data; }); } diff --git a/client/api/houses.js b/client/api/houses.js index e69de29..ed92db1 100644 --- a/client/api/houses.js +++ b/client/api/houses.js @@ -0,0 +1,18 @@ +const ENDPOINT = '/data/v1/houses'; +import extend from 'extend'; + +class HousesApi { + + static index(params){ + return jQuery.ajax({ + url: ENDPOINT + '?' + jQuery.param(params), + type: 'GET', + dataType: 'json' + }).then((res)=>{ + return res.data; + }); + } + +} + +export default HousesApi; diff --git a/client/api/power_data.js b/client/api/power_data.js index 8b13789..f2aea90 100644 --- a/client/api/power_data.js +++ b/client/api/power_data.js @@ -1 +1,29 @@ +const ENDPOINT = '/data/v1/power'; +import extend from 'extend'; + +// send all date parameters as unix timestamps; +class PowerDataApi { + + static index(params){ + params = extend({ + }, params); + if (params.dates){ + params.dates = params.dates.map((date_range)=>{ + if (date_range[0]) date_range[0] = date_range[0].unix(); + if (date_range[1]) date_range[1] = date_range[1].unix(); + return [date_range[0], date_range[1]]; + }) + } + return jQuery.ajax({ + url: ENDPOINT + '?' + jQuery.param(params), + type: 'GET', + dataType: 'json' + }).then((res)=>{ + return res.data; + }); + } + +} + +export default PowerDataApi; diff --git a/client/app.js b/client/app.js index 2b52233..c6f0749 100644 --- a/client/app.js +++ b/client/app.js @@ -1,9 +1,11 @@ import 'babel-polyfill'; +import 'bootstrap/dist/js/bootstrap.min'; import React from 'react'; import ReactDOM from 'react-dom'; import Layout from './dashboard/layout/layout'; + ReactDOM.render( React.createElement(Layout), document.getElementById('root') diff --git a/client/config/api.js b/client/config/api.js new file mode 100644 index 0000000..cc0bafa --- /dev/null +++ b/client/config/api.js @@ -0,0 +1 @@ +api.js diff --git a/client/config/store.js b/client/config/store.js new file mode 100644 index 0000000..b1c4591 --- /dev/null +++ b/client/config/store.js @@ -0,0 +1 @@ +store.js diff --git a/client/dashboard/layout/layout.js b/client/dashboard/layout/layout.js index 050df02..09c6871 100644 --- a/client/dashboard/layout/layout.js +++ b/client/dashboard/layout/layout.js @@ -1,11 +1,21 @@ import React from 'react'; - import layoutRt from './layout.rt.js'; +import House from './../../models/house'; + +const VIEWS = [['power', 'Power Savings'], ['energy', 'Energy Production']]; var Layout = React.createClass({ getInitialState: function(){ - return {view: "????"}; + var layout = this; + layout.view_name = VIEWS[0][1]; + return { + views: VIEWS, + houses: null, + house: null, + view: 'power', + requesting_data: true + }; }, handleResize: function(e) { @@ -13,13 +23,49 @@ var Layout = React.createClass({ }, componentDidMount: function() { - window.addEventListener('resize', this.handleResize); + var layout = this; + // window.addEventListener('resize', this.handleResize); + House.ensureHouses().then((houses)=>{ + layout.setState({houses: houses, house: houses[0]}); + layout.ensureHouseViewData(); + }); }, - setView: function(event) { - var layout = this; - console.log(event.target.value) - layout.setState({view: event.target.value}); + setView: function(event){ + var layout = this, + view = event.target.value; + layout.view_name = event.target.innerText; + layout.setState({view: view}); + layout.ensureHouseViewData(); + }, + + setHouse: function(event){ + var layout = this, + house_id = event.target.value, + house = layout.state.houses.find((house)=>{ return house.data.id == house_id }); + layout.setState({house: house}, function(){ + layout.ensureHouseViewData(); + }); + }, + + ensureHouseViewData: function(){ + var layout = this, + house = layout.state.house, + view = layout.state.view, + request; + layout.setState({requesting_data: true}, ()=>{ + if (view === 'power'){ + request = house.ensurePowerData(); + } else { + request = house.ensureEnergyData(); + } + request.then(()=>{ + console.log('data retrieved') + layout.setState({requesting_data: false}, ()=>{ + console.log(layout.state.requesting_data); + }); + }); + }); }, render: function() { diff --git a/client/dashboard/layout/layout.rt b/client/dashboard/layout/layout.rt index fed8186..6bc107d 100644 --- a/client/dashboard/layout/layout.rt +++ b/client/dashboard/layout/layout.rt @@ -1,8 +1,54 @@ -
-

{this.state.view}

- +
Loading data...
+

{this.state.house.name}

+

{this.view_name}

+ +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + +
DayConsumption (Wh)Production (Wh)
{energy_datum.day_to_s}{energy_datum.consumption_to_s}{energy_datum.production_to_s}
+ + + + + + + + + + + + + + + + + + +
TimeConsumption (W)Production (W)
{power_datum.time_to_s}{power_datum.consumption_to_s}{power_datum.production_to_s}
diff --git a/client/dashboard/layout/layout.rt.js b/client/dashboard/layout/layout.rt.js index 67f1406..db1a687 100644 --- a/client/dashboard/layout/layout.rt.js +++ b/client/dashboard/layout/layout.rt.js @@ -1,5 +1,45 @@ import React from 'react'; import _ from 'lodash'; +function repeatView1(view, viewIndex) { + return React.createElement('option', { + 'value': view[0], + 'key': 'view-' + view[0] + }, view[1]); +} +function repeatHouse2(house, houseIndex) { + return React.createElement('option', { + 'value': house.data.id, + 'key': house.react_key + }, house.data.name); +} +function repeatEnergy_datum3(energy_datum, energy_datumIndex) { + return React.createElement('tr', { 'key': energy_datum.react_key }, React.createElement('td', {}), React.createElement('td', {}, energy_datum.day_to_s), React.createElement('td', {}, energy_datum.consumption_to_s), React.createElement('td', {}, energy_datum.production_to_s)); +} +function repeatPower_datum4(power_datum, power_datumIndex) { + return React.createElement('tr', { 'key': power_datum.react_key }, React.createElement('td', {}), React.createElement('td', {}, power_datum.time_to_s), React.createElement('td', {}, power_datum.consumption_to_s), React.createElement('td', {}, power_datum.production_to_s)); +} export default function () { - return React.createElement('div', { 'id': 'layout' }, React.createElement('h1', {}, this.state.view), React.createElement('select', { 'onChange': this.setView }, React.createElement('option', { 'value': 'savings' }, 'Savings'), React.createElement('option', { 'value': 'production' }, 'Production'))); + return React.createElement('div', { 'id': 'layout' }, this.state.requesting_data ? React.createElement('div', { 'className': 'alert alert-warning' }, 'Loading data...') : null, this.state.house ? React.createElement('h1', {}, this.state.house.name) : null, this.state.view ? React.createElement('h3', {}, this.view_name) : null, React.createElement('div', {}, React.createElement.apply(this, [ + 'select', + { + 'className': 'form-control', + 'onChange': this.setView + }, + _.map(this.state.views, repeatView1.bind(this)) + ])), this.state.houses ? React.createElement('div', {}, React.createElement.apply(this, [ + 'select', + { + 'className': 'form-control', + 'onChange': this.setHouse + }, + _.map(this.state.houses, repeatHouse2.bind(this)) + ])) : null, this.state.view === 'energy' && this.state.house ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Day'), React.createElement('th', {}, 'Consumption (Wh)'), React.createElement('th', {}, 'Production (Wh)'))), React.createElement.apply(this, [ + 'tbody', + {}, + _.map(this.state.house.energy_data, repeatEnergy_datum3.bind(this)) + ])) : null, this.state.view === 'power' && this.state.house ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Time'), React.createElement('th', {}, 'Consumption (W)'), React.createElement('th', {}, 'Production (W)'))), React.createElement.apply(this, [ + 'tbody', + {}, + _.map(this.state.house.power_data, repeatPower_datum4.bind(this)) + ])) : null); }; \ No newline at end of file diff --git a/client/models/energy_datum.js b/client/models/energy_datum.js index a3d58ec..25c37d1 100644 --- a/client/models/energy_datum.js +++ b/client/models/energy_datum.js @@ -1,13 +1,17 @@ import extend from 'extend'; - +import moment from 'moment-timezone'; class EnergyDatum { - __constructor(data, house){ + constructor(data, house){ var energy_datum = this; energy_datum.house = house; + data.day = moment.tz(data.day, house.data.timezone); energy_datum.data = data; - moment.tz(data.day, house.data.timezone); - EnergyDatum.store[data.id] energy_datum; + EnergyDatum.store.set(data.id, energy_datum); + } + + get react_key(){ + return `energy-datum-${this.data.id}`; } get day_to_date(){ @@ -17,6 +21,21 @@ class EnergyDatum { return moment(moment_tz.toArray()).toDate(); } + get day_to_s(){ + var energy_datum = this; + return energy_datum.data.day.format('YYYY-MM-DD'); + } + + get consumption_to_s(){ + var energy_datum = this; + return Math.round(energy_datum.data.consumption); + } + + get production_to_s(){ + var energy_datum = this; + return Math.round(energy_datum.data.production); + } + update(data){ var energy_datum = this, house = power_datum.house; @@ -24,11 +43,13 @@ class EnergyDatum { extend(energy_datum.data, data); } - static updateOrInitialize(id, data, house){ - var energy_datum = EnergyDatum.store.get(id); + static updateOrInitialize(data, house){ + var energy_datum = EnergyDatum.store.get(data.id); if (energy_datum) energy_datum.update(data); return energy_datum || new EnergyDatum(data, house) } } EnergyDatum.store = new Map(); + +export default EnergyDatum; diff --git a/client/models/house.js b/client/models/house.js index d8a3499..730e612 100644 --- a/client/models/house.js +++ b/client/models/house.js @@ -1,38 +1,56 @@ -import Api from './../api'; -import Store from './../store'; -import ArrayUtil from './../../shared/util/array' +import extend from 'extend'; -class House extends Base { +import PowerDatum from './power_datum'; +import EnergyDatum from './energy_datum'; +import PowerDataApi from './../api/power_data'; +import EnergyDataApi from './../api/energy_data'; +import HousesApi from './../api/houses'; +import ArrayUtil from './../../shared/utils/array' +import MathUtil from './../../shared/utils/math' - __constructor(data, house){ - var energy_datum = this; +class House { + + constructor(data){ + var house = this; House.store.set(data.id, house); house.data = data; - house.energy_data = new Map(); - house.power_data = new Map(); + house.energy_data = []; + house.power_data = []; + house.energy_data_store = new Map(); + house.power_data_store = new Map(); } - ensurePowerData(start_date, end_date){ + get react_key(){ + return `house-${this.data.id}`; + } + + ensurePowerData(opts){ + opts = extend({ + start_date: undefined, + end_date: undefined + }, opts || {}); var house = this, - date_range = Array.from(house.power_data.keys()), + date_range = Array.from(house.power_data_store.keys()), min_date = Math.min(date_range), max_date = Math.max(date_range), query_ranges, cache; - if (date_range.length === 0) return house.getPowerData({dates: [[start_date, end_date]]}) + if (date_range.length === 0){ + return house.getPowerData({dates: [[opts.start_date, opts.end_date]]}); + } - query_ranges = MathUtil.minusRange([start_date, end_date], [min_date, max_date]); + query_ranges = MathUtil.minusRange([opts.start_date, opts.end_date], [min_date, max_date]); cache = ArrayUtil.selectMap(date_range, (datum_time)=>{ return ArrayUtil.all(query_ranges, (query_range)=>{ !MathUtil.inRange(datum_time, query_range); - })); + }); }, (datum_time)=>{ - return house.power_data.get(datum_time); + return house.power_data_store.get(datum_time); }); if (query_ranges.length > 0){ - return house.getPowerData({dates: dates}).then((new_power_data)=>{ + return house.getPowerData({dates: query_ranges}).then((new_power_data)=>{ return new_power_data.concat(cache); }); } else return Promise.resolve(cache); @@ -41,37 +59,42 @@ class House extends Base { getPowerData(params){ var house = this; params.house_id = house.data.id; - return Api.PowerData.index(params).then((power_data)=>{ + return PowerDataApi.index(params).then((power_data)=>{ return power_data.map((power_datum_data)=>{ - var power_datum = Store.PowerDatum.updateOrInitialize(power_datum_data, house); - house.power_data.set(power_datum.data.time, power_datum); + var power_datum = PowerDatum.updateOrInitialize(power_datum_data, house); + house.power_data_store.set(power_datum.data.time, power_datum); + house.power_data.push(power_datum); return power_datum; }); }); } - ensureEnergyData(start_date, end_date){ + ensureEnergyData(opts){ + opts = extend({ + start_date: undefined, + end_date: undefined + }, opts || {}); var house = this, - date_range = Array.from(house.energy_data.keys()), + date_range = Array.from(house.energy_data_store.keys()), min_date = Math.min(date_range), max_date = Math.max(date_range), query_ranges, cache; - if (date_range.length === 0) return house.getEnergyData({dates: [[start_date, end_date]]}) + if (date_range.length === 0) return house.getEnergyData({dates: [[opts.start_date, opts.end_date]]}) query_ranges = MathUtil.minusRange([start_date, end_date], [min_date, max_date]); cache = ArrayUtil.selectMap(date_range, (datum_day)=>{ return ArrayUtil.all(query_ranges, (query_range)=>{ - !MathUtil.inRange(datum_day, query_range); - })); + return !MathUtil.inRange(datum_day, query_range); + }); }, (datum_day)=>{ - return house.energy_data.get(datum_day); + return house.energy_data_store.get(datum_day); }); if (query_ranges.length > 0){ - return house.getEnergyData({dates: dates}).then((new_energy_data)=>{ - return new_energy_data.concat(cache); + return house.getEnergyData({dates: query_ranges}).then((new_energy_data)=>{ + return new_energy_data_store.concat(cache); }); } else return Promise.resolve(cache); } @@ -79,10 +102,11 @@ class House extends Base { getEnergyData(params){ var house = this; params.house_id = house.data.id; - return Api.PowerData.index(params).then((energy_data)=>{ - return power_data.map((energy_datum_data)=>{ - var energy_datum = Store.EnergyDatum.updateOrInitialize(energy_datum_data, house); - house.energy_data.set(power_datum.time, energy_datum); + return EnergyDataApi.index(params).then((energy_data)=>{ + return energy_data.map((energy_datum_data)=>{ + var energy_datum = EnergyDatum.updateOrInitialize(energy_datum_data, house); + house.energy_data_store.set(power_datum.time, energy_datum); + house.energy_data.push(energy_datum); return energy_datum; }); }); @@ -100,18 +124,21 @@ class House extends Base { } static ensureHouses(ids){ - var required_ids = ArrayUtil.diff(ids, House.store.keys()), + var required_ids, cached_houses = []; + if (ids){ + required_ids = ArrayUtil.diff(ids, House.store.keys()); cached_houses = ArrayUtil.diff(ids, required_ids).map((id)=>{ return House.store.get(id); }); - if (required_ids.length == 0) return Promise.resolve([]); + } + if (required_ids && required_ids.length == 0) return Promise.resolve([]); - return House.getHouses(required_ids).then((new_houses){ + return House.getHouses(required_ids).then((new_houses)=>{ // if these need to be ordered, then concatenation needs to be merged in order. return new_houses.concat(cached_houses); }); } static getHouses(ids){ - return Api.HousesApi.index({id: ids}).then((houses_data)=>{ + return HousesApi.index({id: ids}).then((houses_data)=>{ return houses_data.map((house_data)=>{ return new House(house_data); }); diff --git a/client/models/power_datum.js b/client/models/power_datum.js index bd4ac26..22b3c88 100644 --- a/client/models/power_datum.js +++ b/client/models/power_datum.js @@ -1,13 +1,17 @@ import extend from 'extend'; - +import moment from 'moment-timezone'; class PowerDatum { - __constructor(data, house){ + constructor(data, house){ var power_datum = this; power_datum.house = house; + data.time = moment.tz(data.time, house.data.timezone); power_datum.data = data; - moment.format(data.time); - PowerDatum.store[data.id] power_datum; + PowerDatum.store.set(data.id, power_datum); + } + + get react_key(){ + return `power-datum-${this.data.id}`; } get time_to_date(){ @@ -17,6 +21,19 @@ class PowerDatum { return moment(moment_tz.toArray()).toDate(); } + get time_to_s(){ + var power_datum = this; + return power_datum.data.time.format('YYYY-MM-DD HH:MM'); + } + get consumption_to_s(){ + var power_datum = this; + return Math.round(power_datum.data.consumption); + } + get production_to_s(){ + var power_datum = this; + return Math.round(power_datum.data.production); + } + update(data){ var power_datum = this, house = power_datum.house; @@ -24,8 +41,8 @@ class PowerDatum { extend(power_datum.data, data); } - static updateOrInitialize(id, data, house){ - var power_datum = PowerDatum.store.get(id); + static updateOrInitialize(data, house){ + var power_datum = PowerDatum.store.get(data.id); if (power_datum) power_datum.update(data); return power_datum || new PowerDatum(data, house) } @@ -33,3 +50,5 @@ class PowerDatum { } PowerDatum.store = new Map(); + +export default PowerDatum; diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index f818a33..0000000 --- a/npm-debug.log +++ /dev/null @@ -1,45 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'start' ] -2 info using npm@3.5.3 -3 info using node@v5.4.1 -4 verbose run-script [ 'prestart', 'start', 'poststart' ] -5 info lifecycle spike_proto@0.0.0~prestart: spike_proto@0.0.0 -6 silly lifecycle spike_proto@0.0.0~prestart: no script for prestart, continuing -7 info lifecycle spike_proto@0.0.0~start: spike_proto@0.0.0 -8 verbose lifecycle spike_proto@0.0.0~start: unsafe-perm in lifecycle true -9 verbose lifecycle spike_proto@0.0.0~start: PATH: /usr/local/lib/node_modules/npm/bin/node-gyp-bin:/home/eric/Code/spike2/node_modules/.bin:/home/eric/.rvm/gems/ruby-1.9.3-p484@oroeco_dev/bin:/home/eric/.rvm/gems/ruby-1.9.3-p484@global/bin:/home/eric/.rvm/rubies/ruby-1.9.3-p484/bin:/home/eric/.rvm/bin:/home/eric/bin:/usr/local/heroku/bin:/home/eric/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/lampp/bin -10 verbose lifecycle spike_proto@0.0.0~start: CWD: /home/eric/Code/spike2 -11 silly lifecycle spike_proto@0.0.0~start: Args: [ '-c', 'babel-node ./server/app.express.js' ] -12 silly lifecycle spike_proto@0.0.0~start: Returned: code: 1 signal: null -13 info lifecycle spike_proto@0.0.0~start: Failed to exec start script -14 verbose stack Error: spike_proto@0.0.0 start: `babel-node ./server/app.express.js` -14 verbose stack Exit status 1 -14 verbose stack at EventEmitter. (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:232:16) -14 verbose stack at emitTwo (events.js:87:13) -14 verbose stack at EventEmitter.emit (events.js:172:7) -14 verbose stack at ChildProcess. (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:24:14) -14 verbose stack at emitTwo (events.js:87:13) -14 verbose stack at ChildProcess.emit (events.js:172:7) -14 verbose stack at maybeClose (internal/child_process.js:821:16) -14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5) -15 verbose pkgid spike_proto@0.0.0 -16 verbose cwd /home/eric/Code/spike2 -17 error Linux 3.19.0-49-generic -18 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "start" -19 error node v5.4.1 -20 error npm v3.5.3 -21 error code ELIFECYCLE -22 error spike_proto@0.0.0 start: `babel-node ./server/app.express.js` -22 error Exit status 1 -23 error Failed at the spike_proto@0.0.0 start script 'babel-node ./server/app.express.js'. -23 error Make sure you have the latest version of node.js and npm installed. -23 error If you do, this is most likely a problem with the spike_proto package, -23 error not with npm itself. -23 error Tell the author that this fails on your system: -23 error babel-node ./server/app.express.js -23 error You can get information on how to open an issue for this project with: -23 error npm bugs spike_proto -23 error Or if that isn't available, you can get their info via: -23 error npm owner ls spike_proto -23 error There is likely additional logging output above. -24 verbose exit [ 1, true ] diff --git a/package.json b/package.json index cf47781..7105210 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "raw-loader": "0.5.1", "sass-loader": "3.1.2", "style-loader": "^0.12.3", + "json-loader": "0.5.4", "node-sass": "3.4.2", "moment-timezone":"0.5.0", "yargs": "3.32.0", diff --git a/server/app.express.js b/server/app.express.js index 2726f4a..ff46718 100644 --- a/server/app.express.js +++ b/server/app.express.js @@ -45,7 +45,7 @@ var config = require('./config/webpack/development'), contentBase: __dirname + '/../client/build/development', publicPath: "/assets/", proxy: { - '/data': `http://localhost:${APP_PORT}`, + '/data*': `http://localhost:${API_PORT}`, }, stats: {colors: true} }), diff --git a/server/config/webpack/development.js b/server/config/webpack/development.js index 5b3ce84..aa8502b 100644 --- a/server/config/webpack/development.js +++ b/server/config/webpack/development.js @@ -11,10 +11,6 @@ module.exports = { filename: '[name].js', path: ROOT + 'client/build/development' }, - externals: { - jquery: "$", - d3: "d3" - }, module: { loaders: [ { @@ -26,6 +22,9 @@ module.exports = { }, { test: /\.js$/, loader: 'babel' + }, { + test: /\.json$/, + loader: 'json-loader' } ] }, @@ -37,6 +36,10 @@ module.exports = { $: "jquery", jQuery: "jquery", "window.jQuery": "jquery" + }), + new webpack.ProvidePlugin({ + d3: "d3", + "window.d3": "d3" }) ] } diff --git a/server/controllers/energy_controller.js b/server/controllers/energy_controller.js index e3bc2c9..06c05d0 100644 --- a/server/controllers/energy_controller.js +++ b/server/controllers/energy_controller.js @@ -5,10 +5,8 @@ const NAME = 'EnergyController'; class EnergyController{ static index(req, res){ - DB.House.findOne({where: {name: req.housename}}).then((house)=>{ - house.getEnergyDataByTime(req.params.start_time, req.params.end_time).then((energy_data)=>{ - req.json(energy_data); - }); + DB.EnergyDatum.exposeForHouseAtDates(req.query.house_id, req.query.dates).then((energy_data)=>{ + req.json({data: energy_data}); }); } diff --git a/server/controllers/houses_controller.js b/server/controllers/houses_controller.js index 7a14c68..9b4c328 100644 --- a/server/controllers/houses_controller.js +++ b/server/controllers/houses_controller.js @@ -1,12 +1,14 @@ import DB from './../config/database.js'; -const NAME = HousesController; +const NAME = 'HousesController'; -class HousesController{ +class HousesController { static index(req, res){ - DB.House.findAll({attributes: ['id', 'name']}).then((houses)=>{ - res.json(houses); + var params = {}; + if (req.query.ids) query.id = ids; + DB.House.findAll({where: params, attributes: ['id', 'name', 'timezone']}).then((houses)=>{ + res.json({data: houses.map((house)=>{ return house.dataValues; })}); }); } diff --git a/server/controllers/power_controller.js b/server/controllers/power_controller.js index b8f132f..59ca459 100644 --- a/server/controllers/power_controller.js +++ b/server/controllers/power_controller.js @@ -5,10 +5,9 @@ const NAME = 'PowerController'; class PowerController{ static index(req, res){ - DB.House.findOne({where: {name: req.housename}}).then((house)=>{ - house.getPowerDataByTime(req.params.start_time, req.params.end_time).then((power_data)=>{ - res.json(power_data); - }); + console.log(req.query); + DB.PowerDatum.exposeForHouseAtDates(req.query.house_id, req.query.dates).then((power_data)=>{ + res.json({data: power_data}); }); } diff --git a/server/helpers/api_helper.js b/server/helpers/api_helper.js new file mode 100644 index 0000000..a62754a --- /dev/null +++ b/server/helpers/api_helper.js @@ -0,0 +1,31 @@ +import moment from 'moment'; + +class ApiHelper { + + // assume all dates from api coming as UNIX timestamps. + static datesParamToSequelize(dates, field_name){ + if (!dates) return {}; + var params = {}; + + if (dates.length > 1){ + params['$or'] = []; + dates.forEach((min_max)=>{ + var condition_n = {}; + condition_n[field_name] = {}; + if (min_max[0]) condition_n[field_name]['$gt'] = moment.unix(min_max[0]).toDate(); + if (min_max[1]) condition_n[field_name]['$lt'] = moment.unix(min_max[1]).toDate(); + if (Object.keys(condition_n).length) params['$or'].push(condition_n); + }); + } else { + var min_max = dates[0], + condition = {} + if (min_max[0]) params[field_name]['$gt'] = moment.unix(min_max[0]).toDate(); + if (min_max[1]) params[field_name]['$lt'] = moment.unix(min_max[1]).toDate(); + if (Object.keys(condition).length) params[field_name] = condition; + } + return params; + } + +} + +export default ApiHelper; diff --git a/server/models/energy_datum.js b/server/models/energy_datum.js index 709e8ff..77cae18 100644 --- a/server/models/energy_datum.js +++ b/server/models/energy_datum.js @@ -1,4 +1,6 @@ import DB from "./../config/database"; +import extend from 'extend'; +import ApiHelper from './../helpers/api_helper'; const NAME = 'EnergyDatum'; @@ -20,7 +22,12 @@ var EnergyDatum = DB.sequelize.define(NAME, { underscored: true, tableName: "energy_data", instanceMethods: { - + exposeToApi: ()=>{ + var energy_datum = this, + values = this.dataValues; + values.energy_datum = energy_datum.day.getTime() / 1000; + return values; + } }, classMethods: { set: ()=>{ @@ -28,6 +35,18 @@ var EnergyDatum = DB.sequelize.define(NAME, { }, associate: ()=>{ EnergyDatum.belongsTo(DB.House); + }, + exposeForHouseAtDates: (house_id, dates)=>{ + var params = {house_id: house_id}; + extend(params, ApiHelper.datesParamToSequelize(dates, 'day')); + return EnergyDatum.findAll({ + where: params, + attributes: ['id', 'production', 'consumption', 'day'] + }).then((energy_data)=>{ + return energy_data.map((energy_datum)=>{ + return energy_datum.exposeToApi(); + }); + }); } } }); diff --git a/server/models/house.js b/server/models/house.js index 2084486..ad3d453 100644 --- a/server/models/house.js +++ b/server/models/house.js @@ -44,8 +44,6 @@ var House = DB.sequelize.define(NAME, { power_data.forEach((power_datum)=>{ var day = house.timeToDateString(power_datum.time), energy_datum = energy_data.get(day) || {production: 0, consumption: 0, day: day, house_id: house.id}; - console.log(power_datum.time) - console.log(day) energy_datum.production += power_datum.production; energy_datum.consumption += power_datum.consumption; energy_data.set(day, energy_datum); @@ -62,10 +60,10 @@ var House = DB.sequelize.define(NAME, { associate: ()=>{ House.hasMany(DB.PowerDatum, {as: 'PowerData'}); }, - getPowerDataByTime: (start_date, end_date)=>{ + getPowerDataByTime: (dates)=>{ var params = { where: {time: {}}, - attributes: ['time', 'consumption', 'production'] + attributes: ['id', 'time', 'consumption', 'production'] }; if (start_date) params.where.time.$gt = moment.utc(start_date).toDate(); if (end_date) params.where.time.$lt = moment.utc(end_date).toDate(); diff --git a/server/models/power_datum.js b/server/models/power_datum.js index e9fed99..36bc3b2 100644 --- a/server/models/power_datum.js +++ b/server/models/power_datum.js @@ -1,4 +1,6 @@ import DB from "./../config/database"; +import extend from 'extend'; +import ApiHelper from './../helpers/api_helper'; const NAME = 'PowerDatum'; @@ -20,9 +22,28 @@ var PowerDatum = DB.sequelize.define(NAME, { underscored: true, tableName: "power_data", instanceMethods: { - + exposeToApi: function(){ + var power_datum = this, + data = power_datum.dataValues; + data.consumption = data.consumption * 4; // convert Wh / 15 minutes, to W + data.production = data.production * 4; // convert Wh / 15 minutes, to W + return data; + } }, classMethods: { + exposeForHouseAtDates: (house_id, dates)=>{ + var params = {house_id: house_id}; + params = extend(params, ApiHelper.datesParamToSequelize(dates, 'time')); + console.log(params); + return PowerDatum.findAll({ + where: params, + attributes: ['id', 'production', 'consumption', 'time'] + }).then((power_data)=>{ + return power_data.map((power_datum)=>{ + return power_datum.exposeToApi(); + }); + }); + }, set: ()=>{ PowerDatum.associate(); }, diff --git a/server/routes.js b/server/routes.js index ae92e45..3502aa6 100644 --- a/server/routes.js +++ b/server/routes.js @@ -4,8 +4,8 @@ export default function(app){ Controllers.sync(); - app.use('/data/v1/savings/:housename', Controllers.PowerController.index); - app.use('/data/v1/production/:housename', Controllers.EnergyController.index); - app.use('/data/v1/houses/', Controllers.EnergyController.index); + app.use('/data/v1/power', Controllers.PowerController.index); + app.use('/data/v1/energy', Controllers.EnergyController.index); + app.use('/data/v1/houses', Controllers.HousesController.index); }; diff --git a/shared/utils/array.js b/shared/utils/array.js index 24dc1a9..bc333bf 100644 --- a/shared/utils/array.js +++ b/shared/utils/array.js @@ -1,15 +1,13 @@ class ArrayUtil { static diff(a1, a2){ - a1.filter((a1n)=>{ return a2.indexOf(a1n) < 0; }); + return a1.filter((a1n)=>{ return a2.indexOf(a1n) < 0; }); } static selectMap(a, fnSelect, fnMap){ var map = []; for (var elem of a){ - if (fnSelect(elem)){ - map.push(fnMap(elem)); - } + if (fnSelect(elem)) map.push(fnMap(elem)); } return map; } diff --git a/shared/utils/math.js b/shared/utils/math.js index 802ee37..ef78d36 100644 --- a/shared/utils/math.js +++ b/shared/utils/math.js @@ -26,7 +26,9 @@ export default class { } static inRange(n, min_max){ - return n >= min_max[0] && n =< min_max[1]; + var min = min_max[0], + max = min_max[1]; + return ((n >= min) && (n <= max)); } }