Compare commits

...

20 Commits

Author SHA1 Message Date
b995123a36 added docs 2021-04-21 11:43:20 +02:00
Timo John
0ed58d6fa1 Add more comments 2020-07-10 15:35:38 +02:00
Timo John
29666babbc Add comments 2020-07-10 15:16:04 +02:00
Timo John
2c16bbf3e7 Merge branch 'feature/api-doc' into 'develop'
Feature/api doc

See merge request tjohn/cc-data!36
2020-07-10 11:21:25 +02:00
Timo John
8231127099 Small changes to Swagger API doc 2020-07-10 11:20:24 +02:00
Timo John
531f133875 Changed to better JSDoc Syntax 2020-07-10 11:20:24 +02:00
Timo John
4fa241ad60 Changed place and country docs so query parameters work 2020-07-10 11:20:24 +02:00
Timo John
3cccdab368 Changed regions doc so it works with query parameters 2020-07-10 11:20:24 +02:00
Timo John
ebb1304eeb Edited title and description 2020-07-10 11:20:24 +02:00
Timo John
8c4f67b106 Implemented basic Swagger. Reachable by ./api/v1/doc 2020-07-10 11:20:24 +02:00
Patrick Gebhardt
26c4954e37 Fix hovering cursor over place 2020-06-26 11:35:32 +02:00
Timo Volkmann
91e84f5cf1 Merge branch 'tv/ui-improvements' into 'develop'
ui improvements

See merge request tjohn/cc-data!35
2020-06-25 22:48:30 +02:00
8161f62ce0 further improvements for search ui 2020-06-25 22:45:53 +02:00
f2c7aec2d3 better spacing advanced search 2020-06-25 22:45:53 +02:00
Patrick Gebhardt
67ea0587db Fix advanced search 2020-06-25 22:18:21 +02:00
Patrick Gebhardt
eaaee3c14a Add fixed graph scales 2020-06-25 21:41:28 +02:00
Patrick Gebhardt
28b25da1ad Fix format string on numbers 2020-06-25 21:12:08 +02:00
Patrick Gebhardt
c3c0ad74a7 Add more parameters to advanced search 2020-06-25 20:54:50 +02:00
Patrick Gebhardt
aa4c994069 Fix minor search issues
- Reset sorting for a new search
- Descending sort is default for tags
2020-06-25 20:16:11 +02:00
Timo Volkmann
ad1c728876 Merge branch 'bugfix/timecalc' into 'develop'
some timetravels

See merge request tjohn/cc-data!34
2020-06-25 18:28:49 +02:00
32 changed files with 715 additions and 197 deletions

Binary file not shown.

BIN
Travopti_Docs.pdf Normal file

Binary file not shown.

View File

@ -14,14 +14,50 @@ const port = process.env.PORT
const search = require("./routes/search"); 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 places = require("./routes/place"); const places = require("./routes/place");
const update = require("./routes/update"); const update = require("./routes/update");
const app = express(); const app = express();
// Swagger API doc set up
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const swaggerOptions = {
swaggerDefinition: {
openapi: "3.0.0",
info: {
title: "TravOpti API",
version: "1.0.0",
description:
"Enable intrest controlled region searching with this API\n" +
"No API Key required.",
license: {
name: "Licensing Pending",
url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be"
},
contact: {
name: "travOpti",
url: "https://travopti.de/home",
email: "feedback@travopti.de"
}
},
servers: [
{
url: "https://travopti.de/api/v1"
}
]
},
apis: [
"./Routes/*.js",
"./Models/handleClimateUpdate.js",
"./Models/handleClimateUpdateV2.js",
]
};
const swaggerDocs = swaggerJsdoc(swaggerOptions);
(async () => { (async () => {
try { try {
// Connect to MariaDB
const dbConn = await dbConnection(); const dbConn = await dbConnection();
// Express middleware // Express middleware
@ -29,12 +65,12 @@ const app = express();
app.use(express.static(path.join(__dirname, "../../dist"))); app.use(express.static(path.join(__dirname, "../../dist")));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(cors()); app.use(cors());
app.use('/api/v1/doc', swaggerUi.serve, swaggerUi.setup(swaggerDocs, {explorer: false, docExpansion: "list"}));
// Express routes // Express routes
app.use(search(dbConn)); app.use(search(dbConn));
app.use(regions(dbConn)); app.use(regions(dbConn));
app.use(countries(dbConn)); app.use(countries(dbConn));
app.use(climate(dbConn));
app.use(places(dbConn)); app.use(places(dbConn));
app.use(update(dbConn)) app.use(update(dbConn))
@ -52,7 +88,6 @@ const app = express();
console.log(`Travopti backend listening at http://localhost:${port}`) console.log(`Travopti backend listening at http://localhost:${port}`)
}); });
} catch (error) { } catch (error) {
// TODO: logging
console.error("Failed to start the webserver"); console.error("Failed to start the webserver");
console.error(error); console.error(error);
} }

View File

@ -1,7 +1,7 @@
const axios = require("axios") const axios = require("axios")
const getPlacePhoto = require("./getPlacePhoto.js") const getPlacePhoto = require("./getPlacePhoto.js")
const fields = "photos,place_id,name,rating,geometry" const fields = "photos,place_id,name,rating,geometry" // Parameters for Google Place API
module.exports = async (q) => { module.exports = async (q) => {
const res = await axios.get( const res = await axios.get(

View File

@ -1,9 +1,9 @@
const axios = require("axios") const axios = require("axios")
const getPlacePhoto = require("./getPlacePhoto.js") const getPlacePhoto = require("./getPlacePhoto.js")
const radius = 20000 const radius = 20000 // Search radius in meters
const rankby = "prominence" const rankby = "prominence" // Sorting of results
const types = "tourist_attraction" const types = "tourist_attraction" // Category which shall be searched
module.exports = async (lat, lng) => { module.exports = async (lat, lng) => {
const res = await axios.get( const res = await axios.get(

View File

@ -1,45 +0,0 @@
exports.getBYTdataByRegion = async (dbConn, id, travelstyle = 1) => {
const res = await dbConn.query(
`SELECT
region_id,
travelstyle,
average_per_day AS average_per_day_costs,
accomodation AS accommodation_costs,
food AS food_costs,
water AS water_costs,
local_transportation AS local_transportation_costs,
entertainment AS entertainment_costs
FROM regions_byt
WHERE region_id = ? AND travelstyle = ?`,
[id, travelstyle]
);
return res;
};
exports.getAllBYTdata = async (dbConn, travelstyle = 1) => {
const res = await dbConn.query(
`SELECT
region_id,
travelstyle,
average_per_day AS average_per_day_costs,
accomodation AS accommodation_costs,
food AS food_costs,
water AS water_costs,
local_transportation AS local_transportation_costs,
entertainment AS entertainment_costs
FROM regions_byt
WHERE travelstyle = ?`,
[travelstyle]
);
return res;
};
exports.getTrivagoData = async (dbConn, id) => {
const region = await dbConn.query(
`...`,
[id]
);
return region;
};

View File

@ -58,17 +58,6 @@ module.exports = async (dbConn) => {
return regions.map(region => { return regions.map(region => {
/*
const emptyArr = Array.from({ length: 12 }, () => null)
if (region.avg_price_relative === null) region.avg_price_relative = undefined
if (region.temperature_mean === null) region.temperature_mean = undefined
if (region.temperature_mean_min === null) region.temperature_mean_min = undefined
if (region.temperature_mean_max === null) region.temperature_mean_max = undefined
if (region.precipitation === null) region.precipitation = undefined
if (region.rain_days === null) region.rain_days = undefined
if (region.sun_hours === null) region.sun_hours = undefined
if (region.humidity === null) region.humidity = undefined
*/
region.tags = tags.filter(tag => tag.region_id === region.region_id).map(tag => { region.tags = tags.filter(tag => tag.region_id === region.region_id).map(tag => {
delete tag.region_id delete tag.region_id
return tag return tag

View File

@ -11,15 +11,11 @@ module.exports = async (dbConn) => {
); );
for (k = 0; k < presets.length; k++) { for (k = 0; k < presets.length; k++) {
//if (presets[k].values.toString().includes("|")) {
const value = presets[k].value const value = presets[k].value
presets[k].value = value.split("|"); presets[k].value = value.split("|");
for (i = 0; i < presets[k].value.length; i++) { for (i = 0; i < presets[k].value.length; i++) {
presets[k].value[i] = parseFloat(presets[k].value[i]) presets[k].value[i] = parseFloat(presets[k].value[i])
} }
//} else {
// presets[k].values = parseInt(presets[k].values)
//}
} }
return presets; return presets;
}; };

View File

@ -1,11 +1,12 @@
const axios = require('axios') const axios = require('axios')
const _ = require('lodash') const _ = require('lodash')
// TODO: Automatically retrieve dates via aviable Data and get rid of random dates // Constants
// TODO: Automatically retrieve dates via aviable Data from database and get rid of "random" dates
const rangeStartDate = '2019-01' // If no date is given, this date will be used as startDate const rangeStartDate = '2019-01' // If no date is given, this date will be used as startDate
const rangeEndDate = '2020-05'// If no date is given, this date will be used as endDate const rangeEndDate = '2020-05'// If no date is given, this date will be used as endDate
// TODO: call method periodically, not over API // TODO: call method periodically, not over API (fine for prototyping, tho)
module.exports = async (dbConn, startDate = rangeStartDate, endDate = rangeEndDate) => { module.exports = async (dbConn, startDate = rangeStartDate, endDate = rangeEndDate) => {
console.log('update climate with:', startDate, endDate); console.log('update climate with:', startDate, endDate);
@ -54,7 +55,6 @@ async function createClimateObjectFrom(src, startDate, endDate) {
sun_hours: element.sunshine, sun_hours: element.sunshine,
humidity: element.humidity ? element.humidity : null humidity: element.humidity ? element.humidity : null
} }
//console.log(result)
return result return result
}) })
return retVal return retVal
@ -62,7 +62,6 @@ async function createClimateObjectFrom(src, startDate, endDate) {
async function writeToDatabase(dbConn, climateObjArr) { async function writeToDatabase(dbConn, climateObjArr) {
for (const element of climateObjArr) { for (const element of climateObjArr) {
//console.log(element)
try { try {
await dbConn.query(` await dbConn.query(`
INSERT INTO region_climate INSERT INTO region_climate

View File

@ -2,10 +2,11 @@ const axios = require('axios')
const _ = require('lodash') const _ = require('lodash')
// Constants // Constants
// TODO: Automatically retrieve dates via aviable Data from database and get rid of "random" dates
const rangeStartDate = '2019-01-01' // If no date is given, this date will be used as startDate const rangeStartDate = '2019-01-01' // If no date is given, this date will be used as startDate
const rangeEndDate = '2019-12-31'// If no date is given, this date will be used as endDate const rangeEndDate = '2019-12-31'// If no date is given, this date will be used as endDate
// TODO: call method periodically, not over API // TODO: call method periodically, not over API (fine for prototyping, tho)
module.exports = async (dbConn, startDate = rangeStartDate, endDate = rangeEndDate) => { module.exports = async (dbConn, startDate = rangeStartDate, endDate = rangeEndDate) => {
console.log('update climate with:', startDate, endDate); console.log('update climate with:', startDate, endDate);

View File

@ -1,7 +1,7 @@
const axios = require("axios") const axios = require("axios")
const getRegions = require("../models/getRegions.js") const getRegions = require("../models/getRegions.js")
const fields = "geometry" const fields = "geometry" // Parameters for Google Places API
module.exports = async (dbConn) => { module.exports = async (dbConn) => {
const regions = await getRegions(dbConn) const regions = await getRegions(dbConn)

View File

@ -4,6 +4,45 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-8.0.0.tgz",
"integrity": "sha512-n4YBtwQhdpLto1BaUCyAeflizmIbaloGShsPyRtFf5qdFJxfssj+GgLavczgKJFa3Bq+3St2CKcpRJdjtB4EBw==",
"requires": {
"@jsdevtools/ono": "^7.1.0",
"call-me-maybe": "^1.0.1",
"js-yaml": "^3.13.1"
}
},
"@apidevtools/openapi-schemas": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.0.3.tgz",
"integrity": "sha512-QoPaxGXfgqgGpK1p21FJ400z56hV681a8DOcZt3J5z0WIHgFeaIZ4+6bX5ATqmOoCpRCsH4ITEwKaOyFMz7wOA=="
},
"@apidevtools/swagger-methods": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.1.tgz",
"integrity": "sha512-1Vlm18XYW6Yg7uHunroXeunWz5FShPFAdxBbPy8H6niB2Elz9QQsCoYHMbcc11EL1pTxaIr9HXz2An/mHXlX1Q=="
},
"@apidevtools/swagger-parser": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-9.0.1.tgz",
"integrity": "sha512-Irqybg4dQrcHhZcxJc/UM4vO7Ksoj1Id5e+K94XUOzllqX1n47HEA50EKiXTCQbykxuJ4cYGIivjx/MRSTC5OA==",
"requires": {
"@apidevtools/json-schema-ref-parser": "^8.0.0",
"@apidevtools/openapi-schemas": "^2.0.2",
"@apidevtools/swagger-methods": "^3.0.0",
"@jsdevtools/ono": "^7.1.0",
"call-me-maybe": "^1.0.1",
"openapi-types": "^1.3.5",
"z-schema": "^4.2.2"
}
},
"@jsdevtools/ono": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz",
"integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ=="
},
"@sindresorhus/is": { "@sindresorhus/is": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@ -50,29 +89,6 @@
"negotiator": "0.6.2" "negotiator": "0.6.2"
} }
}, },
"agent-base": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz",
"integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==",
"requires": {
"debug": "4"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"ansi-align": { "ansi-align": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
@ -126,6 +142,14 @@
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
} }
}, },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"array-flatten": { "array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -142,8 +166,7 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"dev": true
}, },
"basic-auth": { "basic-auth": {
"version": "2.0.1", "version": "2.0.1",
@ -196,7 +219,6 @@
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -248,6 +270,11 @@
} }
} }
}, },
"call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms="
},
"camelcase": { "camelcase": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@ -342,11 +369,15 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"commander": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-5.0.0.tgz",
"integrity": "sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ=="
},
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"dev": true
}, },
"configstore": { "configstore": {
"version": "5.0.1", "version": "5.0.1",
@ -444,6 +475,14 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
}, },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"requires": {
"esutils": "^2.0.2"
}
},
"dot-prop": { "dot-prop": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
@ -505,6 +544,11 @@
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
}, },
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"etag": { "etag": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@ -598,6 +642,11 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
}, },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": { "fsevents": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
@ -622,6 +671,19 @@
"pump": "^3.0.0" "pump": "^3.0.0"
} }
}, },
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": { "glob-parent": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
@ -700,30 +762,6 @@
"resolved": "https://registry.npmjs.org/httpolyglot/-/httpolyglot-0.1.2.tgz", "resolved": "https://registry.npmjs.org/httpolyglot/-/httpolyglot-0.1.2.tgz",
"integrity": "sha1-5NNH/omEpi9GfUBg31J/GFH2mXs=" "integrity": "sha1-5NNH/omEpi9GfUBg31J/GFH2mXs="
}, },
"https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"requires": {
"agent-base": "6",
"debug": "4"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -750,6 +788,15 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true "dev": true
}, },
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@ -856,6 +903,15 @@
"integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
"dev": true "dev": true
}, },
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"json-buffer": { "json-buffer": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
@ -885,6 +941,16 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
}, },
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"long": { "long": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
@ -988,7 +1054,6 @@
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -1169,11 +1234,15 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
}, },
"openapi-types": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-1.3.5.tgz",
"integrity": "sha512-11oi4zYorsgvg5yBarZplAqbpev5HkuVNPlZaPTknPDzAynq+lnJdXAmruGWP0s+dNYZS7bjM+xrTpJw7184Fg=="
},
"p-cancelable": { "p-cancelable": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
@ -1214,6 +1283,11 @@
"util": "^0.10.3" "util": "^0.10.3"
} }
}, },
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-to-regexp": { "path-to-regexp": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
@ -1452,6 +1526,11 @@
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true "dev": true
}, },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sqlstring": { "sqlstring": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz",
@ -1526,6 +1605,39 @@
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
} }
}, },
"swagger-jsdoc": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-4.0.0.tgz",
"integrity": "sha512-wHrmRvE/OQa3d387YIrRNPvsPwxkJc0tAYeCVa359gUIKPjC4ReduFhqq/+4erLUS79kY1T5Fv0hE0SV/PgBig==",
"requires": {
"commander": "5.0.0",
"doctrine": "3.0.0",
"glob": "7.1.6",
"js-yaml": "3.13.1",
"swagger-parser": "9.0.1"
}
},
"swagger-parser": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-9.0.1.tgz",
"integrity": "sha512-oxOHUaeNetO9ChhTJm2fD+48DbGbLD09ZEOwPOWEqcW8J6zmjWxutXtSuOiXsoRgDWvORYlImbwM21Pn+EiuvQ==",
"requires": {
"@apidevtools/swagger-parser": "9.0.1"
}
},
"swagger-ui-dist": {
"version": "3.28.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.28.0.tgz",
"integrity": "sha512-aPkfTzPv9djSiZI1NUkWr5HynCUsH+jaJ0WSx+/t19wq7MMGg9clHm9nGoIpAtqml1G51ofI+I75Ym72pukzFg=="
},
"swagger-ui-express": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.4.tgz",
"integrity": "sha512-Ea96ecpC+Iq9GUqkeD/LFR32xSs8gYqmTW1gXCuKg81c26WV6ZC2FsBSPVExQP6WkyUuz5HEiR0sEv/HCC343g==",
"requires": {
"swagger-ui-dist": "^3.18.1"
}
},
"term-size": { "term-size": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz",
@ -1651,6 +1763,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
}, },
"validator": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz",
"integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -1668,8 +1785,7 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"dev": true
}, },
"write-file-atomic": { "write-file-atomic": {
"version": "3.0.3", "version": "3.0.3",
@ -1693,6 +1809,25 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"z-schema": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz",
"integrity": "sha512-zkvK/9TC6p38IwcrbnT3ul9in1UX4cm1y/VZSs4GHKIiDCrlafc+YQBgQBUdDXLAoZHf2qvQ7gJJOo6yT1LH6A==",
"requires": {
"commander": "^2.7.1",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"validator": "^12.0.0"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"optional": true
}
}
} }
} }
} }

View File

@ -21,7 +21,9 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql2": "^2.1.0", "mysql2": "^2.1.0",
"path": "^0.12.7", "path": "^0.12.7",
"sqlstring": "^2.3.2" "sqlstring": "^2.3.2",
"swagger-jsdoc": "^4.0.0",
"swagger-ui-express": "^4.1.4"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.4" "nodemon": "^2.0.4"

View File

@ -1,19 +0,0 @@
const router = require("express").Router()
// Models
const handleClimateUpdate = require("../models/handleClimateUpdate.js")
const handleClimateUpdateV2 = require("../models/handleClimateUpdateV2.js")
module.exports = dbConn => {
router.put("/api/v1/climate/update", async (req, res) => {
const update = await handleClimateUpdate(dbConn)
res.json(update)
});
router.put("/api/v1/climate/update/v2", async (req, res) => {
const update = await handleClimateUpdateV2(dbConn)
res.json(update)
});
return router;
};

View File

@ -1,3 +1,10 @@
/**
* @swagger
* tags:
* name: Countries
* description: Access country data.
*/
const router = require("express").Router(); const router = require("express").Router();
// Models // Models
@ -8,10 +15,37 @@ const getCountryById = require("../models/getCountryById.js");
const sqlSanitzer = require("../util/sqlstring_sanitizer.js") const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
module.exports = dbConn => { module.exports = dbConn => {
/**
* @swagger
* /countries:
* get:
* summary: Get all countries
* tags: [Countries]
* responses:
* "200":
* description: Returns aviable data for all countries
*/
router.get("/api/v1/countries", async (req, res) => { router.get("/api/v1/countries", async (req, res) => {
res.json(await getCountries(dbConn)); res.json(await getCountries(dbConn));
}); });
/**
* @swagger
* /countries/{id}:
* get:
* summary: Get a specific country by id
* tags: [Countries]
* parameters:
* - name: "id"
* in: "path"
* required: true
* type: int
* example: 23
* responses:
* "200":
* description: Returns aviable data for the country
* example: test
*/
router.get("/api/v1/countries/:id", async (req, res) => { router.get("/api/v1/countries/:id", async (req, res) => {
const id = sqlSanitzer(req.params.id); const id = sqlSanitzer(req.params.id);
res.json(await getCountryById(dbConn, id)) res.json(await getCountryById(dbConn, id))

View File

@ -1,3 +1,10 @@
/**
* @swagger
* tags:
* name: Places
* description: Access to the Google Place API via the Key used in backend. Only for manual use in the prototype application!
*/
const router = require("express").Router() const router = require("express").Router()
// Models // Models
@ -9,11 +16,51 @@ const getPlacePhoto = require("../models/getPlacePhoto.js")
const sqlSanitzer = require("../util/sqlstring_sanitizer.js") const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
module.exports = dbConn => { module.exports = dbConn => {
/**
* @swagger
* /place:
* get:
* summary: Get a specific place
* tags: [Places]
* parameters:
* - name: "q"
* in: "query"
* required: true
* type: int
* description: "Querystring, by which the place is searched"
* example: Berlin
* responses:
* "200":
* description: Returns a place from the google places API.
*/
router.get("/api/v1/place", async (req, res) => { router.get("/api/v1/place", async (req, res) => {
const place = await getPlace(req.query.q) const place = await getPlace(req.query.q)
res.json(place) res.json(place)
}); });
/**
* @swagger
* /place/nearby:
* get:
* summary: Get nearby touristic places
* tags: [Places]
* parameters:
* - name: "lat"
* in: "query"
* required: true
* type: float
* description: "Latitiude"
* example: 52.520365
* - name: "lng"
* in: "query"
* required: true
* type: float
* description: "Longitude"
* example: 13.403509
* responses:
* "200":
* description: Returns nearby places from the google places API.
*/
router.get("/api/v1/place/nearby", async (req, res) => { router.get("/api/v1/place/nearby", async (req, res) => {
const lat = req.query.lat const lat = req.query.lat
const lng = req.query.lng const lng = req.query.lng
@ -21,6 +68,23 @@ module.exports = dbConn => {
res.json(place) res.json(place)
}); });
/**
* @swagger
* /place/photo:
* get:
* summary: Get a photo for a place
* tags: [Places]
* parameters:
* - name: "photoref"
* in: "query"
* required: true
* type: int
* description: "Photo_Reference which is returned for a place by Google Places API"
* example: CmRaAAAAbupojmH94negtiCnLGdfx2azxhVTEDI1rtTrYnQ7KclEI-Yy9_YGxN9h63AKrCzd22kk5z-UiK7fS4-zXnO5OqfNRZu2hrmfcp8b77rItediibAVovOOA5LnyJ9YYuofEhAAr0Im0zuiAtbDKPjbPUSBGhTFkSrH6FZxenbo1bCkdCXaUMhOug
* responses:
* "200":
* description: Returns the matching url to the photo.
*/
router.get("/api/v1/place/photo", async (req, res) => { router.get("/api/v1/place/photo", async (req, res) => {
const photoref = req.query.photoref const photoref = req.query.photoref
const photo = await getPlacePhoto(photoref) const photo = await getPlacePhoto(photoref)

View File

@ -1,3 +1,10 @@
/**
* @swagger
* tags:
* name: Regions
* description: Access region data.
*/
const router = require("express").Router(); const router = require("express").Router();
// Models // Models
@ -12,6 +19,16 @@ const _ = require('lodash')
const sqlSanitzer = require("../util/sqlstring_sanitizer.js") const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
module.exports = dbConn => { module.exports = dbConn => {
/**
* @swagger
* /regions:
* get:
* summary: Get all regions
* tags: [Regions]
* responses:
* "200":
* description: Returns available data for all regions
*/
router.get("/api/v1/regions", async (req, res) => { router.get("/api/v1/regions", async (req, res) => {
const data = await getRegions(dbConn) const data = await getRegions(dbConn)
if (req.query.randomize) { if (req.query.randomize) {
@ -22,6 +39,21 @@ module.exports = dbConn => {
} }
}); });
/**
* @swagger
* /regions/{id}:
* get:
* summary: Get a specific region by id
* tags: [Regions]
* parameters:
* - name: "id"
* in: "path"
* required: true
* type: int
* responses:
* "200":
* description: Returns available data for the region
*/
router.get("/api/v1/regions/:id", async (req, res) => { router.get("/api/v1/regions/:id", async (req, res) => {
console.log(typeof req.params.id) console.log(typeof req.params.id)
const id = sqlSanitzer(req.params.id); const id = sqlSanitzer(req.params.id);
@ -29,14 +61,49 @@ module.exports = dbConn => {
res.json(await getRegionById(dbConn, id)) res.json(await getRegionById(dbConn, id))
}); });
/**
* @swagger
* /regions/{id}/image:
* get:
* summary: Get image for specific region
* tags: [Regions]
* parameters:
* - name: "id"
* in: "path"
* required: true
* type: int
* responses:
* "200":
* description: Returns the image for a specific region
* "404":
* description: Returns a placeholder image for the region
*/
router.get('/api/v1/regions/:id/image', (req, res) => { router.get('/api/v1/regions/:id/image', (req, res) => {
console.log("HERE")
if (fs.existsSync(path.join(__dirname, `../data/regions/images/${req.params.id}.jpg`))) { if (fs.existsSync(path.join(__dirname, `../data/regions/images/${req.params.id}.jpg`))) {
res.sendFile(path.join(__dirname, `../data/regions/images/${req.params.id}.jpg`)) console.log("EXISTS")
res.status(200).sendFile(path.join(__dirname, `../data/regions/images/${req.params.id}.jpg`))
} else { } else {
res.sendFile(path.join(__dirname, `../data/regions/images/x.png`)) console.log("NOT EXISTS")
res.status(404).sendFile(path.join(__dirname, `../data/regions/images/x.png`))
} }
}) })
/**
* @swagger
* /regions/{id}/nearby:
* get:
* summary: Get nearby places of a specific region by id
* tags: [Regions]
* parameters:
* - name: "id"
* in: "path"
* required: true
* type: int
* responses:
* "200":
* description: Returns all nearby places for the region
*/
router.get("/api/v1/regions/:id/nearby", async (req,res) => { router.get("/api/v1/regions/:id/nearby", async (req,res) => {
const id = sqlSanitzer(req.params.id); const id = sqlSanitzer(req.params.id);
res.json(await getRegionNearbyById(dbConn,id)) res.json(await getRegionNearbyById(dbConn,id))

View File

@ -1,3 +1,10 @@
/**
* @swagger
* tags:
* name: Search
* description: Access the search algorithm and the data provided for searching.
*/
const router = require("express").Router(); const router = require("express").Router();
// Models // Models
@ -12,8 +19,47 @@ const oldToNewQuerySyntax = require("../util/oldToNewQuerySyntax.js")
const { getUniqueTags } = require("../models/getTags.js"); const { getUniqueTags } = require("../models/getTags.js");
module.exports = dbConn => { 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)); 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)); 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)); router.get("/api/v1/search/tags", tagsHandler(dbConn));
return router; return router;

View File

@ -1,6 +1,15 @@
/**
* @swagger
* tags:
* name: Update
* description: Endpoint for updating region specific data. Only for manual use in the prototype application!
*/
const router = require("express").Router(); const router = require("express").Router();
// Models // Models
const handleClimateUpdate = require("../models/handleClimateUpdate.js")
const handleClimateUpdateV2 = require("../models/handleClimateUpdateV2.js")
const handleUpdateRegionNearby = require("../models/handleUpdateRegionNearby.js") const handleUpdateRegionNearby = require("../models/handleUpdateRegionNearby.js")
const handleUpdateRegionNearbyById = require("../models/handleUpdateRegionNearbyById.js") const handleUpdateRegionNearbyById = require("../models/handleUpdateRegionNearbyById.js")
const handleUpdateRegionNearbyImgUrl = require("../models/handleUpdateRegionNearbyImgUrl.js") const handleUpdateRegionNearbyImgUrl = require("../models/handleUpdateRegionNearbyImgUrl.js")
@ -10,24 +19,114 @@ const handleUpdateRegionNearbyImgUrlById = require("../models/handleUpdateRegion
const sqlSanitzer = require("../util/sqlstring_sanitizer.js") const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
module.exports = dbConn => { module.exports = dbConn => {
router.patch("/api/v1/update/regions/all/nearby", async (req, res) => { /**
* @swagger
* /update/climate/v1:
* put:
* summary: Pull monthly data from meteostat API V1
* tags: [Update]
* responses:
* "200":
* description: Update information is logged in backend
*/
router.put("/api/v1/update/climate/v1", async (req, res) => {
const update = await handleClimateUpdate(dbConn)
res.json(update)
});
/**
* @swagger
* /update/climate/v2:
* put:
* summary: Pull daily data from meteostat API V2. Data is written to Travopti database and must be processed manually before it can be used.
* tags: [Update]
* responses:
* "200":
* description: Update information is logged in backend
*/
router.put("/api/v1/update/climate/v2", async (req, res) => {
const update = await handleClimateUpdateV2(dbConn)
res.json(update)
});
/**
* @swagger
* /update/regions/all/nearby:
* put:
* summary: Updates all nearby data for all regions
* tags: [Update]
* responses:
* "200":
* description: Updates all nearby data for all regions
*/
router.put("/api/v1/update/regions/all/nearby", async (req, res) => {
res.json(await handleUpdateRegionNearby(dbConn)) res.json(await handleUpdateRegionNearby(dbConn))
}); });
router.patch("/api/v1/update/regions/all/lonlat", async (req,res) => { /**
* @swagger
* /update/regions/all/lonlat:
* put:
* summary: Updates all coordinate data for all regions
* tags: [Update]
* responses:
* "200":
* description: Updates all coordinate data for all regions
*/
router.put("/api/v1/update/regions/all/lonlat", async (req,res) => {
res.json(await handleRegionLonLat(dbConn)) res.json(await handleRegionLonLat(dbConn))
}); });
router.patch("/api/v1/update/regions/:id/nearby", async (req, res) => { /**
* @swagger
* /update/regions/all/nearby/image:
* put:
* summary: Updates the nearby image urls for all regions
* tags: [Update]
* responses:
* "200":
* description: Updates the nearby image urls for all regions
*/
router.put("/api/v1/update/regions/all/nearby/image", async (req, res) => {
res.json(await handleUpdateRegionNearbyImgUrl(dbConn))
});
/**
* @swagger
* /update/regions/{id}/nearby:
* put:
* summary: Updates the nearby data for a specific region
* tags: [Update]
* parameters:
* - name: "id"
* in: "path"
* required: true
* type: int
* responses:
* "200":
* description: Updates the nearby data for a specific region
*/
router.put("/api/v1/update/regions/:id/nearby", async (req, res) => {
const id = sqlSanitzer(req.params.id); const id = sqlSanitzer(req.params.id);
res.json(await handleUpdateRegionNearbyById(dbConn, id)) res.json(await handleUpdateRegionNearbyById(dbConn, id))
}); });
router.patch("/api/v1/update/regions/all/nearby/imgurl", async (req, res) => { /**
res.json(await handleUpdateRegionNearbyImgUrl(dbConn)) * @swagger
}); * /update/regions/{id}/nearby/image:
* put:
router.patch("/api/v1/update/regions/:id/nearby/imgurl", async (req, res) => { * summary: Updates the nearby image urls for a specific region
* tags: [Update]
* parameters:
* - name: "id"
* in: "path"
* required: true
* type: int
* responses:
* "200":
* description: Updates the nearby image urls for a specific region
*/
router.put("/api/v1/update/regions/:id/nearby/image", async (req, res) => {
const id = sqlSanitzer(req.params.id); const id = sqlSanitzer(req.params.id);
res.json(await handleUpdateRegionNearbyImgUrlById(dbConn, id)) res.json(await handleUpdateRegionNearbyImgUrlById(dbConn, id))
}); });

View File

@ -14,10 +14,20 @@ exports.base64ToObj = function(base64) {
return JSON.parse(atob(base64)); return JSON.parse(atob(base64));
} }
/**
* Decodes a base64 encoded object.
* @param base64 encoded object
* @returns {string} decoded object
*/
function atob(base64) { function atob(base64) {
return Buffer.from(base64, 'base64').toString('binary') return Buffer.from(base64, 'base64').toString('binary')
} }
/**
* Encodes an object as base64 string.
* @param string The object to encode
* @returns {string} base64 encoded object
*/
function btoa(string) { function btoa(string) {
return Buffer.from(string).toString('base64') return Buffer.from(string).toString('base64')
} }

View File

@ -1,3 +1,8 @@
/**
* Seperate Strings created via GROUP_CONCAT by database into an array
* @param array String with comma-seperated values
* @returns [float] array of float values
*/
module.exports = (array) => { module.exports = (array) => {
if (array !== null && array !== undefined) { if (array !== null && array !== undefined) {
const value = array const value = array

View File

@ -1,5 +1,3 @@
module.exports = function (dbConn) { module.exports = function (dbConn) {
return async function getAllRegionsWithClimatePerMonth(month) { return async function getAllRegionsWithClimatePerMonth(month) {
console.log('getAllRegionsWithClimatePerMonth') console.log('getAllRegionsWithClimatePerMonth')

View File

@ -1,7 +1,12 @@
const sqlstring = require("sqlstring") const sqlstring = require("sqlstring")
/**
* Sanitizes value if it isn't a numerical value
* @param val
* @returns string Sanitized String
*/
module.exports = (val) => { module.exports = (val) => {
if(!isNaN(val)) { if(!isNaN(val)) { // Checks if the value is a numerical value (in a string)
return val return val
} else { } else {
return sqlstring.escape(val) return sqlstring.escape(val)

View File

@ -20,6 +20,8 @@ export class GraphComponent implements AfterViewInit {
formatSting: string; formatSting: string;
@Input() @Input()
graphType = 'line'; graphType = 'line';
@Input()
minMax: number[];
readonly randomId = uuidv4(); readonly randomId = uuidv4();
@ -60,6 +62,14 @@ export class GraphComponent implements AfterViewInit {
data[i].color = this.colors[i]; data[i].color = this.colors[i];
} }
let axisY: CanvasJS.ChartOptions.ChartAxisY;
if (this.minMax) {
axisY = {
minimum: this.minMax[0],
maximum: this.minMax[1]
};
}
const chart = new CanvasJS.Chart(this.randomId, { const chart = new CanvasJS.Chart(this.randomId, {
animationEnabled: true, animationEnabled: true,
@ -70,6 +80,7 @@ export class GraphComponent implements AfterViewInit {
horizontalAlign: 'left', horizontalAlign: 'left',
dockInsidePlotArea: true dockInsidePlotArea: true
}, },
axisY,
data data
}); });

View File

@ -25,6 +25,7 @@
<!-- Multi presets --> <!-- Multi presets -->
<mat-step> <mat-step>
<ng-template matStepLabel>Which climate do you prefer?</ng-template> <ng-template matStepLabel>Which climate do you prefer?</ng-template>
<div class="flexer">
<div *ngFor="let key of multiPresetsKeys" class="sub-group"> <div *ngFor="let key of multiPresetsKeys" class="sub-group">
<span class="label">{{key|translate}}:</span><br> <span class="label">{{key|translate}}:</span><br>
<mat-radio-group [ngModel]="multiPresetSelection[key]" [value]="undefined"> <mat-radio-group [ngModel]="multiPresetSelection[key]" [value]="undefined">
@ -36,6 +37,7 @@
>{{preset.tag_label|translate}}</mat-radio-button> >{{preset.tag_label|translate}}</mat-radio-button>
</mat-radio-group> </mat-radio-group>
</div> </div>
</div>
</mat-step> </mat-step>
<!-- Single presets --> <!-- Single presets -->
<mat-step> <mat-step>
@ -81,9 +83,9 @@
<mat-icon>clear</mat-icon> <mat-icon>clear</mat-icon>
</button> </button>
</mat-form-field> </mat-form-field>
<div class="horizontal space center"> <div class="horizontal space center-desc">
<span>Search in description </span>
<mat-slide-toggle [(ngModel)]="fullText"></mat-slide-toggle> <mat-slide-toggle [(ngModel)]="fullText"></mat-slide-toggle>
<span>Search in description </span>
</div> </div>
</div> </div>
</section> </section>
@ -93,19 +95,36 @@
<span class="title">Climate</span> <span class="title">Climate</span>
<span class="desc">| sweetspot selection</span> <span class="desc">| sweetspot selection</span>
</div> </div>
<app-toggle-slider [(model)]="temperatureMeanMax" [label]="'Temperature'" [max]="45" <app-toggle-slider [(model)]="temperatureMeanMax" [label]="'Temperature (°C)'" [max]="45"
[min]="0"></app-toggle-slider> [min]="0"></app-toggle-slider>
<app-toggle-slider [(model)]="precipitation" [label]="'Precipitation'" [max]="500" <app-toggle-slider [(model)]="precipitation" [label]="'Precipitation (mm)'" [max]="500"
[min]="0"></app-toggle-slider>
<app-toggle-slider [(model)]="sunHours" [label]="'Sun hours'" [max]="400"
[min]="0"></app-toggle-slider>
<app-toggle-slider [(model)]="rainDays" [label]="'Rain days'" [max]="31"
[min]="0"></app-toggle-slider> [min]="0"></app-toggle-slider>
</section> </section>
<!-- Financial --> <!-- Financial -->
<section class="group"> <section class="group">
<div class="horizontal"> <div class="horizontal">
<span class="title">Financial</span> <span class="title">Financial</span>
<span class="desc">| sweetspot selection</span> <span class="desc">| sweetspot selection (€/day)</span>
</div> </div>
<app-toggle-slider [(model)]="accommodation" [label]="'Accommodation'" [max]="60" [min]="0"></app-toggle-slider> <app-toggle-slider [(model)]="costPerDay" [label]="'Total cost'" [max]="400" [min]="0"></app-toggle-slider>
<app-toggle-slider [(model)]="accommodation" [label]="'Accommodation'" [max]="200"
[min]="0"></app-toggle-slider>
<app-toggle-slider [(model)]="localTransport" [label]="'Local transport'" [max]="20"
[min]="0"></app-toggle-slider>
</section> </section>
<!-- Tags -->
<section class="group">
<div class="horizontal">
<span class="title">Tags</span>
<span class="desc">| Search by user ratings</span>
</div>
<app-multi-tag-select [(model)]="selectedTags" [availableTags]="tags"></app-multi-tag-select>
</section>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>

View File

@ -2,7 +2,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
> .search-btn { >.search-btn {
margin-top: 1rem; margin-top: 1rem;
} }
} }
@ -31,6 +31,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 1rem 0; margin: 1rem 0;
overflow: hidden;
.title { .title {
font-size: 1.2rem; font-size: 1.2rem;
@ -45,13 +46,18 @@
color: gray; color: gray;
} }
> .content { >.content {
margin: 0 2.5rem; // margin: 0 2.5rem;
margin-top: 0.5rem;
.text-input { .text-input {
min-width: 14rem; min-width: 14rem;
} }
} }
> app-multi-tag-select {
margin: 1rem;
}
} }
.horizontal { .horizontal {
@ -60,7 +66,7 @@
align-items: center; align-items: center;
&.space { &.space {
> * { >* {
margin: 0.5rem; margin: 0.5rem;
} }
} }
@ -69,6 +75,10 @@
justify-content: space-around; justify-content: space-around;
} }
&.center-desc {
justify-content: center;
}
} }
@ -81,15 +91,30 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-evenly; justify-content: space-between;
align-items: center; align-items: center;
margin-left: -0.5rem;
margin-right: -0.5rem;
> * { >* {
width: 45%; flex-grow: 1;
min-width: 15rem; margin: 0 0.5rem;
} }
} }
.small-bottom-margin { .small-bottom-margin {
margin-bottom: 0.4rem !important; margin-bottom: 0.4rem !important;
} }
.flexer {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
margin-right: -1rem;
> .sub-group {
flex-grow: 1;
margin-right: 1rem;
}
}

View File

@ -6,6 +6,7 @@ import {PresetService} from '../../services/preset.service';
import {Preset} from '../../interfaces/preset.interface'; import {Preset} from '../../interfaces/preset.interface';
import {formatDate} from '@angular/common'; import {formatDate} from '@angular/common';
import {SearchService} from '../../services/search.service'; import {SearchService} from '../../services/search.service';
import {toMinMaxArray} from '../../utils/toMinMaxArray';
@Component({ @Component({
selector: 'app-search-input', selector: 'app-search-input',
@ -37,7 +38,12 @@ export class SearchInputComponent implements OnInit {
fullText = false; fullText = false;
temperatureMeanMax: number; temperatureMeanMax: number;
precipitation: number; precipitation: number;
rainDays: number;
sunHours: number;
accommodation: number; accommodation: number;
costPerDay: number;
entertainment: number;
localTransport: number;
readonly today = this.from = formatDate(new Date(), 'yyyy-MM-dd', 'en-GB'); readonly today = this.from = formatDate(new Date(), 'yyyy-MM-dd', 'en-GB');
@ -124,6 +130,7 @@ export class SearchInputComponent implements OnInit {
const query: Query = { const query: Query = {
from: new Date(this.from).getTime(), from: new Date(this.from).getTime(),
to: new Date(this.to).getTime(), to: new Date(this.to).getTime(),
tags: this.selectedTags
}; };
if (this.textFilter.length > 0) { if (this.textFilter.length > 0) {
@ -131,9 +138,14 @@ export class SearchInputComponent implements OnInit {
query.textfilter = this.textFilter; query.textfilter = this.textFilter;
} }
query.temperature_mean_max = this.temperatureMeanMax ? [this.temperatureMeanMax, this.temperatureMeanMax] : undefined; query.temperature_mean_max = toMinMaxArray(this.temperatureMeanMax);
query.precipitation = this.precipitation ? [this.precipitation, this.precipitation] : undefined; query.precipitation = toMinMaxArray(this.precipitation);
query.accommodation_costs = this.accommodation ? [this.accommodation, this.accommodation] : undefined; query.sun_hours = toMinMaxArray(this.sunHours);
query.rain_days = toMinMaxArray(this.rainDays);
query.average_per_day_costs = toMinMaxArray(this.costPerDay);
query.accommodation_costs = toMinMaxArray(this.accommodation);
query.entertainment_costs = toMinMaxArray(this.entertainment);
query.local_transportation_costs = toMinMaxArray(this.localTransport);
return query; return query;
} }
@ -150,7 +162,12 @@ export class SearchInputComponent implements OnInit {
textFiler: this.textFilter, textFiler: this.textFilter,
tempMeanMax: this.temperatureMeanMax, tempMeanMax: this.temperatureMeanMax,
precipitation: this.precipitation, precipitation: this.precipitation,
rain_days: this.rainDays,
sun_hours: this.sunHours,
average_per_day_costs: this.costPerDay,
accommodation: this.accommodation, accommodation: this.accommodation,
entertainment_costs: this.entertainment,
local_transportation_costs: this.localTransport,
}); });
} }
@ -168,7 +185,12 @@ export class SearchInputComponent implements OnInit {
this.selectedTab = prevInput.wasAdvanced ? 1 : 0; this.selectedTab = prevInput.wasAdvanced ? 1 : 0;
this.temperatureMeanMax = prevInput.tempMeanMax; this.temperatureMeanMax = prevInput.tempMeanMax;
this.precipitation = prevInput.precipitation; this.precipitation = prevInput.precipitation;
this.rainDays = prevInput.rain_days;
this.sunHours = prevInput.sun_hours;
this.costPerDay = prevInput.average_per_day_costs;
this.accommodation = prevInput.accommodation; this.accommodation = prevInput.accommodation;
this.entertainment = prevInput.entertainment_costs;
this.localTransport = prevInput.local_transportation_costs;
} }
} }

View File

@ -27,7 +27,8 @@
<mat-tab-group> <mat-tab-group>
<mat-tab *ngIf="region.avg_price_relative" label="Price Deviation"> <mat-tab *ngIf="region.avg_price_relative" label="Price Deviation">
<ng-template matTabContent> <ng-template matTabContent>
<app-graph [monthlyDatas]="[region.avg_price_relative]" class="graph" formatSting="##,##%" graphType="column"> <app-graph [monthlyDatas]="[region.avg_price_relative]" class="graph" formatSting="##0.##'%'"
graphType="column">
</app-graph> </app-graph>
</ng-template> </ng-template>
</mat-tab> </mat-tab>
@ -35,25 +36,27 @@
<ng-template matTabContent> <ng-template matTabContent>
<app-graph [colors]="['blue', 'red']" [labels]="['Min', 'Max']" <app-graph [colors]="['blue', 'red']" [labels]="['Min', 'Max']"
[monthlyDatas]="[region.temperature_mean_min, region.temperature_mean_max]" class="graph" [monthlyDatas]="[region.temperature_mean_min, region.temperature_mean_max]" class="graph"
formatSting="##,##°C"> [minMax]="[-25, 50]" formatSting="##,##°C">
</app-graph> </app-graph>
</ng-template> </ng-template>
</mat-tab> </mat-tab>
<mat-tab *ngIf="region.precipitation" label="Precipitation"> <mat-tab *ngIf="region.precipitation" label="Precipitation">
<ng-template matTabContent> <ng-template matTabContent>
<app-graph [monthlyDatas]="[region.precipitation]" class="graph" formatSting="####mm"> <app-graph [minMax]="[0, 1200]" [monthlyDatas]="[region.precipitation]" class="graph" formatSting="####'mm'">
</app-graph> </app-graph>
</ng-template> </ng-template>
</mat-tab> </mat-tab>
<mat-tab *ngIf="region.rain_days" label="Rain days"> <mat-tab *ngIf="region.rain_days" label="Rain days">
<ng-template matTabContent> <ng-template matTabContent>
<app-graph [monthlyDatas]="[region.rain_days]" class="graph" formatSting="####" graphType="column"> <app-graph [minMax]="[0, 31]" [monthlyDatas]="[region.rain_days]" class="graph" formatSting="####"
graphType="column">
</app-graph> </app-graph>
</ng-template> </ng-template>
</mat-tab> </mat-tab>
<mat-tab *ngIf="region.sun_hours" label="Sun hours"> <mat-tab *ngIf="region.sun_hours" label="Sun hours">
<ng-template matTabContent> <ng-template matTabContent>
<app-graph [monthlyDatas]="[region.sun_hours]" class="graph" formatSting="####" graphType="column"> <app-graph [minMax]="[0, 450]" [monthlyDatas]="[region.sun_hours]" class="graph" formatSting="####"
graphType="column">
</app-graph> </app-graph>
</ng-template> </ng-template>
</mat-tab> </mat-tab>

View File

@ -84,6 +84,7 @@
app-place { app-place {
margin: 0.5rem; margin: 0.5rem;
cursor: pointer;
} }
} }
} }

View File

@ -61,12 +61,16 @@ export class SearchComponent implements OnInit {
{name: 'Price', property: 'average_per_day_costs'} {name: 'Price', property: 'average_per_day_costs'}
]; ];
this.sortBy = 'score';
this.sortDes = true;
if (this.results.length > 0) { if (this.results.length > 0) {
this.results[0].scores.forEach(({type}) => { const tags = await this.ss.getAvailableTags();
for (const {type} of this.results[0].scores) {
if (!this.sortOptions.find(i => i.property === type)) { if (!this.sortOptions.find(i => i.property === type)) {
this.sortOptions.push({name: type, property: type, isScore: true}); this.sortOptions.push({name: type, property: type, isScore: true, descending: tags.includes(type)});
}
} }
});
} }
// Calculate duration // Calculate duration

View File

@ -13,7 +13,12 @@ export interface SearchInput {
fullText: boolean; fullText: boolean;
tempMeanMax: number; tempMeanMax: number;
precipitation: number; precipitation: number;
rain_days: number;
sun_hours: number;
average_per_day_costs: number;
accommodation: number; accommodation: number;
entertainment_costs: number;
local_transportation_costs: number;
} }
@Injectable({ @Injectable({

View File

@ -0,0 +1,7 @@
/**
* Transforms a value into a min max array.
* @param value The value
*/
export function toMinMaxArray(value: number): number[] {
return value ? [value, value] : undefined;
}