add Python server backend
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2024-05-11 14:06:39 +02:00
parent 14d63294a3
commit cd3afa4d61
22 changed files with 1896 additions and 119 deletions

1
.env
View File

@@ -1,3 +1,2 @@
PORT=3000
MONGO_SERVER=nuc.home
MONGO_DB=sensor_data

3
.gitignore vendored
View File

@@ -13,11 +13,10 @@ yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
server/.env
__pycache__

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.codeActionsOnSave": {
"source.organizeImports": "always"
}
},
}

View File

@@ -1,7 +1,18 @@
FROM node:18-slim
FROM node:20-slim AS frontend
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["npm", "run", "server"]
# CMD ["npm", "run", "server"]
FROM python:3.12-slim
COPY --from=frontend /app/dist /app/dist
WORKDIR /app/server-python
RUN pip install poetry==1.8.2
COPY server-python/pyproject.toml server-python/poetry.lock ./
RUN poetry install
COPY server-python/app.py .
ENV PRODUCTION="true"
CMD ["poetry", "run", "fastapi", "run", "app.py", "--port", "3000"]

View File

@@ -1,3 +1,13 @@
.PHONY: server-python server-node
server-python:
cd server-python; poetry run fastapi dev app.py --port 3000; cd -
server-node:
npm run server:dev
fe:
npm run dev
schema:
cd server-python; poetry run python -m generate_schema; cd -
build:
docker build -t sensor-web-v2 .
run: build

293
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"express": "^4.18.2",
"highcharts": "^10.3.2",
"mongodb": "^4.13.0",
"openapi-fetch": "^0.9.5",
"sass": "^1.69.5",
"vue": "^3.4.3",
"vue-router": "^4.2.5"
@@ -33,6 +34,7 @@
"jsdom": "^23.0.1",
"nodemon": "^3.0.1",
"npm-run-all": "^4.1.5",
"openapi-typescript": "^6.7.5",
"prettier": "^3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
@@ -1561,6 +1563,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -2935,9 +2946,9 @@
}
},
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -2992,6 +3003,15 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -3295,15 +3315,9 @@
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -3316,6 +3330,9 @@
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
@@ -3382,6 +3399,12 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"node_modules/confbox": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
"dev": true
},
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -3665,11 +3688,14 @@
}
},
"node_modules/dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/eastasianwidth": {
@@ -5388,12 +5414,6 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -5631,15 +5651,15 @@
}
},
"node_modules/mlly": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz",
"integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz",
"integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==",
"dev": true,
"dependencies": {
"acorn": "^8.10.0",
"pathe": "^1.1.1",
"pkg-types": "^1.0.3",
"ufo": "^1.3.0"
"acorn": "^8.11.3",
"pathe": "^1.1.2",
"pkg-types": "^1.1.0",
"ufo": "^1.5.3"
}
},
"node_modules/mongodb": {
@@ -6097,6 +6117,48 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/openapi-fetch": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.5.tgz",
"integrity": "sha512-ToRnypJB2G5bEUZqJ4mGty/qDVgAZ4BW0znlXQAECxAp4EM8dYtgQ1mrw2Ij6W7knN4VawHvFq8uTqzXyMGNPA==",
"dependencies": {
"openapi-typescript-helpers": "^0.0.8"
}
},
"node_modules/openapi-typescript": {
"version": "6.7.5",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.5.tgz",
"integrity": "sha512-ZD6dgSZi0u1QCP55g8/2yS5hNJfIpgqsSGHLxxdOjvY7eIrXzj271FJEQw33VwsZ6RCtO/NOuhxa7GBWmEudyA==",
"dev": true,
"dependencies": {
"ansi-colors": "^4.1.3",
"fast-glob": "^3.3.2",
"js-yaml": "^4.1.0",
"supports-color": "^9.4.0",
"undici": "^5.28.2",
"yargs-parser": "^21.1.1"
},
"bin": {
"openapi-typescript": "bin/cli.js"
}
},
"node_modules/openapi-typescript-helpers": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz",
"integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g=="
},
"node_modules/openapi-typescript/node_modules/supports-color": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
"integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -6268,9 +6330,9 @@
}
},
"node_modules/pathe": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
"integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
"dev": true
},
"node_modules/pathval": {
@@ -6320,14 +6382,14 @@
}
},
"node_modules/pkg-types": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
"integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz",
"integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==",
"dev": true,
"dependencies": {
"jsonc-parser": "^3.2.0",
"mlly": "^1.2.0",
"pathe": "^1.1.0"
"confbox": "^0.1.7",
"mlly": "^1.7.0",
"pathe": "^1.1.2"
}
},
"node_modules/postcss": {
@@ -7459,9 +7521,9 @@
}
},
"node_modules/ufo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz",
"integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==",
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
"dev": true
},
"node_modules/unbox-primitive": {
@@ -7485,6 +7547,18 @@
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"node_modules/undici": {
"version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
"dev": true,
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -9178,6 +9252,15 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
@@ -10324,6 +10407,12 @@
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
"dev": true
},
"@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"dev": true
},
"@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -11258,9 +11347,9 @@
}
},
"acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true
},
"acorn-jsx": {
@@ -11297,6 +11386,12 @@
"uri-js": "^4.2.2"
}
},
"ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
"dev": true
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -11519,9 +11614,9 @@
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -11585,6 +11680,12 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"confbox": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
"dev": true
},
"config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -11798,9 +11899,9 @@
}
},
"dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="
},
"eastasianwidth": {
"version": "0.2.0",
@@ -13060,12 +13161,6 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -13240,15 +13335,15 @@
"dev": true
},
"mlly": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz",
"integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz",
"integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==",
"dev": true,
"requires": {
"acorn": "^8.10.0",
"pathe": "^1.1.1",
"pkg-types": "^1.0.3",
"ufo": "^1.3.0"
"acorn": "^8.11.3",
"pathe": "^1.1.2",
"pkg-types": "^1.1.0",
"ufo": "^1.5.3"
}
},
"mongodb": {
@@ -13589,6 +13684,41 @@
"mimic-fn": "^4.0.0"
}
},
"openapi-fetch": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.5.tgz",
"integrity": "sha512-ToRnypJB2G5bEUZqJ4mGty/qDVgAZ4BW0znlXQAECxAp4EM8dYtgQ1mrw2Ij6W7knN4VawHvFq8uTqzXyMGNPA==",
"requires": {
"openapi-typescript-helpers": "^0.0.8"
}
},
"openapi-typescript": {
"version": "6.7.5",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.5.tgz",
"integrity": "sha512-ZD6dgSZi0u1QCP55g8/2yS5hNJfIpgqsSGHLxxdOjvY7eIrXzj271FJEQw33VwsZ6RCtO/NOuhxa7GBWmEudyA==",
"dev": true,
"requires": {
"ansi-colors": "^4.1.3",
"fast-glob": "^3.3.2",
"js-yaml": "^4.1.0",
"supports-color": "^9.4.0",
"undici": "^5.28.2",
"yargs-parser": "^21.1.1"
},
"dependencies": {
"supports-color": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
"integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
"dev": true
}
}
},
"openapi-typescript-helpers": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz",
"integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g=="
},
"optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -13714,9 +13844,9 @@
"dev": true
},
"pathe": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
"integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
"dev": true
},
"pathval": {
@@ -13748,14 +13878,14 @@
"dev": true
},
"pkg-types": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
"integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz",
"integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==",
"dev": true,
"requires": {
"jsonc-parser": "^3.2.0",
"mlly": "^1.2.0",
"pathe": "^1.1.0"
"confbox": "^0.1.7",
"mlly": "^1.7.0",
"pathe": "^1.1.2"
}
},
"postcss": {
@@ -14545,9 +14675,9 @@
"devOptional": true
},
"ufo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz",
"integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==",
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
"dev": true
},
"unbox-primitive": {
@@ -14568,6 +14698,15 @@
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"undici": {
"version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
"dev": true,
"requires": {
"@fastify/busboy": "^2.0.0"
}
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -15471,6 +15610,12 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View File

@@ -8,10 +8,11 @@
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"server": "node --no-warnings --loader ts-node/esm server/index.ts",
"server:dev": "nodemon --watch \"server/**\" --ext \"ts\" --exec \"npm run server\"",
"server": "node --no-warnings --loader ts-node/esm server-node/index.ts",
"server:dev": "nodemon --watch \"server-node/**\" --ext \"ts\" --exec \"npm run server\"",
"test": "vitest",
"test:ci": "vitest run"
"test:ci": "vitest run",
"generate-api-client": "openapi-typescript server-python/openapi.json -o src/api.d.ts"
},
"dependencies": {
"@vueuse/core": "^10.5.0",
@@ -21,6 +22,7 @@
"express": "^4.18.2",
"highcharts": "^10.3.2",
"mongodb": "^4.13.0",
"openapi-fetch": "^0.9.5",
"sass": "^1.69.5",
"vue": "^3.4.3",
"vue-router": "^4.2.5"
@@ -39,6 +41,7 @@
"jsdom": "^23.0.1",
"nodemon": "^3.0.1",
"npm-run-all": "^4.1.5",
"openapi-typescript": "^6.7.5",
"prettier": "^3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",

View File

@@ -9,7 +9,7 @@ dotenv.config()
console.log('Starting with parameters:')
console.table(
['PORT', 'MONGO_SERVER', 'MONGO_DB'].map((parameter) => {
['MONGO_SERVER', 'MONGO_DB'].map((parameter) => {
return {
parameter,
value: process.env[parameter]
@@ -18,8 +18,8 @@ console.table(
)
const app = express()
app.listen(process.env.PORT, () => {
console.log('Listening on ' + process.env.PORT)
app.listen(3000, () => {
console.log('Listening on ' + 3000)
})
let db: mongo.Db

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "ESNext", /* Specify what module code is generated. */
"module": "NodeNext", /* Specify what module code is generated. */
"moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */

View File

@@ -0,0 +1 @@
3.12.2

140
server-python/app.py Normal file
View File

@@ -0,0 +1,140 @@
import json
import os
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from typing import Literal
import motor.motor_asyncio
from bson import json_util
from bson.objectid import ObjectId
from dotenv import find_dotenv, load_dotenv
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from prettytable import PrettyTable
from pydantic import BaseModel
env_file = find_dotenv(".env")
load_dotenv("../.env")
IS_PROD = os.getenv("PRODUCTION", "") == "true"
class Config:
MONGO_SERVER: str = os.environ.get("MONGO_SERVER", "")
MONGO_DB: str = os.environ.get("MONGO_DB", "")
class Document(BaseModel):
date: int
value: float
@asynccontextmanager
async def lifespan(app: FastAPI):
table = PrettyTable()
table.field_names = ["VARIABLE", "VALUE"]
table.add_rows(
[
[env_variable, os.environ.get(env_variable)]
for env_variable in ["MONGO_SERVER", "MONGO_DB"]
]
)
print(table)
yield
app = FastAPI(openapi_url=("/openapi.json" if not IS_PROD else ""), lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
client = motor.motor_asyncio.AsyncIOMotorClient(Config.MONGO_SERVER, 27017)
db = client[Config.MONGO_DB]
def object_id_from_date(date: int) -> ObjectId:
timestamp = datetime.fromtimestamp(date, tz=timezone.utc)
object_id = ObjectId.from_datetime(timestamp)
return object_id
@app.get(
"/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}",
tags=["Retrieve sensor data"],
)
async def get_sensor_data(
type: Literal["temperature", "humidity"],
start_date: int,
end_date: int,
sample: int,
) -> list[Document]:
collection = db["dht22"]
pipeline = [
{
"$match": {
"$and": [
{
"_id": {
"$gt": object_id_from_date(start_date),
"$lt": object_id_from_date(end_date),
}
},
{"type": type},
{"value": {"$ne": "NaN"}},
]
}
},
{
"$group": {
"_id": {
"$toDate": {
"$subtract": [
{"$toLong": {"$toDate": "$_id"}},
{
"$mod": [
{"$toLong": {"$toDate": "$_id"}},
1000 * 60 * sample,
]
},
]
}
},
"value": {"$avg": "$value"},
"date": {"$min": {"$toDate": "$_id"}},
}
},
{
"$project": {
"_id": 0,
"value": {"$round": ["$value", 1]},
"date": {"$toLong": "$date"},
}
},
{"$sort": {"date": 1}},
]
docs = [doc async for doc in collection.aggregate(pipeline)]
return json.loads(json_util.dumps(docs))
index = FileResponse(path="../dist/index.html", headers={"Cache-Control": "no-cache"})
@app.middleware("http")
async def add_default_404(request: Request, call_next):
response = await call_next(request)
if response.status_code == 404:
return index
else:
return response
app.mount("/", StaticFiles(directory="../dist", html=True), name="dist")

View File

@@ -0,0 +1,16 @@
import json
from app import app
from fastapi.openapi.utils import get_openapi
with open("openapi.json", "w") as f:
json.dump(
get_openapi(
title=app.title,
version=app.version,
openapi_version=app.openapi_version,
description=app.description,
routes=app.routes,
),
f,
)

View File

@@ -0,0 +1,81 @@
{
"openapi": "3.1.0",
"info": { "title": "FastAPI", "version": "0.1.0" },
"paths": {
"/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}": {
"get": {
"tags": ["Retrieve sensor data"],
"summary": "Get Sensor Data",
"operationId": "get_sensor_data_type__type__startDate__start_date__endDate__end_date__sample__sample__get",
"parameters": [
{
"name": "type",
"in": "path",
"required": true,
"schema": { "enum": ["temperature", "humidity"], "type": "string", "title": "Type" }
},
{
"name": "start_date",
"in": "path",
"required": true,
"schema": { "type": "integer", "title": "Start Date" }
},
{ "name": "end_date", "in": "path", "required": true, "schema": { "type": "integer", "title": "End Date" } },
{ "name": "sample", "in": "path", "required": true, "schema": { "type": "integer", "title": "Sample" } }
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": { "$ref": "#/components/schemas/Document" },
"title": "Response Get Sensor Data Type Type Startdate Start Date Enddate End Date Sample Sample Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
}
}
}
}
},
"components": {
"schemas": {
"Document": {
"properties": {
"date": { "type": "integer", "title": "Date" },
"value": { "type": "number", "title": "Value" }
},
"type": "object",
"required": ["date", "value"],
"title": "Document"
},
"HTTPValidationError": {
"properties": {
"detail": { "items": { "$ref": "#/components/schemas/ValidationError" }, "type": "array", "title": "Detail" }
},
"type": "object",
"title": "HTTPValidationError"
},
"ValidationError": {
"properties": {
"loc": {
"items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] },
"type": "array",
"title": "Location"
},
"msg": { "type": "string", "title": "Message" },
"type": { "type": "string", "title": "Error Type" }
},
"type": "object",
"required": ["loc", "msg", "type"],
"title": "ValidationError"
}
}
}
}

1209
server-python/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
[tool.poetry]
name = "sensor-web-api"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
motor = "^3.4.0"
fastapi = "^0.111.0"
python-dotenv = "^1.0.1"
[tool.poetry.group.dev.dependencies]
ruff = "^0.4.4"
prettytable = "^3.10.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

78
src/api.d.ts vendored Normal file
View File

@@ -0,0 +1,78 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}": {
/** Get Sensor Data */
get: operations["get_sensor_data_type__type__startDate__start_date__endDate__end_date__sample__sample__get"];
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/** Document */
Document: {
/** Date */
date: number;
/** Value */
value: number;
};
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
detail?: components["schemas"]["ValidationError"][];
};
/** ValidationError */
ValidationError: {
/** Location */
loc: (string | number)[];
/** Message */
msg: string;
/** Error Type */
type: string;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
/** Get Sensor Data */
get_sensor_data_type__type__startDate__start_date__endDate__end_date__sample__sample__get: {
parameters: {
path: {
type: "temperature" | "humidity";
start_date: number;
end_date: number;
sample: number;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": components["schemas"]["Document"][];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}

View File

@@ -1,18 +1,17 @@
<template>
<div class="chart-container">
<Loader v-if="loading"></Loader>
<Loader v-if="isLoading"></Loader>
<div class="chart" id="chart" ref="chart" v-else></div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ref, watch } from 'vue'
import * as Highcharts from 'highcharts'
import 'highcharts/css/highcharts.scss'
import Loader from '@/components/Loader.vue'
import { capitalizeFirstLetter } from '@/utils/helpers'
import type { Window, NavType } from '@/utils/types'
import { typeApi } from '@/utils/types'
import { useFetch } from '@vueuse/core'
import type { Window, NavTypes } from '@/utils/types'
import { useApi } from '@/composables/api'
Highcharts.setOptions({
time: {
@@ -22,26 +21,12 @@ Highcharts.setOptions({
const props = defineProps<{
activeWindow: Window
activeType: NavType | undefined
activeType: NavTypes | undefined
}>()
const chart = ref<HTMLElement | null>(null)
const fetchUrl = computed(() => {
const [start, end] = [props.activeWindow.getStart(), props.activeWindow.getEnd()]
const sample = Math.round((end - start) / 60 / 288) || 1
const host = import.meta.env.MODE === 'development' ? 'http://localhost:3000' : ''
const fetchUrl = `${host}/type/${
typeApi[props.activeType || 'temperatuur']
}/startDate/${start}/endDate/${end}/sample/${sample}`
return fetchUrl
})
interface ChartDataPoint {
date: Number
value: Number
}
const { data: chartData, isFetching: loading } = useFetch(fetchUrl, { refetch: true }).json<ChartDataPoint[]>()
const { chartData, isLoading } = useApi(props)
watch([chart, chartData], () => {
if (chart.value && chartData.value) renderChart()

View File

@@ -15,11 +15,13 @@ import NavBar from '@/components/NavBar.vue'
import TimeWindows from '@/components/TimeWindows.vue'
import Chart from '@/components/Chart.vue'
import { windows } from '@/utils/helpers'
import type { Window, NavType } from '@/utils/types'
import type { Window, NavTypes } from '@/utils/types'
import { navTypes } from '@/utils/types'
const defaultWindow = windows[1]
const defaultType = navTypes[0]
const activeType = ref<NavType>()
const activeType = ref<NavTypes>()
const activeWindow = ref<Window>(defaultWindow)
const router = useRouter()
@@ -27,9 +29,10 @@ const route = useRoute()
const urlEncodeWindow = (label: string) => label.replace(' ', '-')
if (route.params.type) activeType.value = route.params.type as NavType
if (route.params.type)
// activeType.value = (navTypes.includes(route.params.type.toString()) ? route.params.type : navTypes[0]) as NavTypes
activeType.value = (navTypes.find((t) => t === route.params.type) || defaultType) as NavTypes
if (route.params.window) {
console.log(route.params.window, windows)
activeWindow.value = windows.find((w) => urlEncodeWindow(w.label) === route.params.window) || defaultWindow
}

View File

@@ -34,17 +34,17 @@
<script setup lang="ts">
import { capitalizeFirstLetter } from '@/utils/helpers'
import { ref } from 'vue'
import type { NavType } from '@/utils/types'
import type { NavTypes } from '@/utils/types'
const activeType = defineModel<NavType>()
const activeType = defineModel<NavTypes>()
const setType = (type: NavType) => {
const setType = (type: NavTypes) => {
toggled.value = false
activeType.value = type
}
const toggled = ref(false)
const navTypes: NavType[] = ['temperatuur', 'luchtvochtigheid']
const navTypes: NavTypes[] = ['temperatuur', 'luchtvochtigheid']
if (!activeType.value) setType(navTypes[0])
const toggleMenu = () => (toggled.value = !toggled.value)
</script>

56
src/composables/api.ts Normal file
View File

@@ -0,0 +1,56 @@
import createClient from 'openapi-fetch'
import { navTypeToApiTypeMapping } from '@/utils/types'
import type { Window, NavTypes } from '@/utils/types'
import { computed, ref, watch } from 'vue'
import type { paths } from '@/api'
type Props = {
activeWindow: Window
activeType: NavTypes | undefined
}
type ApiParameters =
paths['/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}']['get']['parameters']['path']
type ApiResponse =
paths['/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}']['get']['responses']['200']['content']['application/json']
type ApiError =
paths['/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}']['get']['responses']['422']['content']['application/json']
export function useApi(props: Props) {
const client = createClient<paths>({ baseUrl: import.meta.env.MODE === 'development' ? 'http://localhost:3000' : '' })
const chartData = ref<ApiResponse | null>(null)
const chartError = ref<ApiError | null>(null)
const isLoading = ref(false)
const apiParameters = computed<ApiParameters>(() => {
const [start_date, end_date] = [props.activeWindow.getStart(), props.activeWindow.getEnd()]
return {
type: navTypeToApiTypeMapping[props.activeType || 'temperatuur'],
start_date,
end_date,
sample: Math.round((start_date - end_date) / 60 / 288) || 1
}
})
const getData = async () => {
isLoading.value = true
const { data } = await client.GET('/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}', {
params: { path: apiParameters.value }
})
if (data) chartData.value = data
isLoading.value = false
}
watch([() => [props.activeType, props.activeWindow]], () => getData(), { immediate: true })
return {
chartData,
chartError,
isLoading
}
}

View File

@@ -1,3 +1,5 @@
import type { paths } from '@/api'
export interface Window {
label: string
getStart: { (): number }
@@ -6,9 +8,18 @@ export interface Window {
interval: number
}
export const typeApi = {
export type ApiTypes =
paths['/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}']['get']['parameters']['path']['type']
export type NavTypes = 'temperatuur' | 'luchtvochtigheid'
type NavTypeToApiTypeMapping = {
[key in NavTypes]: ApiTypes
}
export const navTypeToApiTypeMapping: NavTypeToApiTypeMapping = {
temperatuur: 'temperature',
luchtvochtigheid: 'humidity'
}
export type NavType = keyof typeof typeApi
export const navTypes = Object.keys(navTypeToApiTypeMapping)