From cdae8c6e4578ac3e6bc621e5268c15921343f9bd Mon Sep 17 00:00:00 2001 From: Timo Volkmann Date: Sun, 14 Jun 2020 19:28:06 +0200 Subject: [PATCH] changed query syntax --- backend/app.js | 149 ++++++++++++++++++++++++++++------------------ backend/base64.js | 23 +++++++ 2 files changed, 113 insertions(+), 59 deletions(-) create mode 100644 backend/base64.js diff --git a/backend/app.js b/backend/app.js index c3b7ffa..f76b308 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,11 +1,11 @@ -var express = require('express') -var _ = require('lodash') -var sampledata = require('./sampledata') -var db = require('./mysql') -var score = require('./score') -var transformer = require('./transformer') -var climate = require('./climate') -var moment = require('moment') +const express = require('express') +const moment = require('moment') +const _ = require('lodash') +const db = require('./mysql') +const score = require('./score') +const transformer = require('./transformer') +const climate = require('./climate') +const base = require('./base64') const app = express() const port = 3000 @@ -17,13 +17,6 @@ const multiplier = { sunhours: 2.5, } -const sampleRegions = [ - { - id: 29837, - name: "Timbuktu", - country: "Mali" - } -] const samplePresets = [ { id: 29837, @@ -34,8 +27,8 @@ const samplePresets = [ ] app.get('/', (req, res) => res.send('Hello Timo!')) -app.get('/v1/regions', (req, res) => res.json(sampleRegions)) -app.get('/v1/presets', (req, res) => res.json(samplePresets)) +app.get('/v1/regions', (req, res) => getAllRegions().then(x => res.json({ data: x }))) +app.get('/v1/presets', (req, res) => res.json({ data: samplePresets})) app.get('/v1/search', searchHandler) app.get('/v1/update/climate', climateUpdateHandler) @@ -60,25 +53,23 @@ function climateUpdateHandler(req, res) { function searchHandler(req, res) { let response = {} - + response.meta = { params: req.params, query: req.query, headers: req.headers } - - console.log('log params') - console.log(req.query.from) - console.log(req.query.to) - console.log(req.query.temperature) - - let validQueries = {} - if (req.query.temperature) validQueries['temperature_mean'] = req.query.temperature - if (req.query.percipitation) validQueries['percipitation'] = req.query.percipitation - if (req.query.raindays) validQueries['raindays'] = req.query.raindays - if (req.query.sunhours) validQueries['sunhours'] = req.query.sunhours - - search(req.query.from, req.query.to, validQueries).then(searchResults => { + + let q = req.query.q ? base.base64ToObj(req.query.q) : req.query + console.log('Q:', q) + + let queryObj = {} + if (q.temperature) queryObj['temperature_mean'] = q.temperature + if (q.percipitation) queryObj['percipitation'] = q.percipitation + if (q.raindays) queryObj['raindays'] = q.raindays + if (q.sunhours) queryObj['sunhours'] = q.sunhours + + scoreAndSearch(q.from, q.to, queryObj).then(searchResults => { response.data = searchResults res.json(response) }).catch(e => { @@ -87,42 +78,60 @@ function searchHandler(req, res) { }) } -async function search(from, to, queries) { +async function scoreAndSearch(from, to, queries) { + // TODO break funtion into parts when implementing non-climate queries and modularize (new file) + console.log('search') + // get Min and Max values for each Parameter const minMax = await getClimateMinMax() + // randomize if empty queries if (_.isEmpty(queries)) { - let t = _.random(minMax.min.temperature_mean, minMax.max.temperature_mean-5) - let p = _.random(minMax.min.percipitation, minMax.max.percipitation-50) - let r = _.random(minMax.min.raindays, minMax.max.raindays-5) - let s = _.random(minMax.min.sunhours, minMax.max.sunhours-50) + let t = _.round(_.random(minMax.min.temperature_mean, minMax.max.temperature_mean-5),0) + let p = _.round(_.random(minMax.min.percipitation, minMax.max.percipitation - 50), 0) + let r = _.round(_.random(minMax.min.raindays, minMax.max.raindays - 5), 0) + let s = _.round(_.random(minMax.min.sunhours, minMax.max.sunhours - 50), 0) queries.temperature_mean = `${t},${t + 5}` queries.percipitation = `${p},${p + 50}` queries.raindays = `${r},${r + 5}` queries.sunhours = `${s},${s + 50}` } - // validate regex: YYYY-MM-DD - let re = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/i; - if (!from.match(re) || !to.match(re)) throw new Error("ERR: invalid parameter:",from,to) - // check for valid period - if (moment(from, 'YYYY-MM-DD').isAfter(moment(to, 'YYYY-MM-DD'))) { - console.log(moment(from, 'YYYY-MM-DD')) - console.log(moment(to, 'YYYY-MM-DD')) - console.log(moment(from, 'YYYY-MM-DD').isAfter(moment(to, 'YYYY-MM-DD'))) - throw new Error("ERR: from is before to date.") + queries = oldToNewQuerySyntax(queries) + console.log(queries) + + // TODO simplify and remove support for old query syntaax + let monthFrom = 0 + let monthTo = 0 + let dayFrom = 0 + let dayTo = 0 + + if (_.isNumber(from) && _.isNumber(to)) { + let dateFrom = moment(from).toDate() + let dateTo = moment(to).toDate() + monthFrom = dateFrom.getMonth() + monthTo = dateTo.getMonth() + dayFrom = dateFrom.getDay() + dayTo = dateTo.getDay() + if (moment(dateFrom).add(23, 'hours').isAfter(moment(dateTo))) throw new Error("ERR: 'to' must be at least one day after 'from'.") + } else { + // to still support old query syntax + let re = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/i; + monthFrom = Number(from.split("-")[1]) + monthTo = Number(to.split("-")[1]) + dayFrom = Number(from.split("-")[2]) + dayTo = Number(to.split("-")[2]) + if (!from.match(re) || !to.match(re)) throw new Error("ERR: invalid parameter:",from,to) + if (moment(from, 'YYYY-MM-DD').add(23, 'hours').isAfter(moment(to, 'YYYY-MM-DD'))) throw new Error("ERR: 'to' must be at least one day after 'from'.") } // -- Prepare search -- - // calculate average if traveldates are in more than one month - let monthFrom = Number(from.split("-")[1]) - let monthTo = Number(to.split("-")[1]) - + // to calculate average if traveldates are in more than one month let travelPeriods = [] if (monthFrom === monthTo) { let element = { month: monthFrom, - days: Number(to.split("-")[2]) - Number(from.split("-")[2]) + days: dayTo - dayFrom } travelPeriods.push(element) } else { @@ -131,12 +140,12 @@ async function search(from, to, queries) { if (index === monthFrom) { element = { month: index, - days: 32 - Number(from.split("-")[2]) + days: 32 - dayFrom } } else if (index === monthTo) { element = { month: index, - days: Number(to.split("-")[2]) + days: dayTo } } else { element = { @@ -148,20 +157,20 @@ async function search(from, to, queries) { } } - // calculate score + // calculate detail scores let detailScores = await Promise.all(travelPeriods.map(async period => { period.climate = await getAllRegionsWithClimatePerMonth(period.month) period.scores = {} Object.entries(queries).forEach(([key, value]) => { // console.log('key',key) // console.log('val', value) - period.scores[key] = calculateScores(key, period.climate, value.split(',')[0], value.split(',')[1], minMax) + period.scores[key] = calculateScores(key, period.climate, value[0], value[1], minMax) }); return period })); - // TODO: calculate ratio + // calculate ratio and transform into target object return { results: transformer.transform(detailScores), debug: { @@ -216,10 +225,17 @@ async function getQueryRows(sql) { return rows } -function getRegionIdsWithMeteostatData() { - console.log('getRegionIdsWithMeteostatData') - //return await getQueryRows(`SELECT * FROM regions WHERE meteostat_id IS NOT NULL`) - return getQueryRows(`SELECT region_id FROM region_climate GROUP BY region_id`) +function getAllRegions() { + const sql = `SELECT + regions.id AS region_id, + regions.region AS name, + regions.country_id AS country_id, + countries.country AS country, + regions.meteostat_id AS meteostat_id + FROM regions + JOIN countries + ON regions.country_id = countries.id` + return getQueryRows(sql) } function getClimatePerRegionAndMonth(regionId, month) { @@ -242,4 +258,19 @@ function getAllRegionsWithClimatePerMonth(month) { ROUND(AVG(region_climate.sunshine), 1) AS sunhours FROM region_climate JOIN regions ON region_climate.region_id = regions.id WHERE region_climate.month = ${month} GROUP BY region_id` return getQueryRows(sql) +} + +function oldToNewQuerySyntax(queries) { + let res = {} + try { + if (queries.temperature_mean) res.temperature_mean = [queries.temperature_mean.split(',')[0], queries.temperature_mean.split(',')[1]] + if (queries.percipitation) res.percipitation = [queries.percipitation.split(',')[0], queries.percipitation.split(',')[1]] + if (queries.raindays) res.raindays = [queries.raindays.split(',')[0], queries.raindays.split(',')[1]] + if (queries.sunhours) res.sunhours = [queries.sunhours.split(',')[0], queries.sunhours.split(',')[1]] + console.log('queries successfully transformed'); + } catch (error) { + console.log('queries are ok'); + return queries + } + return res } \ No newline at end of file diff --git a/backend/base64.js b/backend/base64.js new file mode 100644 index 0000000..d4d9583 --- /dev/null +++ b/backend/base64.js @@ -0,0 +1,23 @@ +/** + * Encodes an object as base64 string.. + * @param obj The object to encode + */ +exports.objToBase64 = function(obj) { + return btoa(JSON.stringify(obj)); +} + +/** + * Decodes a base64 encoded object. + * @param base64 Encoded object + */ +exports.base64ToObj = function(base64) { + return JSON.parse(atob(base64)); +} + +function atob(base64) { + return Buffer.from(base64, 'base64').toString('binary') +} + +function btoa(string) { + return Buffer.from(string).toString('base64') +} \ No newline at end of file