Browse Source

Created Cron job to get data from external api every 15 minutes and save it to local database. Created Weather model and controller to get latest weather status

unknown 4 years ago
parent
commit
adc4aaed0f
8 changed files with 195 additions and 0 deletions
  1. 18 0
      controllers/weather.js
  2. 15 0
      models/Weather.js
  3. 93 0
      package-lock.json
  4. 2 0
      package.json
  5. 12 0
      routes/weather.js
  6. 2 0
      server.js
  7. 42 0
      utils/getExternalWeather.js
  8. 11 0
      utils/jobs.js

+ 18 - 0
controllers/weather.js

@@ -0,0 +1,18 @@
+const asyncWrapper = require('../middleware/asyncWrapper');
+const ErrorResponse = require('../utils/ErrorResponse');
+const Weather = require('../models/Weather');
+
+// @desc      Get latest weather status
+// @route     POST /api/weather
+// @access    Public
+exports.getWeather = asyncWrapper(async (req, res, next) => {
+  const weather = await Weather.findAll({
+    order: [['createdAt', 'DESC']],
+    limit: 1
+  });
+
+  res.status(200).json({
+    success: true,
+    data: weather
+  })
+})

+ 15 - 0
models/Weather.js

@@ -0,0 +1,15 @@
+const { DataTypes } = require('sequelize');
+const { sequelize } = require('../db');
+
+const Weather = sequelize.define('Weather', {
+  externalLastUpdate: DataTypes.STRING,
+  tempC: DataTypes.FLOAT,
+  tempF: DataTypes.FLOAT,
+  isDay: DataTypes.INTEGER,
+  conditionText: DataTypes.TEXT,
+  conditionCode: DataTypes.INTEGER
+}, {
+  freezeTableName: true
+});
+
+module.exports = Weather;

+ 93 - 0
package-lock.json

@@ -276,6 +276,14 @@
       "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
       "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
       "optional": true
       "optional": true
     },
     },
+    "axios": {
+      "version": "0.21.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+      "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+      "requires": {
+        "follow-redirects": "^1.10.0"
+      }
+    },
     "balanced-match": {
     "balanced-match": {
       "version": "1.0.2",
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -393,6 +401,15 @@
         }
         }
       }
       }
     },
     },
+    "call-bind": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      }
+    },
     "camelcase": {
     "camelcase": {
       "version": "5.3.1",
       "version": "5.3.1",
       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -633,6 +650,15 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
     },
+    "cron-parser": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz",
+      "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==",
+      "requires": {
+        "is-nan": "^1.3.2",
+        "luxon": "^1.26.0"
+      }
+    },
     "crypto-random-string": {
     "crypto-random-string": {
       "version": "2.0.0",
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
       "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
@@ -681,6 +707,14 @@
       "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
       "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
       "dev": true
       "dev": true
     },
     },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
     "delayed-stream": {
     "delayed-stream": {
       "version": "1.0.0",
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -885,6 +919,11 @@
         "unpipe": "~1.0.0"
         "unpipe": "~1.0.0"
       }
       }
     },
     },
+    "follow-redirects": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
+      "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
+    },
     "forever-agent": {
     "forever-agent": {
       "version": "0.6.1",
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -1002,6 +1041,16 @@
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
     },
     },
+    "get-intrinsic": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+      "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1"
+      }
+    },
     "get-stream": {
     "get-stream": {
       "version": "4.1.0",
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@@ -1104,6 +1153,11 @@
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
       "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
       "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
     },
     },
+    "has-symbols": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+      "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
+    },
     "has-unicode": {
     "has-unicode": {
       "version": "2.0.1",
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
@@ -1273,6 +1327,15 @@
         "is-path-inside": "^3.0.1"
         "is-path-inside": "^3.0.1"
       }
       }
     },
     },
+    "is-nan": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
+      "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "define-properties": "^1.1.3"
+      }
+    },
     "is-npm": {
     "is-npm": {
       "version": "4.0.0",
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz",
       "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz",
@@ -1405,6 +1468,11 @@
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
     },
+    "long-timeout": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
+      "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ="
+    },
     "lowercase-keys": {
     "lowercase-keys": {
       "version": "1.0.1",
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
@@ -1419,6 +1487,11 @@
         "yallist": "^4.0.0"
         "yallist": "^4.0.0"
       }
       }
     },
     },
+    "luxon": {
+      "version": "1.27.0",
+      "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.27.0.tgz",
+      "integrity": "sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA=="
+    },
     "make-dir": {
     "make-dir": {
       "version": "3.1.0",
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -1657,6 +1730,16 @@
         }
         }
       }
       }
     },
     },
+    "node-schedule": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.0.0.tgz",
+      "integrity": "sha512-cHc9KEcfiuXxYDU+HjsBVo2FkWL1jRAUoczFoMIzRBpOA4p/NRHuuLs85AWOLgKsHtSPjN8csvwIxc2SqMv+CQ==",
+      "requires": {
+        "cron-parser": "^3.1.0",
+        "long-timeout": "0.1.1",
+        "sorted-array-functions": "^1.3.0"
+      }
+    },
     "nodemon": {
     "nodemon": {
       "version": "2.0.7",
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz",
       "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz",
@@ -1774,6 +1857,11 @@
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
       "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
       "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
     },
     },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+    },
     "on-finished": {
     "on-finished": {
       "version": "2.3.0",
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -2253,6 +2341,11 @@
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
       "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
       "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
     },
     },
+    "sorted-array-functions": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
+      "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
+    },
     "spawn-command": {
     "spawn-command": {
       "version": "0.0.2-1",
       "version": "0.0.2-1",
       "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
       "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",

+ 2 - 0
package.json

@@ -13,10 +13,12 @@
   "license": "ISC",
   "license": "ISC",
   "dependencies": {
   "dependencies": {
     "@types/express": "^4.17.11",
     "@types/express": "^4.17.11",
+    "axios": "^0.21.1",
     "colors": "^1.4.0",
     "colors": "^1.4.0",
     "concurrently": "^6.0.2",
     "concurrently": "^6.0.2",
     "dotenv": "^9.0.0",
     "dotenv": "^9.0.0",
     "express": "^4.17.1",
     "express": "^4.17.1",
+    "node-schedule": "^2.0.0",
     "sequelize": "^6.6.2",
     "sequelize": "^6.6.2",
     "sqlite3": "^5.0.2"
     "sqlite3": "^5.0.2"
   },
   },

+ 12 - 0
routes/weather.js

@@ -0,0 +1,12 @@
+const express = require('express');
+const router = express.Router();
+
+const {
+  getWeather
+} = require('../controllers/weather');
+
+router
+  .route('')
+  .get(getWeather);
+
+module.exports = router;

+ 2 - 0
server.js

@@ -1,6 +1,7 @@
 const express = require('express');
 const express = require('express');
 const { connectDB } = require('./db');
 const { connectDB } = require('./db');
 const errorHandler = require('./middleware/errorHandler');
 const errorHandler = require('./middleware/errorHandler');
+const jobs = require('./utils/jobs');
 const colors = require('colors');
 const colors = require('colors');
 require('dotenv').config();
 require('dotenv').config();
 
 
@@ -19,6 +20,7 @@ app.use(express.json());
 // Link controllers with routes
 // Link controllers with routes
 app.use('/api/apps', require('./routes/apps'));
 app.use('/api/apps', require('./routes/apps'));
 app.use('/api/config', require('./routes/config'));
 app.use('/api/config', require('./routes/config'));
+app.use('/api/weather', require('./routes/weather'));
 
 
 // Custom error handler
 // Custom error handler
 app.use(errorHandler);
 app.use(errorHandler);

+ 42 - 0
utils/getExternalWeather.js

@@ -0,0 +1,42 @@
+const Config = require('../models/Config');
+const Weather = require('../models/Weather');
+const axios = require('axios');
+
+const getExternalWeather = async () => {
+  // Get API key from database
+  let secret = await Config.findOne({
+    where: { key: 'WEATHER_API_KEY' }
+  });
+
+  if (!secret) {
+    console.log('API key was not found');
+    return;
+  }
+
+  secret = secret.value;
+
+  // Fetch data from external API
+  try {
+    const res = await axios.get(`http://api.weatherapi.com/v1/current.json?key=${secret}&q=52.229676,21.012229`);
+
+    // For dev
+    // console.log(res.data);
+
+    // Save weather data
+    const cursor = res.data.current;
+    await Weather.create({
+      externalLastUpdate: cursor.last_updated,
+      tempC: cursor.temp_c,
+      tempF: cursor.temp_f,
+      isDay: cursor.is_day,
+      conditionText: cursor.condition.text,
+      conditionCode: cursor.condition.code
+    });
+  } catch (err) {
+    console.log(err);
+    console.log('External API request failed');
+    return;
+  }
+}
+
+module.exports = getExternalWeather;

+ 11 - 0
utils/jobs.js

@@ -0,0 +1,11 @@
+const schedule = require('node-schedule');
+const getExternalWeather = require('./getExternalWeather');
+
+const weatherJob = schedule.scheduleJob('updateWeather', '0 */15 * * * *', async () => {
+  try {
+    await getExternalWeather();
+    console.log('weather updated');
+  } catch (err) {
+    console.log(err);
+  }
+})