diff --git a/api/jhu-edu.js b/api/jhu-edu.js index 805d0d6..a85d11d 100644 --- a/api/jhu-edu.js +++ b/api/jhu-edu.js @@ -1,252 +1,268 @@ -const express = require('express') -const router = express.Router() +const express = require("express"); +const router = express.Router(); -const lookup = require('country-code-lookup') -const nestedProperty = require('nested-property') +const lookup = require("country-code-lookup"); +const nestedProperty = require("nested-property"); const column = { - PROVINCE_STATE: 'provincestate', - COUNTRY_REGION: 'countryregion', - LAST_UPDATE: 'lastupdate', - CONFIRMED: 'confirmed', - DEATHS: 'deaths', - RECOVERED: 'recovered' -} - -const request = require('request') -const csv = require('csvtojson') + PROVINCE_STATE: "provincestate", + COUNTRY_REGION: "countryregion", + LAST_UPDATE: "lastupdate", + CONFIRMED: "confirmed", + DEATHS: "deaths", + RECOVERED: "recovered", +}; + +const request = require("request"); +const csv = require("csvtojson"); const csvPath = { - confirmed: 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv', - deaths: 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv', - recovered: 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv' -} + confirmed: + "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv", + deaths: + "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv", + recovered: + "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv", +}; -const fixedCountryCodes = require('../dataset/country-codes.json') -const iso2CountryLoc = require('../dataset/iso2-country-loc.json') +const fixedCountryCodes = require("../dataset/country-codes.json"); +const iso2CountryLoc = require("../dataset/iso2-country-loc.json"); -const schedule = require('node-schedule') -schedule.scheduleJob('42 * * * *', updateCSVDataSet) // Call every hour at 42 minutes +const schedule = require("node-schedule"); +schedule.scheduleJob("42 * * * *", updateCSVDataSet); // Call every hour at 42 minutes const responseSet = { - brief: '{}', - latest: '{}', - latestOnlyCountries: '{}', - timeseries: '{}', - timeseriesOnlyCountries: '{}' -} -let lastUpdate + brief: "{}", + latest: "{}", + latestOnlyCountries: "{}", + timeseries: "{}", + timeseriesOnlyCountries: "{}", +}; +let lastUpdate; -router.get('/brief', function (req, res) { - res.status(200).json(responseSet.brief) -}) +router.get("/brief", function (req, res) { + res.status(200).json(responseSet.brief); +}); -router.get('/latest', function (req, res) { - const { iso2, iso3, onlyCountries } = req.query +router.get("/latest", function (req, res) { + const { iso2, iso3, onlyCountries } = req.query; - let latest = onlyCountries ? responseSet.latestOnlyCountries : responseSet.latest + let latest = onlyCountries + ? responseSet.latestOnlyCountries + : responseSet.latest; - if (iso2) latest = filterIso2code(latest, iso2) - if (iso3) latest = filterIso3code(latest, iso3) + if (iso2) latest = filterIso2code(latest, iso2); + if (iso3) latest = filterIso3code(latest, iso3); - res.status(200).send(JSON.stringify(latest)) -}) + res.status(200).json(latest); +}); -router.get('/timeseries', function (req, res) { - const { iso2, iso3, onlyCountries } = req.query +router.get("/timeseries", function (req, res) { + const { iso2, iso3, onlyCountries } = req.query; - let timeseries = onlyCountries ? responseSet.timeseriesOnlyCountries : responseSet.timeseries + let timeseries = onlyCountries + ? responseSet.timeseriesOnlyCountries + : responseSet.timeseries; - if (iso2) timeseries = filterIso2code(timeseries, iso2) - if (iso3) timeseries = filterIso3code(timeseries, iso3) + if (iso2) timeseries = filterIso2code(timeseries, iso2); + if (iso3) timeseries = filterIso3code(timeseries, iso3); - res.status(200).send(JSON.stringify(timeseries)) -}) + res.status(200).json(timeseries); +}); -function updateCSVDataSet () { - console.log('Updated at ' + new Date().toISOString()) +async function updateCSVDataSet() { + console.log("Updated at " + new Date().toISOString()); const dataSource = { confirmed: {}, deaths: {}, - recovered: {} - } - lastUpdate = new Date().toISOString() - const queryPromise = [] - - Object.entries(csvPath).forEach(([category, path]) => { - queryPromise.push(queryCsvAndSave(dataSource, path, category)) - }) - Promise.all(queryPromise) - .then((_values) => { - const brief = { - [column.CONFIRMED]: 0, - [column.DEATHS]: 0, - [column.RECOVERED]: 0 - } - const latest = {} - const timeseries = {} - - for (const [category, value] of Object.entries(dataSource)) { - for (const [name, item] of Object.entries(value)) { - const keys = Object.keys(item) - const cell = item[keys[keys.length - 1]] - const latestCount = cell ? Number(cell) : Number(item[keys[keys.length - 2]]) - - // For brief - brief[category] += latestCount - - // For latest - createPropertyIfNeed(latest, name, item) - latest[name][category] = latestCount - - // For timeseries - createPropertyIfNeed(timeseries, name, item) - for (const date of keys.slice(4)) { - // recovered csv file uses '3/24/2020' date format instead of '3/24/20' - const fixedDatePropertyName = date.replace('/2020', '/20') - - nestedProperty.set(timeseries[name], `timeseries.${fixedDatePropertyName}.${category}`, Number(item[date])) - } + recovered: {}, + }; + lastUpdate = new Date().toISOString(); + + try { + await Promise.all( + Object.entries(csvPath).map(([category, path]) => + queryCsvAndSave(dataSource, path, category), + ), + ); + + const brief = { + [column.CONFIRMED]: 0, + [column.DEATHS]: 0, + [column.RECOVERED]: 0, + }; + const latest = {}; + const timeseries = {}; + + for (const [category, value] of Object.entries(dataSource)) { + for (const [name, item] of Object.entries(value)) { + const keys = Object.keys(item); + const cell = item[keys[keys.length - 1]]; + const latestCount = cell + ? Number(cell) + : Number(item[keys[keys.length - 2]]); + + // For brief + brief[category] += latestCount; + + // For latest + createPropertyIfNeed(latest, name, item); + latest[name][category] = latestCount; + + // For timeseries + createPropertyIfNeed(timeseries, name, item); + for (const date of keys.slice(4)) { + // recovered csv file uses '3/24/2020' date format instead of '3/24/20' + const fixedDatePropertyName = date.replace("/2020", "/20"); + + nestedProperty.set( + timeseries[name], + `timeseries.${fixedDatePropertyName}.${category}`, + Number(item[date]), + ); } } + } - // Set 0 for unknown recovered value - for (const item of Object.values(timeseries)) { - for (const date of Object.values(item.timeseries)) { - if (!date.recovered) date.recovered = 0 - } + // Set 0 for unknown recovered value + for (const item of Object.values(timeseries)) { + for (const date of Object.values(item.timeseries)) { + if (!date.recovered) date.recovered = 0; } + } - responseSet.brief = brief - responseSet.latest = Object.values(latest) - responseSet.timeseries = Object.values(timeseries) - - // Uses deep copy - // https://stackoverflow.com/a/122704/3614334 - const copyLatest = JSON.parse(JSON.stringify(latest)) - responseSet.latestOnlyCountries = getMergedByCountry(Object.values(copyLatest)) - const copyTimeseries = JSON.parse(JSON.stringify(timeseries)) - responseSet.timeseriesOnlyCountries = getMergedByCountry(Object.values(copyTimeseries)) - - console.log(`Confirmed: ${brief.confirmed}, Deaths: ${brief.deaths}, Recovered: ${brief.recovered}`) - }) - .catch((error) => { - console.log('Error on queryPromise: ' + error) - }) + responseSet.brief = brief; + responseSet.latest = Object.values(latest); + responseSet.timeseries = Object.values(timeseries); + + // Uses deep copy + // https://stackoverflow.com/a/122704/3614334 + const copyLatest = JSON.parse(JSON.stringify(latest)); + responseSet.latestOnlyCountries = getMergedByCountry( + Object.values(copyLatest), + ); + const copyTimeseries = JSON.parse(JSON.stringify(timeseries)); + responseSet.timeseriesOnlyCountries = getMergedByCountry( + Object.values(copyTimeseries), + ); + + console.log( + `Confirmed: ${brief.confirmed}, Deaths: ${brief.deaths}, Recovered: ${brief.recovered}`, + ); + } catch (error) { + console.log("Error on queryPromise: " + error); + } } -function getMergedByCountry (list) { - const mergedList = { } +function getMergedByCountry(list) { + const mergedList = {}; for (const item of list) { - const countryName = item.countryregion + const countryName = item.countryregion; if (mergedList[countryName]) { - const country = mergedList[countryName] + const country = mergedList[countryName]; // for latest api - mergeConfirmDeathRecover(country, item) + mergeConfirmDeathRecover(country, item); // for timeseries api if (item.timeseries) { - const timeseries = item.timeseries - const mergedTimeseries = country.timeseries + const timeseries = item.timeseries; + const mergedTimeseries = country.timeseries; for (const key of Object.keys(timeseries)) { - mergeConfirmDeathRecover(mergedTimeseries[key], timeseries[key]) + mergeConfirmDeathRecover(mergedTimeseries[key], timeseries[key]); } } // Overwrite location if (item.countrycode) { - const iso2 = item.countrycode.iso2 - country.location = iso2CountryLoc[iso2] + const iso2 = item.countrycode.iso2; + country.location = iso2CountryLoc[iso2]; } } else { - delete item.provincestate - mergedList[countryName] = item + delete item.provincestate; + mergedList[countryName] = item; } } - return Object.values(mergedList) + return Object.values(mergedList); } -function mergeConfirmDeathRecover (target, item) { - if (!(target && item)) return +function mergeConfirmDeathRecover(target, item) { + if (!(target && item)) return; - if (item.confirmed) merge(target, item, 'confirmed') - if (item.deaths) merge(target, item, 'deaths') - if (item.recovered) merge(target, item, 'recovered') + if (item.confirmed) merge(target, item, "confirmed"); + if (item.deaths) merge(target, item, "deaths"); + if (item.recovered) merge(target, item, "recovered"); } -function merge (target, item, key) { - const cur = target[key] ? target[key] : 0 - const add = item[key] ? item[key] : 0 +function merge(target, item, key) { + const cur = target[key] ? target[key] : 0; + const add = item[key] ? item[key] : 0; - target[key] = cur + add + target[key] = cur + add; } -function filterIso2code (source, code) { - return source.filter(item => { - const countryCode = item.countrycode ? item.countrycode.iso2 : 'unknown' - return countryCode === code - }) +function filterIso2code(source, code) { + return source.filter((item) => { + const countryCode = item.countrycode ? item.countrycode.iso2 : "unknown"; + return countryCode === code; + }); } -function filterIso3code (source, code) { - return source.filter(item => { - const countryCode = item.countrycode ? item.countrycode.iso3 : 'unknown' - return countryCode === code - }) +function filterIso3code(source, code) { + return source.filter((item) => { + const countryCode = item.countrycode ? item.countrycode.iso3 : "unknown"; + return countryCode === code; + }); } -function queryCsvAndSave (dataSource, path, category) { +function queryCsvAndSave(dataSource, path, category) { return csv() .fromStream(request.get(path)) .subscribe((json) => { - if (json['Province/State']) { - const provincestate = json['Province/State'] - dataSource[category][provincestate] = json - } else if (json['Country/Region']) { - const countryregion = json['Country/Region'] - dataSource[category][countryregion] = json + if (json["Province/State"]) { + const provincestate = json["Province/State"]; + dataSource[category][provincestate] = json; + } else if (json["Country/Region"]) { + const countryregion = json["Country/Region"]; + dataSource[category][countryregion] = json; } return new Promise((resolve, reject) => { - resolve() - }) - }) + resolve(); + }); + }); } -function createPropertyIfNeed (target, name, item) { +function createPropertyIfNeed(target, name, item) { if (!nestedProperty.has(target, name)) { target[name] = { - [column.PROVINCE_STATE]: item['Province/State'], - [column.COUNTRY_REGION]: item['Country/Region'], + [column.PROVINCE_STATE]: item["Province/State"], + [column.COUNTRY_REGION]: item["Country/Region"], [column.LAST_UPDATE]: lastUpdate, location: { lat: Number(item.Lat), - lng: Number(item.Long) - } - } + lng: Number(item.Long), + }, + }; // Append country codes - const countryName = item['Country/Region'] - if (lookup.byCountry(countryName)) { - appendCountryCode(lookup.byCountry(countryName)) - } else if (fixedCountryCodes[countryName]) { - appendCountryCode(lookup.byCountry(fixedCountryCodes[countryName])) - } - } - - function appendCountryCode (countryCode) { - target[name].countrycode = { - iso2: countryCode.iso2, - iso3: countryCode.iso3 + const countryName = item["Country/Region"]; + const countryCode = + lookup.byCountry(countryName) || + lookup.byCountry(fixedCountryCodes[countryName]); + if (countryCode) { + target[name].countrycode = { + iso2: countryCode.iso2, + iso3: countryCode.iso3, + }; } } } -updateCSVDataSet() +updateCSVDataSet(); -module.exports = router +module.exports = router; diff --git a/api/korea-kcdc.js b/api/korea-kcdc.js index 2cd1ca8..e789db9 100644 --- a/api/korea-kcdc.js +++ b/api/korea-kcdc.js @@ -1,48 +1,51 @@ -const express = require('express') -const router = express.Router() +const express = require("express"); +const router = express.Router(); -const fetch = require('node-fetch') -const fs = require('fs') -const url = 'https://raw.githubusercontent.com/LiveCoronaDetector/livecod/master/data/koreaRegionalData.js' -const tempFilePath = './dataset/temp-kcdc.js' -const tempModulePath = '../dataset/temp-kcdc.js' -const exportModule = 'module.exports = koreaRegionalData;' +const fetch = require("node-fetch"); +const fs = require("fs"); +const path = require("path"); +const schedule = require("node-schedule"); -const schedule = require('node-schedule') -schedule.scheduleJob('12 * * * *', updateDataSet) // Call every hour at 12 minutes +const url = + "https://raw.githubusercontent.com/LiveCoronaDetector/livecod/master/data/koreaRegionalData.js"; +const tempFilePath = path.join(__dirname, "../dataset/temp-kcdc.js"); +const exportModule = "module.exports = koreaRegionalData;"; -let koreaRegionalData = {} +let koreaRegionalData = {}; -router.get('/brief', function (req, res) { - res.status(200).json(koreaRegionalData) -}) +router.get("/brief", (req, res) => { + res.status(200).json(koreaRegionalData); +}); -async function updateDataSet () { - console.log('Korea KCDC Updated at ' + new Date().toISOString()) +const updateDataSet = async () => { + console.log(`Korea KCDC Updated at ${new Date().toISOString()}`); try { - const response = await fetch(url) - const body = await response.text() + const response = await fetch(url); + const body = await response.text(); - fs.writeFile(tempFilePath, body + exportModule, function (err) { + fs.writeFile(tempFilePath, `${body}${exportModule}`, (err) => { if (err) { - console.error(err) - } else { - try { - koreaRegionalData = require(tempModulePath) - console.log('Korea KCDC data was saved!') - } catch (e) { - console.error(e) - koreaRegionalData = {} - } + console.error(err); + return; } - }) + + try { + delete require.cache[require.resolve(tempFilePath)]; + koreaRegionalData = require(tempFilePath); + console.log("Korea KCDC data was saved!"); + } catch (e) { + console.error(e); + koreaRegionalData = {}; + } + }); } catch (e) { - console.error(e) - koreaRegionalData = {} + console.error(e); + koreaRegionalData = {}; } -} +}; -updateDataSet() +schedule.scheduleJob("12 * * * *", updateDataSet); // Call every hour at 12 minutes +updateDataSet(); -module.exports = router +module.exports = router;