Merge branch 'feature/flights' into 'develop'
Implemented Endpoints for usage of google place api See merge request tjohn/cc-data!19
This commit is contained in:
commit
940d5426e8
@ -4,4 +4,5 @@ DB_HOST=lhinderberger.dev
|
|||||||
DB_USER=root
|
DB_USER=root
|
||||||
DB_PASSWORD=devtest
|
DB_PASSWORD=devtest
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
DATABASE=travopti
|
DATABASE=travopti
|
||||||
|
GOOGLE_CLOUD_APIS=AIzaSyCeMBLfpqTp0IVB7Xipx6ekRQFUBjPacQc
|
||||||
275
backend/app.js
275
backend/app.js
@ -1,275 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -15,6 +15,7 @@ const search = require("./routes/search");
|
|||||||
const regions = require("./routes/regions");
|
const regions = require("./routes/regions");
|
||||||
const countries = require("./routes/countries");
|
const countries = require("./routes/countries");
|
||||||
const climate = require("./routes/climate");
|
const climate = require("./routes/climate");
|
||||||
|
const places = require("./routes/place");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ const app = express();
|
|||||||
app.use(regions(dbConn));
|
app.use(regions(dbConn));
|
||||||
app.use(countries(dbConn));
|
app.use(countries(dbConn));
|
||||||
app.use(climate(dbConn));
|
app.use(climate(dbConn));
|
||||||
|
app.use(places(dbConn));
|
||||||
|
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
// 500
|
// 500
|
||||||
|
|||||||
18
backend/models/getPlace.js
Normal file
18
backend/models/getPlace.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const axios = require("axios")
|
||||||
|
const getPlacePhoto = require("./getPlacePhoto.js")
|
||||||
|
|
||||||
|
module.exports = async (q) => {
|
||||||
|
const res = await axios.get(
|
||||||
|
`https://maps.googleapis.com/maps/api/place/findplacefromtext/json?inputtype=textquery&fields=photos,formatted_address,name,rating,opening_hours,geometry&input=${q}&key=${process.env.GOOGLE_CLOUD_APIS}`)
|
||||||
|
console.log(res.data)
|
||||||
|
|
||||||
|
// Photo url is not returned since it overuses Google Place API
|
||||||
|
/*
|
||||||
|
for (let candidate of res.data.candidates) {
|
||||||
|
for (let photo of candidate.photos) {
|
||||||
|
photo.url = await getPlacePhoto(photo.photo_reference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
19
backend/models/getPlaceNearby.js
Normal file
19
backend/models/getPlaceNearby.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const axios = require("axios")
|
||||||
|
const getPlacePhoto = require("./getPlacePhoto.js")
|
||||||
|
|
||||||
|
const radius = 20000
|
||||||
|
const rankby = "prominence"
|
||||||
|
|
||||||
|
module.exports = async (lat, lng) => {
|
||||||
|
const res = await axios.get(
|
||||||
|
`https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&radius=${radius}&rankby=${rankby}&key=${process.env.GOOGLE_CLOUD_APIS}`
|
||||||
|
);
|
||||||
|
// Photo url is not returned since it overuses Google Place API
|
||||||
|
/*
|
||||||
|
for (let result of res.data.results) {
|
||||||
|
for (let photo of result.photos) {
|
||||||
|
photo.url = await getPlacePhoto(photo.photo_reference)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
10
backend/models/getPlacePhoto.js
Normal file
10
backend/models/getPlacePhoto.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const axios = require("axios")
|
||||||
|
|
||||||
|
module.exports = async (photoref) => {
|
||||||
|
const res = await axios.get(
|
||||||
|
`https://maps.googleapis.com/maps/api/place/photo?photoreference=${photoref}&maxwidth=1600&key=${process.env.GOOGLE_CLOUD_APIS}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = res.request.res.responseUrl
|
||||||
|
return url
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "cc-data-backend",
|
"name": "cc-data-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "app.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "nodemon ./index.js"
|
"start": "nodemon ./index.js"
|
||||||
},
|
},
|
||||||
|
|||||||
23
backend/routes/place.js
Normal file
23
backend/routes/place.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const router = require("express").Router()
|
||||||
|
const getPlace = require("../models/getPlace.js")
|
||||||
|
const getPlaceNearby = require("../models/getPlaceNearby.js")
|
||||||
|
const getPlacePhoto = require("../models/getPlacePhoto.js")
|
||||||
|
|
||||||
|
module.exports = dbConn => {
|
||||||
|
router.get("/api/v1/place", async (req, res) => {
|
||||||
|
const place = await getPlace(req.query.q)
|
||||||
|
res.json(place)
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/api/v1/place/nearby", async (req, res) => {
|
||||||
|
const place = await getPlaceNearby(req.query.lat, req.query.lng)
|
||||||
|
res.json(place)
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/api/v1/place/photo", async (req, res) => {
|
||||||
|
const photo = await getPlacePhoto(req.query.photoref)
|
||||||
|
res.json(photo)
|
||||||
|
});
|
||||||
|
|
||||||
|
return router;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user