const express = require('express') const moment = require('moment') const _ = require('lodash') const score = require('./util/score') const transformer = require('./util/transformer') const base = require('./util/base64') const app = express() const port = 3000 //const multiplier_temp = 5 const multiplier = { temperature_mean_max: 5, precipitation: 3.5, raindays: 3, sunhours: 2.5, } const samplePresets = [ { id: 29837, parameter: "temperature", label: "warm", values: [22, 25] } ] app.get('/', (req, res) => res.send('Hello Timo!')) 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/climate/update', climateUpdateHandler) app.listen(port, () => console.log(`Travopti backend listening at http://localhost:${port}`)) function climateUpdateHandler(req, res) { let parameter = [] if (req.query.startDate) parameter.push(req.query.startDate) if (req.query.endDate) parameter.push(req.query.endDate) climate.update(...parameter).then(x => { res.send(x) }).catch(e => { let result = { message: 'error during update process. check backend logs.', error: e } res.send(result) } ) } function searchHandler(req, res) { let response = {} response.meta = { params: req.params, query: req.query, headers: req.headers } let q = req.query.q ? base.base64ToObj(req.query.q) : req.query console.log('Q:', q) let queryObj = {} if (q.temperature) queryObj['temperature_mean_max'] = q.temperature if (q.precipitation) queryObj['precipitation'] = q.precipitation 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 => { console.log(e) res.json(e.message) }) } 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 = _.round(_.random(minMax.min.temperature_mean_max, minMax.max.temperature_mean_max - 5), 0) let p = _.round(_.random(minMax.min.precipitation, minMax.max.precipitation - 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_max = `${t},${t + 5}` queries.precipitation = `${p},${p + 50}` queries.raindays = `${r},${r + 5}` queries.sunhours = `${s},${s + 50}` } 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 -- // to calculate average if traveldates are in more than one month let travelPeriods = [] if (monthFrom === monthTo) { let element = { month: monthFrom, days: dayTo - dayFrom } travelPeriods.push(element) } else { for (let index = monthFrom; index <= monthTo; index++) { let element = {} if (index === monthFrom) { element = { month: index, days: 32 - dayFrom } } else if (index === monthTo) { element = { month: index, days: dayTo } } else { element = { month: index, days: 30 } } travelPeriods.push(element) } } // 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[0], value[1], minMax) }); return period })); // calculate ratio and transform into target object return { results: transformer.transform(detailScores), debug: { detailScores: detailScores, minMax: minMax } } } function calculateScores(type, regionDataRows, searchLowParam, searchMaxParam, minMax) { console.log('calculateScores for', type) let result = regionDataRows.map(x => { const sc = Math.round(score.calculateScoreRange(minMax.min[type], minMax.max[type], multiplier[type], x[type], searchLowParam, searchMaxParam) * 100) / 100 return { region_id: x.region_id, type: type, value: x[type], score: x[type] === null ? null : sc } }) return result } async function getClimateMinMax() { console.log('getClimateMinMax') const sqlMin = `SELECT MIN(temperature_mean) AS temperature_mean, MIN(temperature_mean_min) AS temperature_mean_min, MIN(temperature_mean_max) AS temperature_mean_max, MIN(precipitation) AS precipitation, MIN(raindays) AS raindays, MIN(sunshine) AS sunhours FROM region_climate` const sqlMax = `SELECT MAX(temperature_mean) AS temperature_mean, MAX(temperature_mean_min) AS temperature_mean_min, MAX(temperature_mean_max) AS temperature_mean_max, MAX(precipitation) AS precipitation, MAX(raindays) AS raindays, MAX(sunshine) AS sunhours FROM region_climate` const [qResMin, qResMax] = await Promise.all([getQueryRows(sqlMin), getQueryRows(sqlMax)]) //console.log(qResMin) return { min: qResMin[0], max: qResMax[0] } } async function getQueryRows(sql) { //console.log('getQueryRows') const [rows, fields] = await db.execute(sql) return rows } 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) { console.log('getClimatePerRegionAndMonth') const sql = `SELECT region_id, AVG(temperature_mean), AVG(temperature_mean_min), AVG(temperature_mean_max), AVG(precipitation), AVG(sunshine) FROM region_climate WHERE month = ${month} AND region_id = ${regionId}` return getQueryRows(sql) } function getAllRegionsWithClimatePerMonth(month) { console.log('getAllRegionsWithClimatePerMonth') const sql = `SELECT region_climate.region_id AS region_id, regions.country_id AS country_id, regions.region AS name, ROUND(AVG(region_climate.temperature_mean), 1) AS temperature_mean, ROUND(AVG(region_climate.temperature_mean_min), 1) AS temperature_mean_min, ROUND(AVG(region_climate.temperature_mean_max), 1) AS temperature_mean_max, ROUND(AVG(region_climate.precipitation), 1) AS precipitation, ROUND(AVG(region_climate.raindays), 1) AS raindays, 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_max) res.temperature_mean_max = [queries.temperature_mean_max.split(',')[0], queries.temperature_mean_max.split(',')[1]] if (queries.precipitation) res.precipitation = [queries.precipitation.split(',')[0], queries.precipitation.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 }