This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "always"
|
||||
}
|
||||
},
|
||||
}
|
||||
15
Dockerfile
15
Dockerfile
@@ -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"]
|
||||
|
||||
|
||||
10
Makefile
10
Makefile
@@ -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
293
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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. */
|
||||
1
server-python/.python-version
Normal file
1
server-python/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.12.2
|
||||
140
server-python/app.py
Normal file
140
server-python/app.py
Normal 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")
|
||||
16
server-python/generate_schema.py
Normal file
16
server-python/generate_schema.py
Normal 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,
|
||||
)
|
||||
81
server-python/openapi.json
Normal file
81
server-python/openapi.json
Normal 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
1209
server-python/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
server-python/pyproject.toml
Normal file
22
server-python/pyproject.toml
Normal 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
78
src/api.d.ts
vendored
Normal 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"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
56
src/composables/api.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user