diff --git a/nodeapp/app.js b/nodeapp/app.js index c91963d..0180877 100644 --- a/nodeapp/app.js +++ b/nodeapp/app.js @@ -1,7 +1,6 @@ // Initialize project -const db = require('./db/db') +const db = require('./src/db') const path = require('path') -const twit = require('twit') require('dotenv').config({path: path.join(__dirname, '../.env')}) const express = require('express'); const app = express(); @@ -18,72 +17,6 @@ app.get('/newreading', (req, res) => { db.Readings.create({ timestamp: new Date(), reading: req.query.v }); res.status(200).send('Reading received: ' + req.query.v).end(); - - // Fetch all settings - db.Settings.findAll().then((result) => { - // Reset low moisture flag if it's set, and the reading is greater than high-trigger - if (result.find(o => o.dataValues.key === 'low').dataValues.value !== '0' && req.query.v >= parseInt(result.find(o => o.dataValues.key === 'high-trigger').dataValues.value)) { - // Pick a random message from the array below - const tweet = [ - 'Thanks to whoever watered me just now!! So moist.', - 'So refreshing! Thank you, mysterious stranger, for the life-giving sustenance.', - 'Whoever watered me (I\'m kind of guessing it was you, @Katheryn_mur), know that I appreciate it!' - ] - - // Define options for the twitter API - const msg = new twit({ - consumer_key: process.env.CONSUMER_KEY, - consumer_secret: process.env.CONSUMER_SECRET, - access_token: process.env.ACCESS_TOKEN, - access_token_secret: process.env.ACCESS_TOKEN_SECRET - }) - - msg.post('statuses/update', { status: tweet[Math.floor(Math.random() * tweet.length)] }) - - // Update low to false - db.Settings.update({ value: false }, { where: { key: 'low' } }) - // Update alert to false - db.Settings.update({ value: false }, { where: { key: 'alert' } }) - } - - // Set the low moisture flag if it's not set, and the value is less than low-trigger - if (result.find(o => o.dataValues.key === 'low').dataValues.value === '0' && req.query.v <= parseInt(result.find(o => o.dataValues.key === 'low-trigger').dataValues.value)) { - // Update low to true - db.Settings.update({ value: new Date().toISOString() }, { where: { key: 'low' } }) - } - - // If the low moisture flag is set but an alert has not yet been sent, determine if an alert is necessarry - if (result.find(o => o.dataValues.key === 'low').dataValues.value !== '0' && result.find(o => o.dataValues.key === 'alert').dataValues.value === '0') { - const lowtriggered = new Date().getTime() - new Date(result.find(o => o.dataValues.key === 'low').dataValues.value).getTime() - const fourdays = 4 * 24 * 60 * 60 * 1000 - - // If the first low reading was more than 5 days ago, send an alert - if (lowtriggered > fourdays) { - // Pick a random message from the array below - const tweet = [ - 'I\'m getting pretty thirsty over here, @JayWll', - 'So. Very. Dry. You know I\'m a succulent not a cactus, right @JayWll? 🌵', - 'Hey @JayWll, water me. 🥛', - 'They say a man shall not live by bread alone, and a succulent won\'t live if you don\'t WATER ME @JAYWLL!!1! 🍞🚰💦', - '@JayWll seriously bro. A little water. That\'s all I ask.' - ] - - // Define options for the twitter API - const msg = new twit({ - consumer_key: process.env.CONSUMER_KEY, - consumer_secret: process.env.CONSUMER_SECRET, - access_token: process.env.ACCESS_TOKEN, - access_token_secret: process.env.ACCESS_TOKEN_SECRET - }) - - // Post a tweet - msg.post('statuses/update', { status: tweet[Math.floor(Math.random() * tweet.length)] }) - - // Flag that an alert has been sent - db.Settings.update({ value: new Date().toISOString() }, { where: { key: 'alert' } }) - } else console.log('Low for less than 5 days') - } else console.log('No message needed') - }) }); // Handle requests for /getdata by returning a JSON object of data for the relevant time period @@ -124,6 +57,11 @@ app.get('/exportall', (req, res) => { // Handle requests for /showsettings by retrieving all settings from the database and returning a JSON object app.get('/showsettings', (req, res) => { + // Check that the expected key has been included with the web request + if (!req.headers['export-key'] || req.headers['export-key'] != process.env.SECRET) { + return res.status(401).send('Authorization header not found').end(); + } + db.Settings.findAll().then((result) => { res.status(200).send(result).end(); }) diff --git a/nodeapp/db/db.js b/nodeapp/db/db.js deleted file mode 100644 index 42053d0..0000000 --- a/nodeapp/db/db.js +++ /dev/null @@ -1,32 +0,0 @@ -const Sequelize = require('sequelize') -const Op = Sequelize.Op - -// Setup database, using credentials set in .env -const sequelize = new Sequelize('database', process.env.DB_USER, process.env.DB_PASS, { - dialect: 'sqlite', - logging: false, - storage: '.data/database.sqlite' -}) - -// Define 'readings' table structure -const Readings = sequelize.define('readings', { - timestamp: { - type: Sequelize.DATE - }, - reading: { - type: Sequelize.INTEGER - } -}) - -// Define 'settings' table structure -const Settings = sequelize.define('settings', { - key: { - type: Sequelize.STRING, - unique: true - }, - value: { - type: Sequelize.STRING - } -}) - -module.exports = {Op, Readings, Settings} diff --git a/nodeapp/src/alert.js b/nodeapp/src/alert.js new file mode 100644 index 0000000..e55424e --- /dev/null +++ b/nodeapp/src/alert.js @@ -0,0 +1,48 @@ +const path = require('path') +const twit = require('twit') +require('dotenv').config({path: path.join(__dirname, '../../.env')}) + +const alert = (type) => { + console.log('Alert triggered for type: ' + type) + var tweet = false + + switch(type) { + // Water is needed + case 'wantwater': + tweet = [ + 'I\'m getting pretty thirsty over here, @JayWll', + 'So. Very. Dry. You know I\'m a succulent not a cactus, right @JayWll? 🌵', + 'Hey @JayWll, water me. 🥛', + 'They say a man shall not live by bread alone, and a succulent won\'t live if you don\'t WATER ME @JAYWLL!!1! 🍞🚰💦', + '@JayWll seriously bro. A little water. That\'s all I ask.' + ] + break + + // Water has been received + case 'gotwater': + tweet = [ + 'Thanks to whoever watered me just now!! So moist.', + 'So refreshing! Thank you, mysterious stranger, for the life-giving liquidy sustenance.', + 'Whoever watered me (I\'m kind of guessing it was you, @Katheryn_mur), know that I appreciate it!' + ] + break + } + + if (!tweet) { + console.log('nothing to send') + return + } + + // Define options for the twitter API + const msg = new twit({ + consumer_key: process.env.CONSUMER_KEY, + consumer_secret: process.env.CONSUMER_SECRET, + access_token: process.env.ACCESS_TOKEN, + access_token_secret: process.env.ACCESS_TOKEN_SECRET + }) + + // Post tweet + msg.post('statuses/update', { status: tweet[Math.floor(Math.random() * tweet.length)] }) +} + +module.exports = alert diff --git a/nodeapp/src/db.js b/nodeapp/src/db.js new file mode 100644 index 0000000..7b54517 --- /dev/null +++ b/nodeapp/src/db.js @@ -0,0 +1,79 @@ +const Sequelize = require('sequelize') +const Op = Sequelize.Op +const message = require('./alert') + +// Setup database, using credentials set in .env +const sequelize = new Sequelize('database', process.env.DB_USER, process.env.DB_PASS, { + dialect: 'sqlite', + logging: false, + storage: '.data/database.sqlite' +}) + +// Define 'readings' table structure +const Readings = sequelize.define('readings', { + timestamp: { + type: Sequelize.DATE + }, + reading: { + type: Sequelize.INTEGER + } +}, { + hooks: { + afterCreate: (data, opts) => { + // Fetch all settings + Settings.findAll().then((result) => { + const lowtrigger = parseInt(result.find(o => o.dataValues.key === 'low-trigger').dataValues.value) + const hightrigger = parseInt(result.find(o => o.dataValues.key === 'high-trigger').dataValues.value) + const low = result.find(o => o.dataValues.key === 'low').dataValues.value + const alert = result.find(o => o.dataValues.key === 'low-trigger').dataValues.value + + + // Reset low moisture flag if it's set, and the reading is greater than high-trigger + if (low !== '0' && data.reading >= hightrigger) { + // Send alert that water was received + message('gotwater') + + // Update low to false + Settings.update({ value: false }, { where: { key: 'low' } }) + + // Update alert to false + Settings.update({ value: false }, { where: { key: 'alert' } }) + } + + // Set the low moisture flag if it's not set, and the value is less than low-trigger + if (low === '0' && data.reading <= lowtrigger) { + // Update low to true + Settings.update({ value: new Date().toISOString() }, { where: { key: 'low' } }) + } + + // If the low moisture flag is set but an alert has not yet been sent, determine if an alert is necessarry + if (low !== '0' && alert === '0') { + const lowtriggered = new Date().getTime() - new Date(low).getTime() + const fourdays = 4 * 24 * 60 * 60 * 1000 + + // If the first low reading was more than 4 days ago, send an alert + if (lowtriggered > fourdays) { + // Send alert that water is needed + message('needwater') + + // Flag that an alert has been sent + Settings.update({ value: new Date().toISOString() }, { where: { key: 'alert' } }) + } + } + }) + } + } +}) + +// Define 'settings' table structure +const Settings = sequelize.define('settings', { + key: { + type: Sequelize.STRING, + unique: true + }, + value: { + type: Sequelize.STRING + } +}) + +module.exports = {Op, Readings, Settings} diff --git a/nodeapp/utils/import.js b/nodeapp/utils/import.js index 0413359..9ecf511 100644 --- a/nodeapp/utils/import.js +++ b/nodeapp/utils/import.js @@ -1,14 +1,15 @@ -const db = require('../db/db') +const db = require('../src/db') const request = require('request') const path = require('path') require('dotenv').config({path: path.join(__dirname, '../../.env')}) // The URL from which to export production data -const url = 'https://jasonsplant.glitch.me/exportall' +const readingsurl = 'https://jasonsplant.glitch.me/exportall' +const settingsurl = 'https://jasonsplant.glitch.me/showsettings' -// Get data from the production server +// Get readings from the production server request({ - url, + url: readingsurl, headers: { 'export-key': process.env.SECRET }, @@ -17,10 +18,7 @@ request({ if (error) return console.log(error) if (response.statusCode != 200) return console.log('Unsuccessful, as indicated by HTTP status') - console.log(response.body.length + ' items retrieved') - - // Sync the database table - await db.Settings.sync() + console.log(response.body.length + ' readings retrieved') // Remove existing database items await db.Readings.destroy({truncate: true}) @@ -28,3 +26,23 @@ request({ // Add new data to the database await db.Readings.bulkCreate(response.body) }); + +// Get settings from the production server +request({ + url: settingsurl, + headers: { + 'export-key': process.env.SECRET + }, + json: true +}, async(error, response) => { + if (error) return console.log(error) + if (response.statusCode != 200) return console.log('Unsuccessful, as indicated by HTTP status') + + console.log(response.body.length + ' settings retrieved') + + // Remove existing database items + await db.Settings.destroy({truncate: true}) + + // Add new data to the database + await db.Settings.bulkCreate(response.body) +}); diff --git a/nodeapp/utils/init-settings.js b/nodeapp/utils/init-settings.js index 5c13409..ac74363 100644 --- a/nodeapp/utils/init-settings.js +++ b/nodeapp/utils/init-settings.js @@ -1,4 +1,4 @@ -const db = require('../db/db') +const db = require('../src/db') const path = require('path') require('dotenv').config({path: path.join(__dirname, '../../.env')}) diff --git a/nodeapp/views/index.html b/nodeapp/views/index.html index a874895..3ce3d31 100644 --- a/nodeapp/views/index.html +++ b/nodeapp/views/index.html @@ -78,7 +78,7 @@ legend: {display: false}, scales: { xAxes: [{type: 'time', time: {unit: 'day', min: fromdate, max: todate}, scaleLabel: {display: true, labelString: 'Date', fontColor: '#999999'}, gridLines: {color: '#CCCCCC'}, ticks: {fontColor: '#666666'}}], - yAxes: [{gridLines: {color: '#CCCCCC'}, ticks: {beginAtZero: true, max: 1200, fontColor: '#666666'}}] + yAxes: [{gridLines: {color: '#CCCCCC'}, ticks: {beginAtZero: true, max: 100, fontColor: '#666666'}}] }, maintainAspectRatio: false } @@ -113,7 +113,7 @@ $.getJSON('/getdata?from=' + from.toISOString() + '&to=' + to.toISOString(), function(j) { $(j).each(function() { this.x = new Date(this.timestamp); - this.y = this.reading; + this.y = this.reading / 1024 * 100; delete this.timestamp; delete this.reading; });