diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..5230abb --- /dev/null +++ b/.drone.yml @@ -0,0 +1,46 @@ +kind: pipeline +type: docker +name: build dev + +steps: + - name: build and push image + image: plugins/docker + settings: + dockerfile: Dockerfile + registry: git.fsisp.de + repo: git.fsisp.de/fionn/isasure-wx + username: + from_secret: reg_username + password: + from_secret: reg_password + tags: + - dev + - '${DRONE_COMMIT:0:8}' + +trigger: + branch: + - dev + +--- +kind: pipeline +type: docker +name: build master + +steps: + - name: build and push image + image: plugins/docker + settings: + dockerfile: Dockerfile + registry: git.fsisp.de + repo: git.fsisp.de/fionn/isasure-wx + username: + from_secret: reg_username + password: + from_secret: reg_password + tags: + - latest + - '${DRONE_COMMIT:0:8}' + +trigger: + branch: + - main diff --git a/backend/package-lock.json b/backend/package-lock.json index c6106ee..f71db98 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,10 +9,14 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "express": "^4.18.1" + "axios": "^1.3.4", + "express": "^4.18.1", + "morgan": "^1.10.0", + "node-schedule": "^2.1.1" }, "devDependencies": { "@types/express": "^4.17.13", + "@types/morgan": "^1.9.4", "@types/node": "^18.7.6", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", @@ -177,6 +181,15 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "node_modules/@types/morgan": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", + "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -528,12 +541,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -664,6 +708,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -708,6 +763,17 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cron-parser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz", + "integrity": "sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -761,6 +827,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1550,6 +1624,38 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2177,6 +2283,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2189,6 +2300,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "engines": { + "node": ">=12" + } + }, "node_modules/map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", @@ -2289,6 +2408,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2321,6 +2479,19 @@ "integrity": "sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==", "dev": true }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -2398,6 +2569,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2563,6 +2742,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -2902,6 +3086,11 @@ "node": ">=8" } }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, "node_modules/split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", @@ -3401,6 +3590,15 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "@types/morgan": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.4.tgz", + "integrity": "sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -3624,12 +3822,42 @@ "es-shim-unscopables": "^1.0.0" } }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -3737,6 +3965,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3772,6 +4008,14 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cron-parser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz", + "integrity": "sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==", + "requires": { + "luxon": "^3.2.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3808,6 +4052,11 @@ "object-keys": "^1.1.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4435,6 +4684,21 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4875,6 +5139,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -4884,6 +5153,11 @@ "yallist": "^4.0.0" } }, + "luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==" + }, "map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", @@ -4954,6 +5228,41 @@ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4983,6 +5292,16 @@ "integrity": "sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==", "dev": true }, + "node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "requires": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + } + }, "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -5036,6 +5355,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5156,6 +5480,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -5378,6 +5707,11 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + }, "split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", diff --git a/backend/package.json b/backend/package.json index 0f5b1bb..514907e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,10 +14,14 @@ "author": "Fionn Sperath", "license": "ISC", "dependencies": { - "express": "^4.18.1" + "axios": "^1.3.4", + "express": "^4.18.1", + "morgan": "^1.10.0", + "node-schedule": "^2.1.1" }, "devDependencies": { "@types/express": "^4.17.13", + "@types/morgan": "^1.9.4", "@types/node": "^18.7.6", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", @@ -30,4 +34,4 @@ "tsc-watch": "^5.0.3", "typescript": "^4.7.4" } -} \ No newline at end of file +} diff --git a/backend/src/app.ts b/backend/src/app.ts index d8940c2..7675c3e 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,10 +1,16 @@ import express, { NextFunction, Request, Response } from 'express'; +import nodesched from 'node-schedule'; +import morgan from 'morgan'; import router from './router'; +import wxService from './services/wx.service'; const { PORT = 3000 } = process.env; const app = express(); +app.set('trust proxy', true); +app.use(morgan('combined')); + app.use('/api', router.router); const frontendRoot = '/opt/frontend/dist'; @@ -19,6 +25,9 @@ app.use((err, req: Request, res: Response, next: NextFunction) => { res.status(500).json({ msg: 'an error occurred' }); }); +nodesched.scheduleJob('regenerate data', '*/30 * * * * *', wxService.wrappedGenerateData) +wxService.wrappedGenerateData(); + app.listen(PORT, () => { console.log( `application is listening on port ${PORT}`, diff --git a/backend/src/controllers/foo.controller.ts b/backend/src/controllers/foo.controller.ts deleted file mode 100644 index 99122a6..0000000 --- a/backend/src/controllers/foo.controller.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Request, Response } from 'express'; -import fooService from '../services/foo.service'; - -function getFoo(req: Request, res: Response) { - res.json({ - foo: true, - msg: fooService.getFooDetails(), - data: req.body, - }); -} - -export default { - getFoo, -}; diff --git a/backend/src/controllers/region.controller.ts b/backend/src/controllers/region.controller.ts new file mode 100644 index 0000000..19a3fc1 --- /dev/null +++ b/backend/src/controllers/region.controller.ts @@ -0,0 +1,31 @@ +import express from 'express'; +import regionsService from '../services/regions.service'; + +export async function getRegions(req: express.Request, res: express.Response, next: express.NextFunction) { + try { + res.json(regionsService.getRegions()); + } catch (e) { + next(e); + } +} + +export async function getRegion(req: express.Request, res: express.Response, next: express.NextFunction) { + try { + const { region } = req.params; + + const regionData = regionsService.getRegion(region); + + if(!regionData) { + return next(); + } + + res.json(regionData); + } catch (e) { + next(e); + } +} + +export default { + getRegions, + getRegion, +}; diff --git a/backend/src/controllers/wx.controller.ts b/backend/src/controllers/wx.controller.ts new file mode 100644 index 0000000..f3eb62c --- /dev/null +++ b/backend/src/controllers/wx.controller.ts @@ -0,0 +1,15 @@ +import express from 'express'; +import wxService from '../services/wx.service'; + +export async function getRegionWx(req: express.Request, res: express.Response, next: express.NextFunction) { + try { + const { region } = req.params; + res.json(wxService.getWx(region)); + } catch (e) { + next(e); + } +} + +export default { + getRegionWx, +}; diff --git a/backend/src/router.ts b/backend/src/router.ts index c06c289..ecd67dc 100644 --- a/backend/src/router.ts +++ b/backend/src/router.ts @@ -1,10 +1,13 @@ import { Request, Response, Router } from 'express'; -import fooController from './controllers/foo.controller'; + +import regionController from './controllers/region.controller'; +import wxController from './controllers/wx.controller'; const router = Router(); -router.get('/foo', fooController.getFoo); -// aaaaa +router.get('/regions', regionController.getRegions); +router.get('/regions/:region', regionController.getRegion); +router.get('/regions/:region/wx', wxController.getRegionWx); router.use((req: Request, res: Response) => { // 404 diff --git a/backend/src/services/config.service.ts b/backend/src/services/config.service.ts new file mode 100644 index 0000000..94c7460 --- /dev/null +++ b/backend/src/services/config.service.ts @@ -0,0 +1,26 @@ +import fs from 'fs'; + +export interface WxConfig { + regions: WxRegion[]; +} + +export interface WxRegion { + identifier: string; + fixes: WxFix[]; +} + +export interface WxFix { + name: string; + lat: number; + lon: number; +} + +export function getConfig(): WxConfig { + const data = JSON.parse(fs.readFileSync('/opt/wx-config.json').toString()); + + return data; +} + +export default { + getConfig, +} diff --git a/backend/src/services/foo.service.ts b/backend/src/services/foo.service.ts deleted file mode 100644 index 3ae4d00..0000000 --- a/backend/src/services/foo.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -function getFooDetails() { - return 'foo'; -} - -export default { - getFooDetails, -}; diff --git a/backend/src/services/regions.service.ts b/backend/src/services/regions.service.ts new file mode 100644 index 0000000..4e15f8b --- /dev/null +++ b/backend/src/services/regions.service.ts @@ -0,0 +1,18 @@ +import configService, { WxConfig, WxRegion } from "./config.service"; + +export function getRegions(): WxConfig["regions"] { + return configService.getConfig().regions; +} + +export function getRegion(identifier: string): WxRegion | undefined { + const regions = getRegions(); + + const region = regions.find(rg => rg.identifier == identifier); + + return region; +} + +export default { + getRegions, + getRegion, +}; diff --git a/backend/src/services/wx.service.ts b/backend/src/services/wx.service.ts new file mode 100644 index 0000000..8b2b4a1 --- /dev/null +++ b/backend/src/services/wx.service.ts @@ -0,0 +1,134 @@ +import axios from "axios"; +import { WxFix } from "./config.service"; +import regionsService from "./regions.service"; + +const cachedData: {[key: string]: WxData} = {}; + +const qnhLevelMapping = { + 200: 390, + 250: 340, + 300: 300, + 400: 240, + 500: 180, + 600: 140, + 700: 100, + 850: 50 +}; + +const necessaryDatapoints = [ + 'winddirection', + 'windspeed', + 'temperature', +]; + +const requestedData: string[] = [ + 'temperature_2m', + 'windspeed_10m', + 'winddirection_10m', +]; + +for (const qnh of Object.keys(qnhLevelMapping)) { + for (const necessaryDatapoint of necessaryDatapoints) { + requestedData.push(`${necessaryDatapoint}_${qnh}hPa`); + } +} + +interface WxLevelData { + "T(K)": string; + windspeed: string; + windhdg: string; +}; + +export interface WxFixData { + coords: { + lat: string; + long: string; + }; + + levels: { + [key: string]: WxLevelData; + } +} + +export interface WxData { + info: { + date: string; + datestring: string; + }; + data: { + [key: string]: WxFixData; + } +} + +export async function getDataAtFix(fix: WxFix, index: number): Promise { + const response = await axios.get(`https://api.open-meteo.com/v1/forecast?latitude=${fix.lat}&longitude=${fix.lon}&windspeed_unit=kn&forecast_days=1&hourly=${requestedData.join(',')}`); + const hourlyData = response.data.hourly; + + const data: WxFixData = { + coords: { + lat: String(fix.lat), + long: String(fix.lon) + }, + levels: {} + } + + data.levels["0"] = { + "T(K)": String(Number(hourlyData?.[`temperature_2m`]?.[index]) + 273.15), + "windspeed": String(hourlyData?.[`windspeed_10m`]?.[index]), + "windhdg": String(hourlyData?.[`winddirection_10m`]?.[index]), + }; + + for(const [qnh, fl] of Object.entries(qnhLevelMapping)) { + const temp = Number(hourlyData?.[`temperature_${qnh}hPa`]?.[index]) + 273.15; + const dir = hourlyData?.[`windspeed_${qnh}hPa`]?.[index]; + const speed = hourlyData?.[`winddirection_${qnh}hPa`]?.[index]; + + data.levels[String(fl)] = { + "T(K)": String(temp), + "windspeed": String(speed), + "windhdg": String(dir), + } + } + + return data; +} + +export async function generateData() { + const regions = regionsService.getRegions(); + + for (const region of regions) { + const now = new Date(); + + const regionData: WxData = { + info: { + date: now.toISOString(), + datestring: `${now.getUTCDate()}${now.getUTCHours()}`, + }, + data: {} + } + + for (const fix of region.fixes) { + regionData.data[fix.name] = await getDataAtFix(fix, now.getUTCHours()); + } + + cachedData[region.identifier] = regionData; + } + + return cachedData; +} + +export function wrappedGenerateData() { + generateData().catch((...params) => console.error(...params)); +} + +export function getWx(region: string): WxData | null { + const data = cachedData[region]; + + return data; +} + +export default { + getWx, + generateData, + wrappedGenerateData, +} \ No newline at end of file diff --git a/wx-config.json b/wx-config.json new file mode 100644 index 0000000..0d32c0e --- /dev/null +++ b/wx-config.json @@ -0,0 +1,50 @@ +{ + "$schema": "./wx-config.schema.json", + "regions": [ + { + "identifier": "EDGG", + "fixes": [ + { + "name": "DKB", + "lat": 49.142753, + "lon": 10.238306 + }, + { + "name": "DUS", + "lat": 51.283189, + "lon": 6.753725 + }, + { + "name": "NATOR", + "lat": 48.17, + "lon": 8.321261 + }, + { + "name": "RASPU", + "lat": 50.072778, + "lon": 10.098611 + }, + { + "name": "PAD", + "lat": 51.610178, + "lon": 8.618653 + } + ] + }, + { + "identifier": "LOVV", + "fixes": [ + { + "name": "VATET", + "lat": 47.600953, + "lon": 14.033119 + }, + { + "name": "LNZ", + "lat": 48.229711, + "lon": 14.103156 + } + ] + } + ] +} \ No newline at end of file diff --git a/wx-config.schema.json b/wx-config.schema.json new file mode 100644 index 0000000..04e2c1e --- /dev/null +++ b/wx-config.schema.json @@ -0,0 +1,44 @@ +{ + "$id": "https://wx.dotfionn.de/wx-config.schema.json", + "title": "WX-Config", + "description": "The configuration file", + "type": "object", + "properties": { + "regions": { + "description": "The unique identifier for a product", + "type": "array", + "items": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "e.g. the FIR identifier, e.g. EDGG, EDWW, EDMM, LOVV, ..." + }, + "fixes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "identifier of the waypoint/fix" + }, + "lat": { + "type": "number", + "description": "latitude of the waypoint" + }, + "lon": { + "type": "number", + "description": "longitude of the waypoint" + } + }, + "required": ["name", "lat", "lon"] + } + } + } + }, + "required": ["identifier", "fixes"] + } + }, + "required": [ "regions" ] +}