Compare commits

...

226 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
2b0c030980 some timetravels 2020-06-25 18:27:52 +02:00
Patrick Gebhardt
1cda6bfe70 Fix app visual 2020-06-25 16:05:48 +02:00
Timo John
c1198c98db Added method for tags by region id 2020-06-25 15:49:14 +02:00
Timo John
cc64fb1c32 hotfix: this time for real 2020-06-25 15:42:27 +02:00
Patrick Gebhardt
f6dd580894 Improve advanced search visuals 2020-06-25 15:34:34 +02:00
Patrick Gebhardt
8c95857207 Add sun hours to monthly chart 2020-06-25 15:34:34 +02:00
Patrick Gebhardt
509ccea760 Fix diagrams not shown for january null 2020-06-25 15:34:34 +02:00
Timo John
7a6be53e0b hotfix: search handles null values instead of arrays full of nulls 2020-06-25 15:15:04 +02:00
Timo John
85efd83485 If no value is there in a region, null is returned 2020-06-25 15:04:44 +02:00
Timo Volkmann
d67c0ef625 Merge branch 'refactor/scorer-cleanup' into 'develop'
cleaned up and reduced scorer overhead

See merge request tjohn/cc-data!33
2020-06-25 15:02:47 +02:00
6145c40654 cleaned up and reduced scorer overhead 2020-06-25 15:01:21 +02:00
Patrick Gebhardt
2060eea09f Add share button to side-nav 2020-06-25 13:08:24 +02:00
Patrick Gebhardt
218b508f43 Merge branch 'feature/total-price-details' into 'develop'
Add total costs to results

See merge request tjohn/cc-data!32
2020-06-25 12:28:16 +02:00
Patrick Gebhardt
44b09a22e8 Add total costs to results 2020-06-25 12:27:10 +02:00
6be202a0d2 changed result layout 2020-06-25 11:58:59 +02:00
Patrick Gebhardt
cea47548bc Merge branch 'frontend/wording' into 'develop'
Frontend/wording

See merge request tjohn/cc-data!31
2020-06-25 00:18:00 +02:00
Patrick Gebhardt
24ad736091 Improve wording 2020-06-25 00:16:43 +02:00
2077aeebf8 removed unnecessary package from backend 2020-06-24 23:28:13 +02:00
Timo John
7b76f2f696 Newest Version of Setup.sql 2020-06-24 22:32:24 +02:00
Timo John
08a904da7e Merge branch 'feature/weather' into 'develop'
Feature/weather

See merge request tjohn/cc-data!30
2020-06-24 17:24:29 +02:00
Timo John
5fa9d982fb Update update.js 2020-06-24 17:24:12 +02:00
Timo John
d6af174904 Implemented Daily weather API so every region has weather data 2020-06-24 17:24:12 +02:00
Patrick Gebhardt
d2f9d992f5 Change theme color 2020-06-24 17:21:01 +02:00
Patrick Gebhardt
e194019eae Add favicon 2020-06-24 17:05:20 +02:00
Patrick Gebhardt
86c4bc5cd2 Add travopti logo to header 2020-06-24 16:59:25 +02:00
Patrick Gebhardt
384992634d Merge branch 'feature/search-tag-ui' into 'develop'
Add tag search UI

See merge request tjohn/cc-data!29
2020-06-24 15:36:31 +02:00
Patrick Gebhardt
93a51ec922 Add tag search UI 2020-06-24 15:35:53 +02:00
Patrick Gebhardt
43953757e7 Update team page information 2020-06-24 14:11:14 +02:00
Timo Volkmann
2c1d09297a Merge branch 'feature/tags-search-backend' into 'develop'
Feature/tags search backend

See merge request tjohn/cc-data!28
2020-06-24 14:08:02 +02:00
bbe74b83bb implemented search for tags feature backend 2020-06-24 14:06:47 +02:00
e0f8f5490e some tag improvements 2020-06-24 14:06:47 +02:00
Patrick Gebhardt
cd9f68234f Fix explore the world layout for FireFox 2020-06-24 13:38:12 +02:00
Timo John
91ab7ae971 Merge branch 'feature/weather' into 'develop'
Feature/weather

See merge request tjohn/cc-data!27
2020-06-24 13:23:48 +02:00
Timo John
3ae3aca9a5 Hotfix: sql sanitzer now checks if parameter is a number 2020-06-24 13:23:48 +02:00
Patrick Gebhardt
8c296e9397 Add http error messages 2020-06-24 12:57:04 +02:00
Timo John
6cd226bf56 Merge branch 'develop' of https://git.it.hs-heilbronn.de/tjohn/cc-data into develop 2020-06-24 11:51:07 +02:00
Timo John
b5bcae6ada Updated setup.sql to newest version of database 2020-06-24 11:50:49 +02:00
Patrick Gebhardt
72c1386fb8 Fix search layout 2020-06-24 11:48:42 +02:00
Timo John
ae9998f640 Added protection against SQL Injection 2020-06-24 11:47:15 +02:00
Timo Volkmann
2fb2e9a76f Merge branch 'feature/tags-backend' into 'develop'
implemented tags endpoint

See merge request tjohn/cc-data!26
2020-06-24 10:46:09 +02:00
373429b870 implemented tags endpoint 2020-06-24 10:44:32 +02:00
Timo John
20d99f2145 Remoddeled /update API endpoint 2020-06-24 09:14:22 +02:00
Timo John
df083fd78e Add Endpoint for Reloading Imgurl for speicific place 2020-06-24 08:55:53 +02:00
Patrick Gebhardt
3dece30145 Add google maps search to places 2020-06-24 00:01:18 +02:00
Patrick Gebhardt
0e65d6e919 Fix search layout 2020-06-23 23:33:22 +02:00
Patrick Gebhardt
8ae20d96aa Merge branch 'feature/nearby-places' into 'develop'
Add nearby places

See merge request tjohn/cc-data!25
2020-06-23 23:08:51 +02:00
Patrick Gebhardt
56b8c06b84 Add nearby places 2020-06-23 23:02:41 +02:00
Patrick Gebhardt
846410e6bf Add accommodation to permanent sort options 2020-06-23 21:36:16 +02:00
Patrick Gebhardt
1fb6f607a2 Add total accommodation costs to results 2020-06-23 21:25:04 +02:00
5dfb31e91c fixed empty response, code 204 -> 200 2020-06-23 18:54:35 +02:00
Patrick Gebhardt
f9157e8e61 Add search result sorting 2020-06-23 16:42:59 +02:00
Timo John
e948243417 Changed endpoint to /api/v1/regions/:id/nearby 2020-06-23 16:33:58 +02:00
Timo John
5b4ef3c794 Merge branch 'feature/place' into 'develop'
Feature/place

See merge request tjohn/cc-data!24
2020-06-23 16:18:41 +02:00
Timo John
5d05625658 Feature/place 2020-06-23 16:18:41 +02:00
Timo Volkmann
8c1daa308c Merge branch 'feature/accomodation-accumulated-backend' into 'develop'
calculate accommodation total

See merge request tjohn/cc-data!23
2020-06-23 15:06:12 +02:00
faa7a7e5b1 removed incorrectly committed files 2020-06-23 15:04:50 +02:00
Timo Volkmann
5d2f64a688 Merge branch 'bugfix/showscore' into 'develop'
show_match_value now setted through env

See merge request tjohn/cc-data!22
2020-06-23 14:14:07 +02:00
125c6a3f00 show_match_value now setted through env 2020-06-23 14:13:34 +02:00
Patrick Gebhardt
fa38932010 Add compat-data package to dev dependencies 2020-06-23 13:30:52 +02:00
Timo Volkmann
8d2b40f0cd Merge branch 'experimental/nullscorehandling' into 'develop'
Search optimized

See merge request tjohn/cc-data!21
2020-06-23 13:19:02 +02:00
826dd64f1a score balancing 2020-06-23 13:17:02 +02:00
c347b79f02 changed search behaviour 2020-06-23 13:17:02 +02:00
cdda87f3c6 score balancing 2020-06-23 13:17:02 +02:00
6180d2f097 tested different scoring slopes 2020-06-23 13:17:02 +02:00
Patrick Gebhardt
d953046539 Change relative price chart type to column 2020-06-23 13:08:31 +02:00
Lucas Hinderberger
24f47b5f39 Merge branch 'feature/fix_search_dates' into 'develop'
Fixes fixed bug of the search when searching over the turn of the year

See merge request tjohn/cc-data!20
2020-06-23 12:36:52 +02:00
Lucas Hinderberger
fa73c0a1e0 Fixes fixed bug of the search when searching over the turn of the year 2020-06-23 12:35:49 +02:00
Patrick Gebhardt
dc6da042f9 Add total costs to result interface 2020-06-23 11:43:39 +02:00
Patrick Gebhardt
88ce05c848 Add rain days chart 2020-06-23 11:43:05 +02:00
Patrick Gebhardt
64a8fd6011 Fix graph background color 2020-06-23 11:31:13 +02:00
Timo John
940d5426e8 Merge branch 'feature/flights' into 'develop'
Implemented Endpoints for usage of google place api

See merge request tjohn/cc-data!19
2020-06-23 02:03:40 +02:00
Timo John
18463c3ca2 Merge branch 'develop' of https://git.it.hs-heilbronn.de/tjohn/cc-data into feature/flights 2020-06-23 02:03:16 +02:00
Timo John
bf0a71c63c Implemented Endpoints for usage of google place api 2020-06-23 02:01:13 +02:00
Patrick Gebhardt
4c3cc012aa Fix toggled slider visuals 2020-06-23 00:02:40 +02:00
Patrick Gebhardt
f68a2599e9 Add advanced search 2020-06-22 23:57:02 +02:00
Patrick Gebhardt
ad180b00cc Change simple search to stepper 2020-06-22 23:57:01 +02:00
Patrick Gebhardt
d82ce649eb Fix search result details 2020-06-22 19:29:17 +02:00
Patrick Gebhardt
49d207473b Add price derivation graph 2020-06-22 17:00:54 +02:00
Timo Volkmann
47c664894c Merge branch 'bugfix/major-searcherror' into 'develop'
fixed 400 search

See merge request tjohn/cc-data!18
2020-06-22 15:42:54 +02:00
c42a55af12 fixed 400 search 2020-06-22 15:41:22 +02:00
Patrick Gebhardt
a5f102f99d Add flight search button 2020-06-22 12:15:34 +02:00
Patrick Gebhardt
71bbb1a3f1 Fix share button
Aborted navigator share opened the share dialog
2020-06-21 16:29:21 +02:00
Patrick Gebhardt
ecd6f93dcb Fix information on team page 2020-06-21 16:21:54 +02:00
Patrick Gebhardt
9ce8bd49ed Fix scrolling on details page 2020-06-21 16:04:41 +02:00
Patrick Gebhardt
a2a9a3a373 Improve null data visuals 2020-06-21 15:28:53 +02:00
Patrick Gebhardt
e17bbbfe3f Remove mock data 2020-06-21 15:21:47 +02:00
Patrick Gebhardt
d4b543caff Fix translations 2020-06-21 13:00:02 +02:00
Patrick Gebhardt
8c495d5c20 Add min temperatures to chart 2020-06-21 12:59:43 +02:00
Patrick Gebhardt
cc6e6e3146 Update team page 2020-06-20 15:26:43 +02:00
Patrick Gebhardt
458a9d0013 Fix scrolling on safari 2020-06-20 15:22:58 +02:00
Patrick Gebhardt
9409cbb981 Add bundle-frontend.sh 2020-06-20 13:04:06 +02:00
Patrick Gebhardt
8e0b21fb1c Add fixed sidenav for desktop 2020-06-20 12:42:41 +02:00
Patrick Gebhardt
0109f50cc9 Update translations 2020-06-20 12:28:30 +02:00
Patrick Gebhardt
1ba9c8c032 Add hovering cursor over region cards 2020-06-20 12:25:46 +02:00
Patrick Gebhardt
bf596e7d97 Add share button tooltip 2020-06-20 12:22:31 +02:00
Patrick Gebhardt
112c37f7b5 Add team site 2020-06-20 00:16:42 +02:00
Patrick Gebhardt
51d5283917 Add share button 2020-06-19 22:38:43 +02:00
Patrick Gebhardt
de443bb5de Update interfaces 2020-06-19 17:50:38 +02:00
Patrick Gebhardt
9c3f70d36b Add bookmarks 2020-06-19 17:50:38 +02:00
Patrick Gebhardt
587e231e05 Add search input date check 2020-06-19 17:50:37 +02:00
Patrick Gebhardt
615727e09d Add disabled search button if missing values 2020-06-19 17:50:37 +02:00
Patrick Gebhardt
cf611ce8c4 Add toggle group deselection 2020-06-19 17:50:37 +02:00
Patrick Gebhardt
cc07e11f8f Add persistent search input 2020-06-19 17:50:37 +02:00
c602e75f33 quickfix for inverted logic 2020-06-19 16:43:36 +02:00
Timo Volkmann
e54232842b Merge branch 'bugfix/queries' into 'develop'
Bugfix/queries and randomize regions

See merge request tjohn/cc-data!17
2020-06-19 16:16:48 +02:00
7f68ff57f7 implemented randomize regions 2020-06-19 16:13:56 +02:00
a9c6873137 score and search handler cleanup 2020-06-19 16:13:56 +02:00
5ee6cc7a82 added some comments 2020-06-19 16:13:55 +02:00
d92c164127 implemented null score filter 2020-06-19 16:13:55 +02:00
Timo John
76d9036a61 If element has null score, result is not delivered 2020-06-19 16:10:17 +02:00
1cb0a68bb6 fixed bugs 2020-06-19 16:09:12 +02:00
Patrick Gebhardt
afb4234ce4 Add details to search results 2020-06-19 13:35:10 +02:00
Patrick Gebhardt
c8c0b7f900 Hotfix search request 2020-06-18 17:23:08 +02:00
Timo John
96c7a5575a Merge branch 'feature/trivago' into 'develop'
Feature/trivago

See merge request tjohn/cc-data!14
2020-06-18 16:46:19 +02:00
Timo John
bb14c9955d Merge branch 'develop' of https://git.it.hs-heilbronn.de/tjohn/cc-data into feature/trivago 2020-06-18 16:45:47 +02:00
Patrick Gebhardt
cbbf2b53d8 Merge branch 'feature/region-details' into 'develop'
Add region props to region details

See merge request tjohn/cc-data!16
2020-06-18 16:20:27 +02:00
Timo John
2d874337ce getRegionsById now returns just an object 2020-06-18 16:17:22 +02:00
Timo John
b954e356a0 Search handling for null-Subscors 2020-06-18 16:07:57 +02:00
Patrick Gebhardt
1803ed2225 Add region props to region details 2020-06-18 16:00:10 +02:00
Timo John
961760601d Small fix according to database col name change 2020-06-18 15:55:17 +02:00
Timo Volkmann
6f13cb6c87 Merge branch 'feature/dockerfiles' into 'develop'
Dockerfile backend and text search filter

See merge request tjohn/cc-data!15
2020-06-18 15:21:32 +02:00
7e7dea46eb Dockerfile backend and text search filter 2020-06-18 15:19:45 +02:00
Timo John
724ebfe35c Removed getRegions.js for internal usage 2020-06-18 14:53:37 +02:00
Timo John
64abbe4e30 Trivago data added to regions endpoint 2020-06-18 14:44:39 +02:00
Patrick Gebhardt
61aac61469 Merge branch 'feature/region-details' into 'develop'
Add details page

See merge request tjohn/cc-data!12
2020-06-18 13:26:53 +02:00
Patrick Gebhardt
c99033e367 Add graphs to region details 2020-06-18 13:26:32 +02:00
Patrick Gebhardt
299e80e5a2 Add basic details page 2020-06-18 13:26:31 +02:00
Timo John
9b3d6d4800 Add new setup.sql 2020-06-18 12:18:38 +02:00
Timo John
40b5e6f6ee Merge branch 'develop' of https://git.it.hs-heilbronn.de/tjohn/cc-data into develop 2020-06-18 12:14:01 +02:00
Timo John
74efce18f7 Add cors middleware 2020-06-18 12:13:10 +02:00
Timo John
af964be2d6 Merge branch 'refactor/database' into 'develop'
Changed accordingly to database col namechanges

See merge request tjohn/cc-data!13
2020-06-18 12:08:15 +02:00
Timo John
aba72921ea Changed accordingly to database col namechanges 2020-06-18 12:06:55 +02:00
Timo John
ef30abd8b5 Merge branch 'feature/image' into 'develop'
Add Images for all regions

See merge request tjohn/cc-data!11
2020-06-18 01:04:08 +02:00
Timo John
cfc17cb23a Add Images for all regions 2020-06-18 01:02:20 +02:00
Patrick Gebhardt
e706a2633e Fix preset loading 2020-06-18 00:52:27 +02:00
Patrick Gebhardt
25f653008a Implement basic ui concept 2020-06-18 00:10:50 +02:00
410b363713 route for standard image if image not found 2020-06-18 00:09:37 +02:00
1fd35504fd changed search response structure 2020-06-17 23:58:23 +02:00
Lucas Hinderberger
3e1d376a93 Update Dockerfile 2020-06-17 22:20:25 +02:00
Lucas Hinderberger
256c8ab4e0 Update Dockerfile 2020-06-17 22:16:46 +02:00
7e919026e7 fixed empty climate arrays in regions 2020-06-17 21:50:32 +02:00
Timo John
e88952388b Fixed Region endpoint and cleared up a little. 2020-06-17 21:50:32 +02:00
337c1e49cf implemented scoring for prices 2020-06-17 21:50:31 +02:00
0d82057383 fixed wrong date interpretation 2020-06-17 21:50:31 +02:00
0586a7a0ba some fixes by TVM 2020-06-17 21:50:31 +02:00
33249bf817 refactored search function befor implementing price search 2020-06-17 21:50:30 +02:00
46a57891a0 satisfiy region interface 2020-06-17 21:50:30 +02:00
Timo John
ee63caf1ea Changed return names according to Interfacedefinition of frontend 2020-06-17 21:50:29 +02:00
809951fd85 preparation for price search 2020-06-17 21:50:29 +02:00
Timo John
8b0064a5b9 Matched getRegions to API Specification
Matched getRegionsByID to API Specification
2020-06-17 21:50:28 +02:00
Timo John
ea2c87a557 New Version of Setup.sql 2020-06-17 21:50:28 +02:00
Timo John
78a6480871 New Version of Setup.sql 2020-06-17 21:50:28 +02:00
Timo John
7173e0e48e Changed Parsing from int to float
Removed comment
2020-06-17 21:50:27 +02:00
0a7e901e1d finished refactoring 2020-06-17 21:50:27 +02:00
e61aa03996 added sample images 2020-06-17 21:50:27 +02:00
Timo John
ad078bd360 Implemented Endpoint for getting all search presets
Endpoint for presets works now. For real.

Alternative JSON return for presets tested
2020-06-17 21:50:22 +02:00
Timo John
359e93ed4d Added Endpoints for Countries and Region/Country by ID 2020-06-17 21:50:22 +02:00
Timo John
a8d84a4b36 Same for countries 2020-06-17 21:50:21 +02:00
Timo John
61796b1b50 Add description and preview_img to setup.sql 2020-06-17 21:50:20 +02:00
Timo John
b0f5defe1c Search function works as before minus old syntax 2020-06-17 21:50:20 +02:00
Timo John
692336a0ba Split up massive app.js in small files 2020-06-17 21:50:20 +02:00
Timo John
5b7c0e5bb3 Moved files to /util 2020-06-17 21:50:20 +02:00
Timo John
1ce2b94ee3 Refactored climate endpoint 2020-06-17 21:50:20 +02:00
Timo John
063f1e1993 Stage 1: Restructuring the Express Backend 2020-06-17 21:50:20 +02:00
Timo John
2c51e6c5f9 Changed Temp from mean to mean_max
Changed parameters to temperature_mean_max

set database formatting to utf8

add .env to .gitignore

added .env.sample

.gitignore fix
2020-06-17 21:50:20 +02:00
Patrick Gebhardt
b9c53992f0 Merge branch 'feature/sort-presets' into 'develop'
Feature/sort presets

See merge request tjohn/cc-data!10
2020-06-17 20:53:26 +02:00
Patrick Gebhardt
d5ed1f9350 Add preset sorting 2020-06-17 20:51:30 +02:00
Patrick Gebhardt
f21db51d5a Change preset translations 2020-06-17 20:51:06 +02:00
Patrick Gebhardt
632bc801fb Merge branch 'feature/preset-driven-search' into 'develop'
Add basic search ui

See merge request tjohn/cc-data!9
2020-06-17 19:26:07 +02:00
Patrick Gebhardt
dc7a1d63dc Add basic search ui 2020-06-17 19:13:16 +02:00
Patrick Gebhardt
fd325fa005 Update interfaces to latest version 2020-06-16 18:19:40 +02:00
Patrick Gebhardt
d1be826866 Add basic translation service 2020-06-16 18:19:40 +02:00
Patrick Gebhardt
d424479d2f Add basic region values to result page 2020-06-16 18:19:40 +02:00
Patrick Gebhardt
56818c4bff Add basic result page 2020-06-16 18:19:40 +02:00
Patrick Gebhardt
a9eab54674 Merge branch 'feature/support_ci' into 'develop'
Adds needed docker-support to enable automatic deployment via teamcity

See merge request tjohn/cc-data!7
2020-06-16 18:16:41 +02:00
Lucas Hinderberger
157090d382 Adds needed docker-support to enable automatic deployment via teamcity 2020-06-16 15:03:57 +02:00
Patrick Gebhardt
65219907f8 Merge branch 'feature/interfaces' into 'develop'
Feature/interfaces

See merge request tjohn/cc-data!2
2020-06-14 20:57:15 +02:00
Patrick Gebhardt
db5c7fbbe7 Add base64 encoding for search request 2020-06-14 20:57:09 +02:00
Patrick Gebhardt
53cd7c6245 Add basic interface and data service 2020-06-14 20:57:09 +02:00
Timo Volkmann
316cb4f663 Merge branch 'backend' into 'develop'
Backend

See merge request tjohn/cc-data!5
2020-06-14 20:09:37 +02:00
e670b40d1c Merge branch 'backend' of https://git.it.hs-heilbronn.de/tjohn/cc-data into backend 2020-06-14 20:01:54 +02:00
eb5491b64b changed query syntax 2020-06-14 19:58:34 +02:00
4f4d51b514 little cleanup 2020-06-14 19:58:34 +02:00
b249665a9e balancing 2020-06-14 19:58:34 +02:00
06236e881a First working search (climate params, randomizing) 2020-06-14 19:58:34 +02:00
7bb07af76a first score search draft working 2020-06-14 19:58:34 +02:00
e1a788c56b finished climate importer 2020-06-14 19:58:34 +02:00
92b9fe3e9d first draft for database population (climate) 2020-06-14 19:58:34 +02:00
67ec5a132c climate api implementation 1 2020-06-14 19:58:34 +02:00
fccd897f79 add sql connection, config through ENV 2020-06-14 19:58:34 +02:00
b23a7f6858 initial backend, first score calc draft 2020-06-14 19:58:34 +02:00
cdae8c6e45 changed query syntax 2020-06-14 19:28:06 +02:00
Lucas Hinderberger
a345e17d5e Merge branch 'fix/remove-ide-files' into 'develop'
Remove IDE files again

See merge request tjohn/cc-data!4
2020-06-14 15:31:05 +02:00
Patrick Gebhardt
79d2e1af0b Remove IDE files again 2020-06-14 15:29:35 +02:00
bbf483e445 little cleanup 2020-06-14 13:33:07 +02:00
2e4ec7b24e balancing 2020-06-14 01:32:21 +02:00
b5c5abdd4f First working search (climate params, randomizing) 2020-06-14 01:14:04 +02:00
e566344098 first score search draft working 2020-06-12 18:43:09 +02:00
84b9a8abc9 finished climate importer 2020-06-12 11:30:40 +02:00
75bb43a3bd first draft for database population (climate) 2020-06-12 10:48:59 +02:00
1f17d76151 climate api implementation 1 2020-06-12 08:32:01 +02:00
1cdc7f1bc3 add sql connection, config through ENV 2020-06-10 09:20:15 +02:00
258a9047fe Merge branch 'develop' into backend 2020-06-10 08:08:48 +02:00
48f6fef82e initial backend, first score calc draft 2020-06-10 08:07:15 +02:00
Timo John
4be87d473d Merge branch 'scripts' into 'develop'
Scripts

See merge request tjohn/cc-data!1
2020-06-09 23:15:11 +02:00
Timo John
68b76d914b Scripts 2020-06-09 23:15:11 +02:00
203 changed files with 15654 additions and 191 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
npm-debug.log

6
.gitignore vendored
View File

@ -1,3 +1,7 @@
# credentials
.env
config.json
# compiled output # compiled output
/dist /dist
@ -21,3 +25,5 @@
# System Files # System Files
.DS_Store .DS_Store
Thumbs.db Thumbs.db
node_modules

2
.idea/.gitignore generated vendored
View File

@ -1,2 +0,0 @@
# Default ignored files
/workspace.xml

12
.idea/cc-data.iml generated
View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/frontend/dist" />
<excludeFolder url="file://$MODULE_DIR$/frontend/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA">
<option name="configuration">
<map>
<entry key="checkstyle-version" value="8.30" />
<entry key="copy-libs" value="true" />
<entry key="location-0" value="BUNDLED:(bundled):Sun Checks" />
<entry key="location-1" value="BUNDLED:(bundled):Google Checks" />
<entry key="scan-before-checkin" value="false" />
<entry key="scanscope" value="JavaOnly" />
<entry key="suppress-errors" value="false" />
</map>
</option>
</component>
</project>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
.idea/misc.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/cc-data.iml" filepath="$PROJECT_DIR$/.idea/cc-data.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

23
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,23 @@
{
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"name": "NodeJS: nodemon debug",
"program": "${workspaceFolder}/backend/index.js",
"request": "launch",
// "restart": true,
"runtimeExecutable": "node",
// "runtimeExecutable": "nodemon",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"envFile": "${workspaceFolder}/backend/.env",
}
]
}

24
Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM node:12
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY backend/package*.json ./
RUN npm install
# Bundle app source
COPY backend .
EXPOSE 3000
ENV PORT=3000
ENV METEOSTAT_API_KEY=LMlDskju
ENV DB_HOST=lhinderberger.dev
ENV DB_USER=root
ENV DB_PASSWORD=devtest
ENV DB_PORT=3306
CMD [ "node", "index.js" ]

Binary file not shown.

View File

@ -2,9 +2,45 @@
Campus Cup AKMC Data Traveloptimizer Campus Cup AKMC Data Traveloptimizer
## Installation ## Backend
### Requirements
- MariaDB or MySQL
- node `10.12` or higher
- Configure database in `.env`-file or environment variables. See `.env` for reference
- Set API-Key for meteostat.net in `.env`-file or environment variable
- import `setup.sql` for sample data
### Start
- Run `$(cd backend && npm run start)`
- call http://localhost:3000/v1/update/climate to fetch climate data for sample entries.
### Search
Customize your search with query parameters. For now, only climate parameters are supported. If you omit climate queries, all climate parameters will be randomized.
Following queries are supperted by now:
- from=YYYY-MM-DD _(required)_
- to=YYYY-MM-DD _(required)_
- temperature=NUMBER,NUMBER
- raindays=NUMBER,NUMBER
- sunhours=NUMBER,NUMBER
- precipitation=NUMBER,NUMBER
__Examples:__
http://localhost:3000/v1/search?from=2020-06-14&to=2020-07-29&temperature=27,29&raindays=8,12&sunhours=250,300
http://localhost:3000/v1/search?from=2020-06-14&to=2020-07-29
### More
To get more search results, add more entries with meteostat station IDs to the `regions` table in the database
## Frontend
### Installation
- Install node 10.15.3 - Install node 10.15.3
- Run "(cd frontend && npm i)" - Run "(cd frontend && npm i)"
# Start dev server ### Start dev server
- Run "(cd frontend && npm run start)" - Run "(cd frontend && npm run start)"

7070
Scripts/setup.sql Normal file

File diff suppressed because it is too large Load Diff

BIN
Travopti_Docs.pdf Normal file

Binary file not shown.

10
backend/.env.sample Normal file
View File

@ -0,0 +1,10 @@
PORT=3000
METEOSTAT_API_KEY=LMlDskju
METEOSTAT_API_KEY_V2=O9X1xxKjheNwF1vfLcdRMmQ9JlobOugL
DB_HOST=lhinderberger.dev
DB_USER=root
DB_PASSWORD=devtest
DB_PORT=3306
DATABASE=travopti
GOOGLE_CLOUD_APIS=AIzaSyCeMBLfpqTp0IVB7Xipx6ekRQFUBjPacQc
SHOW_MATCH_VALUE=1

27
backend/Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM node:12
# Create app directory
WORKDIR /usr/src/app
# COPY package.json .
# For npm@5 or later, copy package-lock.json as well
COPY package.json package-lock.json ./
# Install app dependencies
RUN npm install
# Bundle app source
COPY . .
ENV PORT=3000
ENV METEOSTAT_API_KEY=LMlDskju
ENV DB_HOST=127.0.0.1
ENV DB_USER=root
ENV DB_PASSWORD=devtest
ENV DB_PORT=3306
ENV DATABASE=travopti
EXPOSE 3000
# Start Node server
CMD [ "node", "./index.js" ]

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

94
backend/index.js Normal file
View File

@ -0,0 +1,94 @@
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const morgan = require("morgan");
const dbConnection = require("./util/dbConnection");
const fs = require("fs");
const cors = require("cors");
require('dotenv').config()
// credentials
const port = process.env.PORT
// Router
const search = require("./routes/search");
const regions = require("./routes/regions");
const countries = require("./routes/countries");
const places = require("./routes/place");
const update = require("./routes/update");
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 () => {
try {
// Connect to MariaDB
const dbConn = await dbConnection();
// Express middleware
app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "../../dist")));
app.use(bodyParser.json());
app.use(cors());
app.use('/api/v1/doc', swaggerUi.serve, swaggerUi.setup(swaggerDocs, {explorer: false, docExpansion: "list"}));
// Express routes
app.use(search(dbConn));
app.use(regions(dbConn));
app.use(countries(dbConn));
app.use(places(dbConn));
app.use(update(dbConn))
app.use((err, req, res, next) => {
// 500
if (true) {
next();
} else {
res.status(500).send();
}
});
// Start webserver
app.listen(port, () => {
console.log(`Travopti backend listening at http://localhost:${port}`)
});
} catch (error) {
console.error("Failed to start the webserver");
console.error(error);
}
})();

View File

@ -0,0 +1,10 @@
module.exports = async (dbConn) => {
const countries = await dbConn.query(
`SELECT countries.id AS country_id,
countries.country AS name,
countries.description
FROM countries`
);
return countries;
};

View File

@ -0,0 +1,12 @@
module.exports = async (dbConn, id) => {
const country = await dbConn.query(
`SELECT countries.id AS country_id,
countries.country AS name,
countries.description
FROM countries
WHERE countries.id = ?`,
[id]
);
return country;
};

View File

@ -0,0 +1,19 @@
const axios = require("axios")
const getPlacePhoto = require("./getPlacePhoto.js")
const fields = "photos,place_id,name,rating,geometry" // Parameters for Google Place API
module.exports = async (q) => {
const res = await axios.get(
`https://maps.googleapis.com/maps/api/place/findplacefromtext/json?inputtype=textquery&fields=${fields}&input=${q}&key=${process.env.GOOGLE_CLOUD_APIS}`)
// Photo url is not returned by default 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
}

View File

@ -0,0 +1,20 @@
const axios = require("axios")
const getPlacePhoto = require("./getPlacePhoto.js")
const radius = 20000 // Search radius in meters
const rankby = "prominence" // Sorting of results
const types = "tourist_attraction" // Category which shall be searched
module.exports = async (lat, lng) => {
const res = await axios.get(
`https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&radius=${radius}&type=${types}&rankby=${rankby}&key=${process.env.GOOGLE_CLOUD_APIS}`
);
// Photo url is not returned by default 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
}

View 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
}

View File

@ -0,0 +1,61 @@
const arrayFormatting = require("../util/databaseArrayFormatting.js")
module.exports = async (dbConn, id) => {
const res = await dbConn.query(
`SELECT regions.id AS region_id,
regions.region AS name,
countries.country AS country,
regions.description AS description,
regions.lon AS lon,
regions.lat AS lat,
rcma.temperature_mean AS temperature_mean,
rcma.temperature_mean_min AS temperature_mean_min,
rcma.temperature_mean_max AS temperature_mean_max,
rcma.precipitation AS precipitation,
rcma.rain_days AS rain_days,
rcma.sun_hours AS sun_hours,
rcma.humidity AS humidity,
regions_byt.average_per_day_costs AS average_per_day_costs,
rtma.avg_price_relative AS avg_price_relative,
regions_byt.accommodation_costs AS accommodation_costs,
regions_byt.food_costs AS food_costs,
regions_byt.water_costs AS water_costs,
regions_byt.local_transportation_costs AS local_transportation_costs,
regions_byt.entertainment_costs AS entertainment_costs,
regions_byt.alcohol_costs AS alcohol_costs
FROM regions
LEFT JOIN countries ON regions.country_id = countries.id
LEFT JOIN (SELECT rcma.region_id,
GROUP_CONCAT(rcma.temperature_mean ORDER BY rcma.month SEPARATOR ', ') AS temperature_mean,
GROUP_CONCAT(rcma.temperature_mean_min ORDER BY rcma.month SEPARATOR ', ') AS temperature_mean_min,
GROUP_CONCAT(rcma.temperature_mean_max ORDER BY rcma.month SEPARATOR ', ') AS temperature_mean_max,
GROUP_CONCAT(rcma.precipitation ORDER BY rcma.month SEPARATOR ', ') AS precipitation,
GROUP_CONCAT(rcma.rain_days ORDER BY rcma.month SEPARATOR ', ') AS rain_days,
GROUP_CONCAT(rcma.sun_hours ORDER BY rcma.month SEPARATOR ', ') AS sun_hours,
GROUP_CONCAT(rcma.humidity ORDER BY rcma.month SEPARATOR ', ') AS humidity
FROM region_climate_monthly_avg AS rcma
GROUP BY rcma.region_id) rcma ON rcma.region_id = regions.id
LEFT JOIN regions_byt ON regions.id = regions_byt.region_id
LEFT JOIN (SELECT rtma.region_id,
GROUP_CONCAT(rtma.avg_price_relative ORDER BY rtma.month SEPARATOR ', ') AS avg_price_relative
FROM regions_trivago_monthly_avg AS rtma
GROUP BY rtma.region_id) rtma
ON regions.id = rtma.region_id
WHERE regions_byt.travelstyle = 1
AND regions.id = ?`,
[id]
);
const region = res[0]
region.avg_price_relative = arrayFormatting(region.avg_price_relative);
region.temperature_mean = arrayFormatting(region.temperature_mean);
region.temperature_mean_min = arrayFormatting(region.temperature_mean_min);
region.temperature_mean_max = arrayFormatting(region.temperature_mean_max);
region.precipitation = arrayFormatting(region.precipitation);
region.rain_days = arrayFormatting(region.rain_days);
region.sun_hours = arrayFormatting(region.sun_hours);
region.humidity = arrayFormatting(region.humidity);
return region;
};

View File

@ -0,0 +1,21 @@
const arrayFormatting = require("../util/databaseArrayFormatting.js")
module.exports = async (dbConn, id) => {
const region_nearby = await dbConn.query(
`SELECT id as place_id,
region_id as region_id,
name as place_name,
lon as lon,
lat as lat,
rating as rating,
vicinity as vicinity,
photo_reference as photo_reference,
img_url as img_url
FROM regions_nearby
WHERE region_id = ?`,
[id]
);
return region_nearby;
};

View File

@ -0,0 +1,69 @@
const arrayFormatting = require("../util/databaseArrayFormatting.js");
const { allTagsWithValues } = require("./getTags.js");
module.exports = async (dbConn) => {
const sqlRegions = `SELECT regions.id AS region_id,
regions.region AS name,
countries.country AS country,
regions.description AS description,
regions.lon AS lon,
regions.lat AS lat,
rcma.temperature_mean AS temperature_mean,
rcma.temperature_mean_min AS temperature_mean_min,
rcma.temperature_mean_max AS temperature_mean_max,
rcma.precipitation AS precipitation,
rcma.rain_days AS rain_days,
rcma.sun_hours AS sun_hours,
rcma.humidity AS humidity,
regions_byt.average_per_day_costs AS average_per_day_costs,
rtma.avg_price_relative AS avg_price_relative,
regions_byt.accommodation_costs AS accommodation_costs,
regions_byt.food_costs AS food_costs,
regions_byt.water_costs AS water_costs,
regions_byt.local_transportation_costs AS local_transportation_costs,
regions_byt.entertainment_costs AS entertainment_costs,
regions_byt.alcohol_costs AS alcohol_costs
FROM regions
LEFT JOIN countries ON regions.country_id = countries.id
LEFT JOIN (SELECT rcma.region_id,
GROUP_CONCAT(rcma.temperature_mean ORDER BY rcma.month SEPARATOR ', ') AS temperature_mean,
GROUP_CONCAT(rcma.temperature_mean_min ORDER BY rcma.month SEPARATOR ', ') AS temperature_mean_min,
GROUP_CONCAT(rcma.temperature_mean_max ORDER BY rcma.month SEPARATOR ', ') AS temperature_mean_max,
GROUP_CONCAT(rcma.precipitation ORDER BY rcma.month SEPARATOR ', ') AS precipitation,
GROUP_CONCAT(rcma.rain_days ORDER BY rcma.month SEPARATOR ', ') AS rain_days,
GROUP_CONCAT(rcma.sun_hours ORDER BY rcma.month SEPARATOR ', ') AS sun_hours,
GROUP_CONCAT(rcma.humidity ORDER BY rcma.month SEPARATOR ', ') AS humidity
FROM region_climate_monthly_avg AS rcma
GROUP BY rcma.region_id) rcma ON rcma.region_id = regions.id
LEFT JOIN regions_byt ON regions.id = regions_byt.region_id
LEFT JOIN (SELECT rtma.region_id,
GROUP_CONCAT(rtma.avg_price_relative ORDER BY rtma.month SEPARATOR ', ') AS avg_price_relative
FROM regions_trivago_monthly_avg AS rtma
GROUP BY rtma.region_id) rtma
ON regions.id = rtma.region_id
WHERE regions_byt.travelstyle = 1`
const [regions, tags] = await Promise.all([dbConn.query(sqlRegions), allTagsWithValues(dbConn)])
for (k = 0; k < regions.length; k++) {
regions[k].avg_price_relative = arrayFormatting(regions[k].avg_price_relative);
regions[k].temperature_mean = arrayFormatting(regions[k].temperature_mean);
regions[k].temperature_mean_min = arrayFormatting(regions[k].temperature_mean_min);
regions[k].temperature_mean_max = arrayFormatting(regions[k].temperature_mean_max);
regions[k].precipitation = arrayFormatting(regions[k].precipitation);
regions[k].rain_days = arrayFormatting(regions[k].rain_days);
regions[k].sun_hours = arrayFormatting(regions[k].sun_hours);
regions[k].humidity = arrayFormatting(regions[k].humidity);
}
return regions.map(region => {
region.tags = tags.filter(tag => tag.region_id === region.region_id).map(tag => {
delete tag.region_id
return tag
})
return region
});
};

View File

@ -0,0 +1,21 @@
module.exports = async (dbConn) => {
let presets = await dbConn.query(
`SELECT search_presets.id AS preset_id,
search_presets.parameter AS parameter,
search_presets.name AS tag_label,
CASE
WHEN value_2 is NULL THEN value_1
ELSE CONCAT(search_presets.value_1,"|",search_presets.value_2)
END AS "value"
FROM search_presets`
);
for (k = 0; k < presets.length; k++) {
const value = presets[k].value
presets[k].value = value.split("|");
for (i = 0; i < presets[k].value.length; i++) {
presets[k].value[i] = parseFloat(presets[k].value[i])
}
}
return presets;
};

22
backend/models/getTags.js Normal file
View File

@ -0,0 +1,22 @@
exports.getUniqueTags = async (dbConn) => {
let tags = await dbConn.query(
`SELECT DISTINCT name FROM region_feedback;`
);
return tags;
};
exports.allTagsWithValues = async (dbConn) => {
let tags = await dbConn.query(
`SELECT region_id, name, value FROM region_feedback;`
);
return tags;
};
exports.getTagsByRegionId = async (dbConn, id) => {
let tags = await dbConn.query(
`SELECT region_id, name, value
FROM region_feedback
WHERE region_id = ${id};`
);
return tags;
};

View File

@ -0,0 +1,89 @@
const axios = require('axios')
const _ = require('lodash')
// 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 rangeEndDate = '2020-05'// If no date is given, this date will be used as endDate
// TODO: call method periodically, not over API (fine for prototyping, tho)
module.exports = async (dbConn, startDate = rangeStartDate, endDate = rangeEndDate) => {
console.log('update climate with:', startDate, endDate);
const result = await dbConn.query(`SELECT * FROM regions WHERE meteostat_id IS NOT NULL`)
const climateObject = await Promise.all(result.map(src => {
return createClimateObjectFrom(src, startDate, endDate)
}))
const climateObjectArr = climateObject.reduce((total, element) => total.concat(element), [])
await writeToDatabase(dbConn, climateObjectArr)
const res = `region_climate update complete. see backend logs for info.`
return res
}
async function createClimateObjectFrom(src, startDate, endDate) {
let res
if (src.meteostat_id === '') {
console.log("skipping: no meteostat id ")
return []
}
try {
res = await axios.get(`https://api.meteostat.net/v1/history/monthly?station=${src.meteostat_id}&start=${startDate}&end=${endDate}&key=${process.env.METEOSTAT_API_KEY}`)
} catch (error) {
console.log("error while getting data from meteostat: couldn't find results for following region: ")
console.log(src.region + " with meteostat_id " + src.meteostat_id)
console.log(error)
return []
}
if (!res.data.data) {
console.log("skipping: no data for station with meteostat_id " + src.meteostat_id + " (" + src.region + ")")
return []
}
const retVal = res.data.data.map(element => {
let result = {
region: src.region,
region_id: src.id,
year: element.month.split("-")[0],
month: element.month.split("-")[1],
temperature_mean: element.temperature_mean,
temperature_mean_min: element.temperature_mean_min,
temperature_mean_max: element.temperature_mean_max,
precipitation: element.precipitation,
rain_days: element.raindays,
sun_hours: element.sunshine,
humidity: element.humidity ? element.humidity : null
}
return result
})
return retVal
}
async function writeToDatabase(dbConn, climateObjArr) {
for (const element of climateObjArr) {
try {
await dbConn.query(`
INSERT INTO region_climate
(region_id, year, month, temperature_mean, temperature_mean_min, temperature_mean_max, precipitation, sun_hours, humidity, rain_days)
VALUES (${element.region_id}, ${element.year}, ${element.month}, ${element.temperature_mean}, ${element.temperature_mean_min}, ${element.temperature_mean_max}, ${element.precipitation}, ${element.sun_hours}, ${element.humidity}, ${element.rain_days})
ON DUPLICATE KEY UPDATE
temperature_mean = ${element.temperature_mean},
temperature_mean_min = ${element.temperature_mean_min},
temperature_mean_max = ${element.temperature_mean_max},
precipitation = ${element.precipitation},
sun_hours = ${element.sun_hours},
humidity = ${element.humidity},
rain_days = ${element.rain_days};`)
} catch (error) {
if (error.code !== 'ER_DUP_ENTRY') {
console.log("element which causes problems: ")
console.log(element)
console.log("query which causes problems: ")
console.log(error)
} else {
console.log(element.region + ": " + error.sqlMessage)
}
}
}
};

View File

@ -0,0 +1,110 @@
const axios = require('axios')
const _ = require('lodash')
// 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 rangeEndDate = '2019-12-31'// If no date is given, this date will be used as endDate
// TODO: call method periodically, not over API (fine for prototyping, tho)
module.exports = async (dbConn, startDate = rangeStartDate, endDate = rangeEndDate) => {
console.log('update climate with:', startDate, endDate);
const result = await dbConn.query(`SELECT id, region, lon, lat FROM regions`)
const climateObject = await Promise.all(result.map(src => {
return createClimateObjectFrom(src, startDate, endDate)
}))
const climateObjectArr = climateObject.reduce((total, element) => total.concat(element), [])
await writeToDatabase(dbConn, climateObjectArr)
const res = `region_climate update v2 complete. see backend logs for info.`
return climateObjectArr
}
async function createClimateObjectFrom(src, startDate, endDate) {
let res
try {
res = await axios.get(
`https://api.meteostat.net/v2/point/daily?lat=${src.lat}&lon=${src.lon}&start=${startDate}&end=${endDate}`,
{
headers: {
"x-api-key": process.env.METEOSTAT_API_KEY_V2
}//,
//httpsAgent: agent
})
} catch (error) {
console.log("error while getting data from meteostat: couldn't find results for following region: ")
console.log(src.region,"with coords:",src.lon,src.lat)
console.log(error)
return []
}
if (!res.data.data) {
console.log("skipping: no data for station with meteostat_id " + src.meteostat_id + " (" + src.region + ")")
return []
}
const retVal = res.data.data.map(element => {
let result = {
region: src.region,
region_id: src.id,
year: element.date.split("-")[0],
month: element.date.split("-")[1],
day: element.date.split("-")[2],
temperature_mean: element.tavg,
temperature_mean_min: element.tmin,
temperature_mean_max: element.tmax,
precipitation: element.prcp,
rain_days: element.prcp > 2 ? 1:0, // More than 2mm => rainday
sun_hours: element.tsun/60
}
//console.log(result)
return result
})
return retVal
}
async function writeToDatabase(dbConn, climateObjArr) {
for (const element of climateObjArr) {
//console.log(element)
try {
await dbConn.query(`
INSERT INTO region_climate_day
(region_id, year, month, day, temperature_mean, temperature_mean_min, temperature_mean_max, precipitation, sun_hours, rain_days)
VALUES (${element.region_id}, ${element.year}, ${element.month}, ${element.day}, ${element.temperature_mean}, ${element.temperature_mean_min}, ${element.temperature_mean_max}, ${element.precipitation}, ${element.sun_hours}, ${element.rain_days})
ON DUPLICATE KEY UPDATE
temperature_mean = ${element.temperature_mean},
temperature_mean_min = ${element.temperature_mean_min},
temperature_mean_max = ${element.temperature_mean_max},
precipitation = ${element.precipitation},
sun_hours = ${element.sun_hours},
rain_days = ${element.rain_days};`)
} catch (error) {
if (error.code !== 'ER_DUP_ENTRY') {
console.log("element which causes problems: ")
console.log(element)
console.log("query which causes problems: ")
console.log(error)
} else {
console.log(element.region + ": " + error.sqlMessage)
}
}
}
};
/*
INSERT INTO region_climate
(region_id, YEAR, MONTH, temperature_mean, temperature_mean_min, temperature_mean_max, precipitation, rain_days, sun_hours)
SELECT
region_id,
YEAR,
MONTH,
ROUND(AVG(temperature_mean),2) AS temperature_mean,
MIN(temperature_mean_min) AS temperature_mean_min,
MAX(temperature_mean_max) AS temperature_mean_max,
ROUND(SUM(precipitation),2) AS precipitation,
SUM(rain_days) AS rain_days,
SUM(sun_hours) AS sun_hours
FROM region_climate_day
GROUP BY region_id, YEAR, month
*/

View File

@ -0,0 +1,34 @@
const axios = require("axios")
const getRegions = require("../models/getRegions.js")
const fields = "geometry" // Parameters for Google Places API
module.exports = async (dbConn) => {
const regions = await getRegions(dbConn)
for (let region of regions) {
try {
const q = region.name
const place = await axios.get(
`https://maps.googleapis.com/maps/api/place/findplacefromtext/json?inputtype=textquery&fields=geometry&input=${q}&key=${process.env.GOOGLE_CLOUD_APIS}`)
const region_id = region.region_id
const lon = parseFloat(place.data.candidates[0].geometry.location.lng)
const lat = parseFloat(place.data.candidates[0].geometry.location.lat)
await dbConn.query(
`UPDATE regions
SET lon=${lon}, lat=${lat}
WHERE id = ${region_id}`
);
console.log("Updating coordinates for region: ", region_id, q, " ", "lon:", lon, "lat", lat)
} catch (e) {
console.log(e)
}
}
const res = "lon lat update finished"
return res
}

View File

@ -0,0 +1,48 @@
const axios = require("axios")
const getRegions = require("../models/getRegions.js")
const getPlaceNearby = require("../models/getPlaceNearby.js")
module.exports = async (dbConn) => {
const regions = await getRegions(dbConn)
for (let region of regions) {
try {
const region_id = region.region_id
const region_lon = region.lon
const region_lat = region.lat
console.log("Updating nearby for region: ", region_id, region.name)
const places = await getPlaceNearby(region_lat, region_lon)
for (let result of places.results) {
const name = result.name
const rating = result.rating === undefined ? null : result.rating
const lon = result.geometry.location.lng
const lat = result.geometry.location.lat
const photo_ref = result.photos[0].photo_reference
const vicinity = result.vicinity
console.log("# New tourist attraction:", region_id, region.name, name)
await dbConn.query(
`INSERT INTO regions_nearby
(region_id,name,lon,lat,rating,vicinity,photo_reference)
VALUES
(${region_id},"${name}",${lon},${lat},${rating},"${vicinity}","${photo_ref}")
ON DUPLICATE KEY UPDATE
lon = ${lon},
lat = ${lat},
rating = ${rating},
vicinity = "${vicinity}",
photo_reference = "${photo_ref}"`
);
}
} catch (e) {
console.log(e)
}
}
const res = "region nearby update finished"
return res
}

View File

@ -0,0 +1,51 @@
const axios = require("axios")
const getRegionById = require("../models/getRegionById.js")
const getPlaceNearby = require("../models/getPlaceNearby.js")
module.exports = async (dbConn, id) => {
const region = await getRegionById(dbConn, id)
try {
const region_id = region.region_id
const region_lon = region.lon
const region_lat = region.lat
console.log("Updating nearby for region: ", region_id, region.name)
const places = await getPlaceNearby(region_lat, region_lon)
for (let result of places.results) {
try {
const name = result.name
const rating = result.rating === undefined ? null : result.rating
const lon = result.geometry.location.lng
const lat = result.geometry.location.lat
const photo_ref = result.photos[0].photo_reference
const vicinity = result.vicinity
console.log("# New tourist attraction:", region_id, region.name, name)
await dbConn.query(
`INSERT INTO regions_nearby
(region_id,name,lon,lat,rating,vicinity,photo_reference)
VALUES
(${region_id},"${name}",${lon},${lat},${rating},"${vicinity}","${photo_ref}")
ON DUPLICATE KEY UPDATE
lon = ${lon},
lat = ${lat},
rating = ${rating},
vicinity = "${vicinity}",
photo_reference = "${photo_ref}"`
);
} catch (e) {
console.log(e)
}
}
} catch (e) {
console.log(e)
}
const res = "region nearby by id update finished"
return res
}

View File

@ -0,0 +1,32 @@
const getRegionNearbyById = require("../models/getRegionNearbyById.js")
const getPlacePhoto = require("../models/getPlacePhoto.js")
module.exports = async (dbConn) => {
try {
const region_ids = await dbConn.query(`
SELECT distinct region_id
FROM regions_nearby
ORDER BY region_id`)
for (let region_id of region_ids) {
const nearby = await getRegionNearbyById(dbConn, region_id.region_id)
for (let place of nearby) {
const url = await getPlacePhoto(place.photo_reference)
console.log("# Setting image Url:", region_id, place.place_name, url)
await dbConn.query(`
UPDATE regions_nearby
SET img_url = "${url}"
WHERE id = ${place.place_id}`)
}
}
} catch (e) {
console.log(e)
}
const res = "region nearby img url update finished"
return res
}

View File

@ -0,0 +1,33 @@
const getRegionNearbyById = require("../models/getRegionNearbyById.js")
const getPlacePhoto = require("../models/getPlacePhoto.js")
module.exports = async (dbConn,id) => {
try {
const region_ids = await dbConn.query(`
SELECT distinct region_id
FROM regions_nearby
WHERE region_id = ?`,
[id])
for (let region_id of region_ids) {
const nearby = await getRegionNearbyById(dbConn, region_id.region_id)
for (let place of nearby) {
const url = await getPlacePhoto(place.photo_reference)
console.log("# Setting image Url:", region_id, place.place_name, url)
await dbConn.query(`
UPDATE regions_nearby
SET img_url = "${url}"
WHERE id = ${place.place_id}`)
}
}
} catch (e) {
console.log(e)
}
const res = "region nearby img url update finished"
return res
}

1833
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
backend/package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "cc-data-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon ./index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.19.2",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"httpolyglot": "^0.1.2",
"lodash": "^4.17.15",
"mariadb": "^2.4.0",
"moment": "^2.26.0",
"morgan": "^1.10.0",
"mysql2": "^2.1.0",
"path": "^0.12.7",
"sqlstring": "^2.3.2",
"swagger-jsdoc": "^4.0.0",
"swagger-ui-express": "^4.1.4"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}

View File

@ -0,0 +1,54 @@
/**
* @swagger
* tags:
* name: Countries
* description: Access country data.
*/
const router = require("express").Router();
// Models
const getCountries = require("../models/getCountries.js");
const getCountryById = require("../models/getCountryById.js");
// Utils
const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
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) => {
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) => {
const id = sqlSanitzer(req.params.id);
res.json(await getCountryById(dbConn, id))
});
return router;
};

95
backend/routes/place.js Normal file
View File

@ -0,0 +1,95 @@
/**
* @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()
// Models
const getPlace = require("../models/getPlace.js")
const getPlaceNearby = require("../models/getPlaceNearby.js")
const getPlacePhoto = require("../models/getPlacePhoto.js")
// Utils
const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
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) => {
const place = await getPlace(req.query.q)
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) => {
const lat = req.query.lat
const lng = req.query.lng
const place = await getPlaceNearby(lat, lng)
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) => {
const photoref = req.query.photoref
const photo = await getPlacePhoto(photoref)
res.json(photo)
});
return router;
};

112
backend/routes/regions.js Normal file
View File

@ -0,0 +1,112 @@
/**
* @swagger
* tags:
* name: Regions
* description: Access region data.
*/
const router = require("express").Router();
// Models
const getRegions = require("../models/getRegions.js");
const getRegionById = require("../models/getRegionById.js");
const getRegionNearbyById = require("../models/getRegionNearbyById.js")
// Utils
const path = require("path");
const fs = require("fs");
const _ = require('lodash')
const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
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) => {
const data = await getRegions(dbConn)
if (req.query.randomize) {
const randomize = sqlSanitzer(req.query.randomize)
res.json(_.sampleSize(data, randomize))
} else {
res.json(data);
}
});
/**
* @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) => {
console.log(typeof req.params.id)
const id = sqlSanitzer(req.params.id);
console.log(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) => {
console.log("HERE")
if (fs.existsSync(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 {
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) => {
const id = sqlSanitzer(req.params.id);
res.json(await getRegionNearbyById(dbConn,id))
});
return router;
};

182
backend/routes/search.js Normal file
View File

@ -0,0 +1,182 @@
/**
* @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
}

135
backend/routes/update.js Normal file
View File

@ -0,0 +1,135 @@
/**
* @swagger
* tags:
* name: Update
* description: Endpoint for updating region specific data. Only for manual use in the prototype application!
*/
const router = require("express").Router();
// Models
const handleClimateUpdate = require("../models/handleClimateUpdate.js")
const handleClimateUpdateV2 = require("../models/handleClimateUpdateV2.js")
const handleUpdateRegionNearby = require("../models/handleUpdateRegionNearby.js")
const handleUpdateRegionNearbyById = require("../models/handleUpdateRegionNearbyById.js")
const handleUpdateRegionNearbyImgUrl = require("../models/handleUpdateRegionNearbyImgUrl.js")
const handleUpdateRegionNearbyImgUrlById = require("../models/handleUpdateRegionNearbyImgUrlById.js")
// Utils
const sqlSanitzer = require("../util/sqlstring_sanitizer.js")
module.exports = dbConn => {
/**
* @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))
});
/**
* @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))
});
/**
* @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);
res.json(await handleUpdateRegionNearbyById(dbConn, id))
});
/**
* @swagger
* /update/regions/{id}/nearby/image:
* put:
* 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);
res.json(await handleUpdateRegionNearbyImgUrlById(dbConn, id))
});
return router
}

62
backend/settings.js Normal file
View File

@ -0,0 +1,62 @@
module.exports = {
scoring: {
// parameter: [transition range, transition function, transistion slope exponent]
temperature_mean_max: [12, 'easeInOut', 2],
precipitation: [60, 'easeInOut', 2], // [170, 'easeInOut', 2],
rain_days: [5, 'easeInOut', 2],
sun_hours: [80, 'easeInOut', 2],
accommodation_costs: [30, 'linear', null],
food_costs: [25, 'linear', null],
alcohol_costs: [15, 'linear', null],
water_costs: [15, 'linear', null],
local_transportation_costs: [20, 'linear', null],
entertainment_costs: [20, 'easeInOut', 0.6],
average_per_day_costs: [100, 'linear', null],
avg_price_relative: [30, 'easeOut', 2],
},
boundaries: {
climate: {
min: {
temperature_mean: -9.6,
temperature_mean_min: -14.5,
temperature_mean_max: -4.7,
precipitation: 0,
rain_days: 0,
sun_hours: 3
},
max: {
temperature_mean: 38.3,
temperature_mean_min: 33.5,
temperature_mean_max: 43.7,
precipitation: 1145,
rain_days: 28,
sun_hours: 416
}
},
static: {
max: {
accommodation_costs: 500,
food_costs: 100,
alcohol_costs: 100,
water_costs: 100,
local_transportation_costs: 100,
entertainment_costs: 100,
average_per_day_costs: 1000,
avg_price_relative: 100
},
min: {
accommodation_costs: 0,
food_costs: 0,
alcohol_costs: 0,
water_costs: 0,
local_transportation_costs: 0,
entertainment_costs: 0,
average_per_day_costs: 0,
avg_price_relative: 0
}
}
}
}

33
backend/util/base64.js Normal file
View File

@ -0,0 +1,33 @@
/**
* 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));
}
/**
* Decodes a base64 encoded object.
* @param base64 encoded object
* @returns {string} decoded object
*/
function atob(base64) {
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) {
return Buffer.from(string).toString('base64')
}

View File

@ -0,0 +1,15 @@
/**
* 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) => {
if (array !== null && array !== undefined) {
const value = array
array = value.split(",");
for (i = 0; i < array.length; i++) {
array[i] = parseFloat(array[i])
}
}
return array;
}

View File

@ -0,0 +1,47 @@
const mariadb = require("mariadb");
let dbConn;
let conPool;
// mariadb doc: https://github.com/MariaDB/mariadb-connector-nodejs/blob/master/documentation/promise-api.md
async function reconnect() {
try {
dbConn = await conPool.getConnection();
} catch (e) {
if (e.code === "ECONNREFUSED") {
let err = new Error("Lost connection to the database");
err.code = "ERR_DB_CONN_LOST";
throw err;
} else {
throw e;
}
}
}
module.exports = async config => {
conPool = mariadb.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
database: process.env.DATABASE,
connectionLimit: 10
});
dbConn = await conPool.getConnection();
return {
async query(q, p) {
let res;
try {
res = await dbConn.query(q, p);
} catch (e) {
if (e.code === "ER_CMD_CONNECTION_CLOSED") {
await reconnect();
await this.query(q, p);
} else {
throw e;
}
}
return res;
}
};
};

View File

@ -0,0 +1,24 @@
module.exports = function (dbConn) {
return async function getAllRegionsWithClimatePerMonth(month) {
console.log('getAllRegionsWithClimatePerMonth')
const sql = `SELECT
region_climate.region_id AS region_id,
region_climate.month AS month,
countries.country AS country,
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 rain_days,
ROUND(AVG(region_climate.sunshine), 1) AS sun_hours
FROM region_climate
JOIN regions ON region_climate.region_id = regions.id
JOIN countries ON regions.country_id = countries.id
WHERE region_climate.month = ${month} GROUP BY region_id`
let response = await dbConn.query(sql)
console.log(response[0]);
return response
}
}

View File

@ -0,0 +1,22 @@
exports.getClimateMinMax = async function (dbConn) {
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(rain_days) AS rain_days,
MIN(sun_hours) AS sun_hours
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(rain_days) AS rain_days,
MAX(sun_hours) AS sun_hours
FROM region_climate`
const [qResMin, qResMax] = await Promise.all([dbConn.query(sqlMin), dbConn.query(sqlMax)])
// console.log(qResMin)
return { min: qResMin[0], max: qResMax[0] }
}

View File

@ -0,0 +1,5 @@
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)
}

View File

@ -0,0 +1,38 @@
const _ = require('lodash')
module.exports = function (queries) {
let res = _.clone(queries)
console.log(res);
// try {
if (queries.temperature_mean_max) res.temperature_mean_max = [Number(queries.temperature_mean_max.split(',')[0]), Number(queries.temperature_mean_max.split(',')[1])]
if (queries.precipitation) res.precipitation = [Number(queries.precipitation.split(',')[0]), Number(queries.precipitation.split(',')[1])]
if (queries.rain_days) res.rain_days = [Number(queries.rain_days.split(',')[0]), Number(queries.rain_days.split(',')[1])]
if (queries.sun_hours) res.sun_hours = [Number(queries.sun_hours.split(',')[0]), Number(queries.sun_hours.split(',')[1])]
if (queries.accommodation_costs) res.accommodation_costs = [Number(queries.accommodation_costs.split(',')[0]), Number(queries.accommodation_costs.split(',')[1])]
if (queries.food_costs) res.food_costs = [Number(queries.food_costs.split(',')[0]), Number(queries.food_costs.split(',')[1])]
if (queries.alcohol_costs) res.alcohol_costs = [Number(queries.alcohol_costs.split(',')[0]), Number(queries.alcohol_costs.split(',')[1])]
if (queries.water_costs) res.water_costs = [Number(queries.water_costs.split(',')[0]), Number(queries.water_costs.split(',')[1])]
if (queries.local_transportation_costs) res.local_transportation_costs = [Number(queries.local_transportation_costs.split(',')[0]), Number(queries.local_transportation_costs.split(',')[1])]
if (queries.entertainment_costs) res.entertainment_costs = [Number(queries.entertainment_costs.split(',')[0]), Number(queries.entertainment_costs.split(',')[1])]
if (queries.average_per_day_costs) res.average_per_day_costs = [Number(queries.average_per_day_costs.split(',')[0]), Number(queries.average_per_day_costs.split(',')[1])]
if (queries.avg_price_relative) res.avg_price_relative = [Number(queries.avg_price_relative.split(',')[0]), Number(queries.avg_price_relative.split(',')[1])]
if (queries.tags) {
res.tags = []
if (queries.tags.includes(',')) {
res.tags.push(...queries.tags.split(',').map(el => el.trim()))
} else {
res.tags.push(queries.tags)
}
}
// console.log(res);
// console.log('queries successfully transformed');
// } catch (error) {
// console.log('oldToNewQuerySyntax error');
// return queries
// }
return res
}

66
backend/util/score.js Normal file
View File

@ -0,0 +1,66 @@
const _ = require('lodash')
exports.calculateAvgScore = (...scores) => {
return avgScore = scores.reduce((total, score) => total += score) / scores.length;
}
exports.calculateScoreRange = (transitionRange, regionVal, sLowVal, sHighVal) => {
//console.log('scores.calculateScoreRange:', min, max, multiplier, regionVal, sLowVal, sHighVal)
// return full score when in range
if (regionVal >= sLowVal && regionVal <= sHighVal) return 10;
// choose value with smallest distance
let sVal = Math.abs(regionVal - sLowVal) < Math.abs(regionVal - sHighVal) ? sLowVal : sHighVal;
return this.calculateScore(transitionRange, regionVal, sVal);
}
exports.calculateScore = (transitionRange, regionVal, searchVal) => {
let score = 1 - (Math.abs(searchVal - regionVal) / transitionRange);
return (score) * 10;
//return score <= 0 ? 0 : score * 10;
}
exports.linear = function (x, exponent) {
if (x < 0) return 0
if (x > 10) return 10
return x
}
exports.easeOut = function (x, exponent) {
if (x < 0) return 0
if (x > 10) return 10
return (1 - Math.pow(1 - (x / 10), exponent)) * 10
}
exports.easeInOut = function (sc, exponent) {
const x = (sc ) / 10
// console.log(sc, x);
if (x<0) return 0
if (x>1) return 10
return x < 0.5 ? Math.pow(2, exponent-1) * Math.pow(x,exponent) * 10 : (1 - Math.pow(-2 * x + 2, exponent)/2) * 10
}
exports.easeInOutAsymmetric = function (sc, exponent) {
const x = (sc ) / 10
// console.log(sc, x);
if (x<0) return 0
if (x>1) return 10
return x < 0.5 ? (2 * x) - 0.5 * 10 : (1 - Math.pow(-2 * x + 2, exponent)/2) * 10
}
exports.sigmoid = function (x, exponent) {
// const sigm = (1 / (1 + Math.pow(Math.E, 5 * -x))) * 10 + 5
// const sigm = 10 / (1 + Math.pow(Math.E, 1.2 * -x + 6))
const sigm = 10 / (1 + 8 * Math.pow(Math.E, 3/4 * -x))
console.log('sigmoid (IN/OUT):', _.round(x,3), _.round(sigm, 3))
return sigm
}
exports.increaseTransitionForHighValues = function (transitionRange, searchVal) {
//console.log(transitionRange);
// console.log(transitionRange);
// console.log(((Math.pow(searchVal / 20, 2) / 100) + 1));
// console.log(((Math.pow(searchVal / 20, 2) / 100) + 1) * transitionRange);
let transFactor = ((Math.pow(searchVal / 20, 2) / 100) + 1)
return transFactor >= 4 ? 4 * transitionRange : transFactor * transitionRange
}

Some files were not shown because too many files have changed in this diff Show More