travopti/backend/routes/search.js
2020-07-10 11:20:24 +02:00

183 lines
5.5 KiB
JavaScript

/**
* @swagger
* tags:
* name: Search
* description: Access the search algorithm and the data provided for searching.
*/
const router = require("express").Router();
// Models
const getRegions = require('../models/getRegions.js');
const getSearchPresets = require("../models/getSearchPresets.js");
// Utils
const _ = require('lodash')
const base64 = require("../util/base64.js")
const scoreAndSearch = require("../util/scoreAndSearch.js");
const oldToNewQuerySyntax = require("../util/oldToNewQuerySyntax.js")
const { getUniqueTags } = require("../models/getTags.js");
module.exports = dbConn => {
/**
* @swagger
* /search:
* get:
* summary: Get Searchresults
* tags: [Search]
* parameters:
* - name: "q"
* in: "query"
* required: true
* type: int
* description: "Base64 encoded JS-Object with searchparameters"
* example: eyJmcm9tIjoxNTkzNjQ4MDAwMDAwLCJ0byI6MTU5NDI1MjgwMDAwMCwidGFncyI6W119
* responses:
* "200":
* description: Returns the region information and scores for the searchresults
*/
router.get("/api/v1/search", searchHandler(dbConn));
/**
* @swagger
* /search/presets:
* get:
* summary: Get the presets for the search parameters
* tags: [Search]
* responses:
* "200":
* description: Returns all presets for the search parameters
*/
router.get("/api/v1/search/presets", presetHandler(dbConn));
/**
* @swagger
* /search/tags:
* get:
* summary: Get the existing searchtags
* tags: [Search]
* responses:
* "200":
* description: Returns all existing searchtags
*/
router.get("/api/v1/search/tags", tagsHandler(dbConn));
return router;
};
function tagsHandler(dbConn) {
return function (req, res) {
getUniqueTags(dbConn).then(tags => {
res.json(tags.map(tag => tag.name))
}).catch(error => {
// TODO error handling
})
}
}
function presetHandler(dbConn) {
return function (req, res) {
getSearchPresets(dbConn).then(presets => {
res.json(presets)
}).catch(error => {
// TODO error handling
})
}
}
function searchHandler(dbConn) {
return async function (req, res) {
let response = {}
response.meta = {
params: req.params,
query: req.query,
headers: req.headers
}
// SWITCH TO SUPPORT OLD AND NEW BASE64 BASED QUERY SYNTAX
let q = req.query.q ? base64.base64ToObj(req.query.q) : req.query
console.log('Q:', q)
// transform syntax and seperate climate queries from price queries
if (!req.query.q) {
q = oldToNewQuerySyntax(q)
}
// CHOOSE PARAMS WHICH SHALL BE PASSED TO SCORE AND SEARCH
let scoreQueryObj = prepareQueries(q)
let [regions] = await Promise.all([getRegions(dbConn)])
let data = {
regions: regions,
}
// FILTER if query contains filterString
if (q.textfilter) {
data.regions = filterByString(data.regions, q.textfilter, q.fulltext)
}
scoreAndSearch(data, q.from, q.to, scoreQueryObj).then(searchResults => {
// only dev:
if (process.env.SHOW_MATCH_VALUE === '1') searchResults.forEach(reg => reg.name = `${reg.name} (${_.round(reg.score * 10, 1)}% match)`)
// FILTER NULLSCORES
if (!_.get(q, 'showRegionsWithNullScore', false)) {
console.log('without null scores');
searchResults.forEach(el => console.log('region:', el.name, 'score:', el.score))
searchResults = searchResults.filter(el => !_.some(el.scores, score => _.isNaN(score.score) || _.isNil(score.score) || score.score <= 0))
}
// SEND RESPONSE
res.json(searchResults)
}).catch(e => {
// TODO error handling
console.log(e)
res.status(400).send(e.message)
})
}
}
function filterByString(searchResults, filterString, boolFulltext) {
return _.filter(searchResults, region => {
// console.log("filtering: filterString, boolFulltext");
// console.log(filterString, boolFulltext);
filterString = filterString.toLowerCase()
let name = region.name.toLowerCase()
let country = region.country.toLowerCase()
if (boolFulltext) {
let desc = region.description.toLowerCase()
return name.includes(filterString) || country.includes(filterString) || desc.includes(filterString)
}
return name.includes(filterString) || country.includes(filterString)
})
}
function prepareQueries(queries) {
let q = {
climate: {},
costs: {},
others: {}
}
// climate
if (queries.temperature_mean_max) q.climate.temperature_mean_max = queries.temperature_mean_max
if (queries.precipitation) q.climate.precipitation = queries.precipitation
if (queries.rain_days) q.climate.rain_days = queries.rain_days
if (queries.sun_hours) q.climate.sun_hours = queries.sun_hours
// costs
if (queries.accommodation_costs) q.costs.accommodation_costs = queries.accommodation_costs
if (queries.food_costs) q.costs.food_costs = queries.food_costs
if (queries.alcohol_costs) q.costs.alcohol_costs = queries.alcohol_costs
if (queries.water_costs) q.costs.water_costs = queries.water_costs
if (queries.local_transportation_costs) q.costs.local_transportation_costs = queries.local_transportation_costs
if (queries.entertainment_costs) q.costs.entertainment_costs = queries.entertainment_costs
if (queries.average_per_day_costs) q.costs.average_per_day_costs = queries.average_per_day_costs
// others
if (queries.avg_price_relative) q.others.avg_price_relative = queries.avg_price_relative
if (queries.tags) q.others.tags = queries.tags
return q
}