From fee1e116d27983150c3949747b42cf32b15fe74a Mon Sep 17 00:00:00 2001 From: anibilag Date: Wed, 30 Jul 2025 23:30:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=87=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=200.1.0=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=B6=D1=83=D1=80=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BD=D0=B0=D0=B1=D0=BB=D1=8E=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9.=20=D0=9F=D0=BE=D1=87=D1=82=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B0=D1=8F=20=D1=80=D1=83=D1=81=D1=81=D0=B8=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/database/gardentrack.db | Bin 0 -> 32768 bytes backend/package-lock.json | 1062 +------------------------ backend/package.json | 2 +- backend/scripts/init-db-better.js | 110 +++ backend/scripts/init-db.js | 105 ++- backend/server.js | 1006 ++++++++++++----------- docker-compose.yml | 13 +- package.json | 2 +- src/App.tsx | 30 +- src/components/Dashboard.tsx | 26 +- src/components/MaintenanceForm.tsx | 38 +- src/components/MaintenanceLog.tsx | 32 +- src/components/ObservationForm.tsx | 229 ++++++ src/components/ObservationJournal.tsx | 260 ++++++ src/components/PlantForm.tsx | 50 +- src/components/PlantRegistry.tsx | 44 +- src/components/PlantStatistics.tsx | 22 +- src/components/TaskForm.tsx | 22 +- src/components/TaskPlanner.tsx | 16 +- src/services/api.ts | 32 +- src/types/index.ts | 14 + 21 files changed, 1434 insertions(+), 1681 deletions(-) create mode 100644 backend/database/gardentrack.db create mode 100644 backend/scripts/init-db-better.js create mode 100644 src/components/ObservationForm.tsx create mode 100644 src/components/ObservationJournal.tsx diff --git a/backend/database/gardentrack.db b/backend/database/gardentrack.db new file mode 100644 index 0000000000000000000000000000000000000000..0691ddff748c6439bd4a9f017a58a2f540f85c77 GIT binary patch literal 32768 zcmeI5&2Jh<6u@`EG1!Vtd$5piV?_~02`#XuseLLY8^;O;7qC>H&;k>)&9aMU7rRtF zB(9_6P^p(*d+4dT^+S!CDoXx_Me2XhOM1^KhpKOu4YtRCS?bTr5d_9 z9umwH=X(>q|5`0rS#VJpzKzYH~pn=yOuA=n;SWY`f`tN6l#A>hLSRJu`555EUUFB$>*Rd zSsCbN(#1^rs_cp;dc>v!;c!^In{l+nQp{VX^A+&uf)nJ3e@KY`DEMBLdTPQOe2N@v z)T}bK$pzC#TBAx$Q}q*6qG~jn8U*T&@wVNZIkYujQnH$Sa z#Ac2xN2+z&P^*d-xlAHADYH~f{hTs>bqG4nRDF-1H(5(}Pc5dnhR6*tct?#yqg<4u zdf};~(lA=O)z=r*EOU+1Pg4tsKV5MWZ?@v}x@`2z;8Y-d>5_PN!_k|%!Vaiu^>k+B z@|;IsMk4=jUDMR8>M1K~(SynAtIbgiqV zWw3VF^tl&P9x-^LUY!uyRAD_s>)~3_45I;EtK9F>T-9%~77T-0wQFW$VeQRY>%15`ova&~ea^r#-H?chQKw&C{zBD!g>St4@>R#RIPhFh4 zdgSX-ifW573X{Q+yXsWYta=%Sf2-B)i~W%`vS%2zh->b>=^VT7M1y75pgr9KeeB9z9unZKj<8FZrhJK$B_PU=c`yUo=nE#E3rgM_+@0-Ykvp%{0OO^+7BUF zDi*&IOMXZat7|JOYb#g!L8~MgUz3t+sViUjL&8OIVO3lb7X%?DNqODb{4TyHK@ua#Il6-&e~@HP1L?v5aAf z27ADh)dbYxvxvBm0Pz3^5Kk6%PBfBhF?ej%cI1+7!tOuR;ku#IHas>kn;ubc3LDHg zgxwEdplOqp%b(5rLVpUO=kSFG5=10" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC", - "optional": true - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -82,81 +32,6 @@ "node": ">= 0.6" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "optional": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -171,28 +46,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC", - "optional": true - }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -203,7 +56,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -226,6 +79,17 @@ ], "license": "MIT" }, + "node_modules/better-sqlite3": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.7.0.tgz", + "integrity": "sha512-99jZU4le+f3G6aIl6PmmV0cxUIWqKieHxsiF7G34CVFiE+/UabpYqkU0NJIkY/96mQKikHeBjtR27vFfs5JpEw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -287,7 +151,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -340,36 +204,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -424,49 +258,13 @@ "fsevents": "~2.3.2" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "optional": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true, + "dev": true, "license": "MIT" }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC", - "optional": true - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -549,13 +347,6 @@ "node": ">=4.0.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT", - "optional": true - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -616,13 +407,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "optional": true - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -632,29 +416,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -664,23 +425,6 @@ "once": "^1.4.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "license": "MIT", - "optional": true - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -842,25 +586,6 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC", - "optional": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -885,27 +610,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -949,28 +653,6 @@ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "license": "MIT" }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -996,13 +678,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC", - "optional": true - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1025,13 +700,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC", - "optional": true - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1044,13 +712,6 @@ "node": ">= 0.4" } }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause", - "optional": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1067,95 +728,6 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1195,45 +767,6 @@ "dev": true, "license": "ISC" }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "license": "ISC", - "optional": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "optional": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1246,20 +779,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "license": "MIT", - "optional": true, - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1292,16 +811,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1315,13 +824,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT", - "optional": true - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1332,61 +834,6 @@ "node": ">=0.12.0" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "optional": true - }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "license": "MIT", - "optional": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "license": "ISC", - "optional": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1472,7 +919,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -1490,113 +937,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -1636,37 +976,6 @@ "node": ">=10" } }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "license": "MIT", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", @@ -1721,22 +1030,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1747,23 +1040,6 @@ "node": ">=0.10.0" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1806,22 +1082,6 @@ "wrappy": "1" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1831,16 +1091,6 @@ "node": ">= 0.8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -1886,27 +1136,6 @@ "node": ">=10" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "license": "ISC", - "optional": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2018,33 +1247,6 @@ "node": ">=8.10.0" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2137,13 +1339,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC", - "optional": true - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -2222,13 +1417,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC", - "optional": true - }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -2287,116 +1475,6 @@ "node": ">=10" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", - "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/socks-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socks-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/sqlite3": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", - "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1", - "tar": "^6.1.11" - }, - "optionalDependencies": { - "node-gyp": "8.x" - }, - "peerDependencies": { - "node-gyp": "8.x" - }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } - } - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2415,34 +1493,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "optional": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -2465,23 +1515,6 @@ "node": ">=4" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/tar-fs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", @@ -2516,15 +1549,6 @@ "node": ">=6" } }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2589,26 +1613,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "license": "ISC", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2642,43 +1646,11 @@ "node": ">= 0.8" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "optional": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" } } } diff --git a/backend/package.json b/backend/package.json index daa800b..6a10511 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "express": "^4.18.2", - "sqlite3": "^5.1.6", + "better-sqlite3": "^8.7.0", "cors": "^2.8.5", "body-parser": "^1.20.2", "dotenv": "^16.3.1" diff --git a/backend/scripts/init-db-better.js b/backend/scripts/init-db-better.js new file mode 100644 index 0000000..d3995a2 --- /dev/null +++ b/backend/scripts/init-db-better.js @@ -0,0 +1,110 @@ +const fs = require('fs'); +const path = require('path'); +const Database = require('better-sqlite3'); + +// Определяем путь +const DB_DIR = path.join(__dirname, '..', 'database'); +const DB_PATH = path.join(DB_DIR, 'gardentrack.db'); + +// Создаём папку, если не существует +if (!fs.existsSync(DB_DIR)) { + fs.mkdirSync(DB_DIR, { recursive: true }); + console.log('📁 Папка database создана'); +} + +// Подключаемся к базе +const db = new Database(DB_PATH); +console.log('✅ Подключено к базе:', DB_PATH); + +// Создаём таблицы +db.exec(` +CREATE TABLE IF NOT EXISTS plants ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + purchase_location TEXT, + seedling_age INTEGER, + type TEXT NOT NULL, + variety TEXT NOT NULL, + seedling_height REAL, + planting_date DATE NOT NULL, + current_height REAL, + health_status TEXT DEFAULT 'good' CHECK(health_status IN ('good', 'needs-attention', 'dead')), + photo_url TEXT, + current_photo_url TEXT, + notes TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS plant_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plant_id INTEGER NOT NULL, + year INTEGER NOT NULL, + blooming_date DATE, + fruiting_date DATE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS harvest_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plant_id INTEGER NOT NULL, + date DATE NOT NULL, + quantity REAL NOT NULL, + unit TEXT NOT NULL, + notes TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS maintenance_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plant_id INTEGER NOT NULL, + date DATE NOT NULL, + type TEXT NOT NULL CHECK(type IN ('chemical', 'fertilizer', 'watering', 'pruning', 'transplanting', 'other')), + description TEXT NOT NULL, + amount TEXT, + is_planned BOOLEAN DEFAULT 0, + is_completed BOOLEAN DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS tasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plant_id INTEGER, + title TEXT NOT NULL, + description TEXT, + deadline DATE NOT NULL, + completed BOOLEAN DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE SET NULL +); +`); + +console.log('✅ Таблицы успешно созданы'); + +// Вставка тестовых данных +const insertPlant = db.prepare(` + INSERT INTO plants (purchase_location, seedling_age, type, variety, seedling_height, planting_date, current_height, health_status, photo_url, current_photo_url, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +`); + +insertPlant.run('Local Nursery', 12, 'tree', 'Apple - Honeycrisp', 45, '2022-04-15', 180, 'good', null, null, 'Growing well'); +insertPlant.run('Garden Center', 8, 'shrub', 'Blueberry - Bluecrop', 25, '2023-03-20', 85, 'needs-attention', null, null, 'Needs fertilizer'); +insertPlant.run('Online Store', 3, 'herb', 'Basil - Sweet Genovese', 8, '2024-05-10', 35, 'good', null, null, 'Harvested weekly'); + +const insertTask = db.prepare(` + INSERT INTO tasks (plant_id, title, description, deadline, completed) + VALUES (?, ?, ?, ?, ?) +`); + +insertTask.run(1, 'Apply fertilizer', 'Spring fertilizer for apple trees', '2024-03-15', 0); +insertTask.run(2, 'Prune blueberries', 'Spring pruning', '2024-02-28', 1); +insertTask.run(3, 'Harvest basil', 'Every 2 weeks', '2024-06-01', 0); + +console.log('✅ Тестовые данные успешно вставлены'); +console.log('📦 База данных готова'); diff --git a/backend/scripts/init-db.js b/backend/scripts/init-db.js index 02cf12c..8f2c1d5 100644 --- a/backend/scripts/init-db.js +++ b/backend/scripts/init-db.js @@ -1,4 +1,4 @@ -const sqlite3 = require('sqlite3').verbose(); +const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); @@ -10,20 +10,19 @@ if (!fs.existsSync(DB_DIR)) { fs.mkdirSync(DB_DIR, { recursive: true }); } -const db = new sqlite3.Database(DB_PATH, (err) => { - if (err) { - console.log(DB_PATH); - console.error('Error creating database:', err.message); - process.exit(1); - } else { - console.log('Connected to SQLite database'); - } -}); +let db; +try { + db = new Database(DB_PATH); + console.log('Connected to SQLite database'); +} catch (err) { + console.error('Error creating database:', err.message); + process.exit(1); +} // Create tables -db.serialize(() => { +try { // Plants table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS plants ( id INTEGER PRIMARY KEY AUTOINCREMENT, purchase_location TEXT, @@ -43,7 +42,7 @@ db.serialize(() => { `); // Plant history table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS plant_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER NOT NULL, @@ -57,7 +56,7 @@ db.serialize(() => { `); // Harvest records table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS harvest_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER NOT NULL, @@ -72,7 +71,7 @@ db.serialize(() => { `); // Maintenance records table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS maintenance_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER NOT NULL, @@ -87,7 +86,7 @@ db.serialize(() => { `); // Tasks table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER, @@ -105,38 +104,62 @@ db.serialize(() => { // Insert sample data console.log('Inserting sample data...'); - + // Sample plants - const plantSql = `INSERT INTO plants (purchase_location, seedling_age, type, variety, seedling_height, planting_date, current_height, health_status, photo_url, current_photo_url, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; - db.run(plantSql, ['Local Nursery', 12, 'tree', 'Apple - Honeycrisp', 45.0, '2022-04-15', 180.0, 'good', null, null, 'Growing well, good fruit production']); - db.run(plantSql, ['Garden Center', 8, 'shrub', 'Blueberry - Bluecrop', 25.0, '2023-03-20', 85.0, 'needs-attention', null, null, 'Leaves showing slight discoloration']); - db.run(plantSql, ['Online Store', 3, 'herb', 'Basil - Sweet Genovese', 8.0, '2024-05-10', 35.0, 'good', null, null, 'Producing well, regular harvests']); + const insertPlant = db.prepare(` + INSERT INTO plants (purchase_location, seedling_age, type, variety, seedling_height, planting_date, current_height, health_status, photo_url, current_photo_url, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + insertPlant.run('Local Nursery', 12, 'tree', 'Apple - Honeycrisp', 45.0, '2022-04-15', 180.0, 'good', null, null, 'Growing well, good fruit production'); + insertPlant.run('Garden Center', 8, 'shrub', 'Blueberry - Bluecrop', 25.0, '2023-03-20', 85.0, 'needs-attention', null, null, 'Leaves showing slight discoloration'); + insertPlant.run('Online Store', 3, 'herb', 'Basil - Sweet Genovese', 8.0, '2024-05-10', 35.0, 'good', null, null, 'Producing well, regular harvests'); // Sample tasks - const taskSql = `INSERT INTO tasks (plant_id, title, description, deadline, completed) VALUES (?, ?, ?, ?, ?)`; - db.run(taskSql, [1, 'Apply fertilizer', 'Apply spring fertilizer to apple trees', '2024-03-15', 0]); - db.run(taskSql, [2, 'Prune blueberry bushes', 'Annual pruning before spring growth', '2024-02-28', 1]); - db.run(taskSql, [3, 'Harvest basil', 'Regular harvest to encourage growth', '2024-06-01', 0]); + const insertTask = db.prepare(` + INSERT INTO tasks (plant_id, title, description, deadline, completed) + VALUES (?, ?, ?, ?, ?) + `); + + insertTask.run(1, 'Apply fertilizer', 'Apply spring fertilizer to apple trees', '2024-03-15', 0); + insertTask.run(2, 'Prune blueberry bushes', 'Annual pruning before spring growth', '2024-02-28', 1); + insertTask.run(3, 'Harvest basil', 'Regular harvest to encourage growth', '2024-06-01', 0); // Sample maintenance records - const maintenanceSql = `INSERT INTO maintenance_records (plant_id, date, type, description, amount) VALUES (?, ?, ?, ?, ?)`; - db.run(maintenanceSql, [1, '2024-01-10', 'pruning', 'Winter pruning - removed dead branches', null]); - db.run(maintenanceSql, [2, '2024-01-05', 'fertilizer', 'Applied organic compost', '2 cups']); - db.run(maintenanceSql, [3, '2024-05-15', 'watering', 'Deep watering during dry spell', '1 gallon']); + const insertMaintenance = db.prepare(` + INSERT INTO maintenance_records (plant_id, date, type, description, amount) + VALUES (?, ?, ?, ?, ?) + `); + + insertMaintenance.run(1, '2024-01-10', 'pruning', 'Winter pruning - removed dead branches', null); + insertMaintenance.run(2, '2024-01-05', 'fertilizer', 'Applied organic compost', '2 cups'); + insertMaintenance.run(3, '2024-05-15', 'watering', 'Deep watering during dry spell', '1 gallon'); // Sample harvest records - const harvestSql = `INSERT INTO harvest_records (plant_id, date, quantity, unit, notes) VALUES (?, ?, ?, ?, ?)`; - db.run(harvestSql, [1, '2023-09-15', 25, 'lbs', 'Excellent harvest, apples were sweet and crisp']); - db.run(harvestSql, [2, '2023-07-20', 3, 'cups', 'First harvest of the season, berries were plump and sweet']); - db.run(harvestSql, [3, '2024-05-25', 0.5, 'cups', 'Fresh basil for cooking, very aromatic']); + const insertHarvest = db.prepare(` + INSERT INTO harvest_records (plant_id, date, quantity, unit, notes) + VALUES (?, ?, ?, ?, ?) + `); + + insertHarvest.run(1, '2023-09-15', 25, 'lbs', 'Excellent harvest, apples were sweet and crisp'); + insertHarvest.run(2, '2023-07-20', 3, 'cups', 'First harvest of the season, berries were plump and sweet'); + insertHarvest.run(3, '2024-05-25', 0.5, 'cups', 'Fresh basil for cooking, very aromatic'); + + // Sample observations + const insertObservation = db.prepare(` + INSERT INTO plant_observations (plant_id, date, title, observation, weather_conditions, temperature, tags) + VALUES (?, ?, ?, ?, ?, ?, ?) + `); + + insertObservation.run(1, '2024-01-15', 'Winter dormancy check', 'Apple tree showing normal winter dormancy. Buds are tight and healthy looking. No signs of pest damage on bark.', 'Clear, cold', 2.5, 'dormancy,health-check,winter'); + insertObservation.run(2, '2024-01-20', 'Pruning completed', 'Finished annual pruning of blueberry bushes. Removed about 20% of old wood and opened up center for better air circulation.', 'Overcast', 8.0, 'pruning,maintenance'); + insertObservation.run(3, '2024-05-12', 'First true leaves', 'Basil seedlings showing first set of true leaves. Growth is vigorous and color is deep green. Ready for transplanting soon.', 'Sunny', 22.0, 'growth,seedling,transplant-ready'); console.log('Sample data inserted successfully!'); -}); +} catch (err) { + console.error('Error initializing database:', err); + process.exit(1); +} -db.close((err) => { - if (err) { - console.error('Error closing database:', err.message); - } else { - console.log('Database initialization completed!'); - } -}); \ No newline at end of file +db.close(); +console.log('Database initialization completed!'); \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index de3a4bd..e703afe 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,7 +1,7 @@ const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); -const sqlite3 = require('sqlite3').verbose(); +const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); require('dotenv').config(); @@ -23,23 +23,23 @@ app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); // Database connection and initialization -const db = new sqlite3.Database(DB_PATH, (err) => { - if (err) { - console.error('Error opening database:', err.message); - process.exit(1); - } else { - console.log('Connected to SQLite database at:', DB_PATH); - initializeDatabase(); - } -}); +let db; +try { + db = new Database(DB_PATH); + console.log('Connected to SQLite database at:', DB_PATH); + initializeDatabase(); +} catch (err) { + console.error('Error opening database:', err.message); + process.exit(1); +} // Initialize database tables function initializeDatabase() { console.log('Initializing database tables...'); - db.serialize(() => { + try { // Plants table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS plants ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, @@ -56,13 +56,30 @@ function initializeDatabase() { created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) - `, (err) => { - if (err) console.error('Error creating plants table:', err); - else console.log('Plants table ready'); - }); + `); + console.log('Plants table ready'); + + // Plant observations table + db.exec(` + CREATE TABLE IF NOT EXISTS plant_observations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plant_id INTEGER NOT NULL, + date DATE NOT NULL, + title TEXT NOT NULL, + observation TEXT NOT NULL, + weather_conditions TEXT, + temperature REAL, + photo_url TEXT, + tags TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE + ) + `); + console.log('Plant observations table ready'); // Plant history table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS plant_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER NOT NULL, @@ -76,13 +93,11 @@ function initializeDatabase() { updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE ) - `, (err) => { - if (err) console.error('Error creating plant_history table:', err); - else console.log('Plant history table ready'); - }); + `); + console.log('Plant history table ready'); // Harvest records table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS harvest_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER NOT NULL, @@ -94,13 +109,11 @@ function initializeDatabase() { updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE ) - `, (err) => { - if (err) console.error('Error creating harvest_records table:', err); - else console.log('Harvest records table ready'); - }); + `); + console.log('Harvest records table ready'); // Maintenance records table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS maintenance_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER NOT NULL, @@ -114,13 +127,11 @@ function initializeDatabase() { updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE CASCADE ) - `, (err) => { - if (err) console.error('Error creating maintenance_records table:', err); - else console.log('Maintenance records table ready'); - }); + `); + console.log('Maintenance records table ready'); // Tasks table - db.run(` + db.exec(` CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, plant_id INTEGER, @@ -132,67 +143,189 @@ function initializeDatabase() { updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (plant_id) REFERENCES plants (id) ON DELETE SET NULL ) - `, (err) => { - if (err) console.error('Error creating tasks table:', err); - else console.log('Tasks table ready'); - }); + `); + console.log('Tasks table ready'); // Insert sample data if tables are empty - db.get('SELECT COUNT(*) as count FROM plants', (err, row) => { - if (err) { - console.error('Error checking plants table:', err); - return; - } - - if (row.count === 0) { - console.log('Inserting sample data...'); - insertSampleData(); - } else { - console.log('Database already contains data'); - } - }); - }); + const plantCount = db.prepare('SELECT COUNT(*) as count FROM plants').get(); + + if (plantCount.count === 0) { + console.log('Inserting sample data...'); + insertSampleData(); + } else { + console.log('Database already contains data'); + } + } catch (err) { + console.error('Error initializing database:', err); + process.exit(1); + } } // Insert sample data function insertSampleData() { - // Sample plants - const plantSql = `INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`; - db.run(plantSql, ['tree', 'Apple - Honeycrisp', 'Local Nursery', 12, 45.5, '2022-04-15', 'good', 180.2, 'Growing well, good fruit production']); - db.run(plantSql, ['shrub', 'Blueberry - Bluecrop', 'Garden Center', 8, 25.0, '2023-03-20', 'needs-attention', 65.8, 'Leaves showing slight discoloration']); - db.run(plantSql, ['herb', 'Basil - Sweet Genovese', 'Online Store', 3, 8.2, '2024-05-10', 'good', 22.5, 'Producing well, regular harvests']); + try { + // Sample plants + const insertPlant = db.prepare(` + INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + insertPlant.run('tree', 'Apple - Honeycrisp', 'Local Nursery', 12, 45.5, '2022-04-15', 'good', 180.2, 'Growing well, good fruit production'); + insertPlant.run('shrub', 'Blueberry - Bluecrop', 'Garden Center', 8, 25.0, '2023-03-20', 'needs-attention', 65.8, 'Leaves showing slight discoloration'); + insertPlant.run('herb', 'Basil - Sweet Genovese', 'Online Store', 3, 8.2, '2024-05-10', 'good', 22.5, 'Producing well, regular harvests'); - // Sample tasks - const taskSql = `INSERT INTO tasks (plant_id, title, description, deadline, completed) VALUES (?, ?, ?, ?, ?)`; - db.run(taskSql, [1, 'Apply fertilizer', 'Apply spring fertilizer to apple trees', '2024-03-15', 0]); - db.run(taskSql, [2, 'Prune blueberry bushes', 'Annual pruning before spring growth', '2024-02-28', 1]); - db.run(taskSql, [3, 'Harvest basil', 'Regular harvest to encourage growth', '2024-06-01', 0]); + // Sample tasks + const insertTask = db.prepare(` + INSERT INTO tasks (plant_id, title, description, deadline, completed) + VALUES (?, ?, ?, ?, ?) + `); + + insertTask.run(1, 'Apply fertilizer', 'Apply spring fertilizer to apple trees', '2024-03-15', 0); + insertTask.run(2, 'Prune blueberry bushes', 'Annual pruning before spring growth', '2024-02-28', 1); + insertTask.run(3, 'Harvest basil', 'Regular harvest to encourage growth', '2024-06-01', 0); - // Sample maintenance records - const maintenanceSql = `INSERT INTO maintenance_records (plant_id, date, type, description, amount, is_planned, is_completed) VALUES (?, ?, ?, ?, ?, ?, ?)`; - db.run(maintenanceSql, [1, '2024-01-10', 'pruning', 'Winter pruning - removed dead branches', null, 0, 1]); - db.run(maintenanceSql, [2, '2024-01-05', 'fertilizer', 'Applied organic compost', '2 cups', 0, 1]); - db.run(maintenanceSql, [3, '2024-05-15', 'watering', 'Deep watering during dry spell', '1 gallon', 0, 1]); + // Sample maintenance records + const insertMaintenance = db.prepare(` + INSERT INTO maintenance_records (plant_id, date, type, description, amount, is_planned, is_completed) + VALUES (?, ?, ?, ?, ?, ?, ?) + `); + + insertMaintenance.run(1, '2024-01-10', 'pruning', 'Winter pruning - removed dead branches', null, 0, 1); + insertMaintenance.run(2, '2024-01-05', 'fertilizer', 'Applied organic compost', '2 cups', 0, 1); + insertMaintenance.run(3, '2024-05-15', 'watering', 'Deep watering during dry spell', '1 gallon', 0, 1); - // Sample harvest records - const harvestSql = `INSERT INTO harvest_records (plant_id, date, quantity, unit, notes) VALUES (?, ?, ?, ?, ?)`; - db.run(harvestSql, [1, '2023-09-15', 25, 'lbs', 'Excellent harvest, apples were sweet and crisp']); - db.run(harvestSql, [2, '2023-07-20', 3, 'cups', 'First harvest of the season, berries were plump and sweet']); - db.run(harvestSql, [3, '2024-05-25', 0.5, 'cups', 'Fresh basil for cooking, very aromatic']); + // Sample harvest records + const insertHarvest = db.prepare(` + INSERT INTO harvest_records (plant_id, date, quantity, unit, notes) + VALUES (?, ?, ?, ?, ?) + `); + + insertHarvest.run(1, '2023-09-15', 25, 'lbs', 'Excellent harvest, apples were sweet and crisp'); + insertHarvest.run(2, '2023-07-20', 3, 'cups', 'First harvest of the season, berries were plump and sweet'); + insertHarvest.run(3, '2024-05-25', 0.5, 'cups', 'Fresh basil for cooking, very aromatic'); - console.log('Sample data inserted successfully!'); + // Sample observations + const insertObservation = db.prepare(` + INSERT INTO plant_observations (plant_id, date, title, observation, weather_conditions, temperature, tags) + VALUES (?, ?, ?, ?, ?, ?, ?) + `); + + insertObservation.run(1, '2024-01-15', 'Winter dormancy check', 'Apple tree showing normal winter dormancy. Buds are tight and healthy looking. No signs of pest damage on bark.', 'Clear, cold', 2.5, 'dormancy,health-check,winter'); + insertObservation.run(2, '2024-01-20', 'Pruning completed', 'Finished annual pruning of blueberry bushes. Removed about 20% of old wood and opened up center for better air circulation.', 'Overcast', 8.0, 'pruning,maintenance'); + insertObservation.run(3, '2024-05-12', 'First true leaves', 'Basil seedlings showing first set of true leaves. Growth is vigorous and color is deep green. Ready for transplanting soon.', 'Sunny', 22.0, 'growth,seedling,transplant-ready'); + console.log('Sample data inserted successfully!'); + } catch (err) { + console.error('Error inserting sample data:', err); + } } // Routes +// Plant Observations +app.get('/api/observations', (req, res) => { + try { + const stmt = db.prepare('SELECT * FROM plant_observations ORDER BY date DESC'); + const rows = stmt.all(); + + res.json(rows.map(row => ({ + id: row.id, + plantId: row.plant_id, + date: row.date, + title: row.title, + observation: row.observation, + weatherConditions: row.weather_conditions, + temperature: row.temperature, + photoUrl: row.photo_url, + tags: row.tags, + createdAt: row.created_at, + updatedAt: row.updated_at + }))); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +app.post('/api/observations', (req, res) => { + try { + const { plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags } = req.body; + + const stmt = db.prepare(` + INSERT INTO plant_observations (plant_id, date, title, observation, weather_conditions, temperature, photo_url, tags) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `); + + const result = stmt.run(plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags); + + const getStmt = db.prepare('SELECT * FROM plant_observations WHERE id = ?'); + const row = getStmt.get(result.lastInsertRowid); + + res.json({ + id: row.id, + plantId: row.plant_id, + date: row.date, + title: row.title, + observation: row.observation, + weatherConditions: row.weather_conditions, + temperature: row.temperature, + photoUrl: row.photo_url, + tags: row.tags, + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +app.put('/api/observations/:id', (req, res) => { + try { + const { plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags } = req.body; + + const stmt = db.prepare(` + UPDATE plant_observations SET plant_id = ?, date = ?, title = ?, observation = ?, + weather_conditions = ?, temperature = ?, photo_url = ?, tags = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + `); + + stmt.run(plantId, date, title, observation, weatherConditions, temperature, photoUrl, tags, req.params.id); + + const getStmt = db.prepare('SELECT * FROM plant_observations WHERE id = ?'); + const row = getStmt.get(req.params.id); + + res.json({ + id: row.id, + plantId: row.plant_id, + date: row.date, + title: row.title, + observation: row.observation, + weatherConditions: row.weather_conditions, + temperature: row.temperature, + photoUrl: row.photo_url, + tags: row.tags, + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +app.delete('/api/observations/:id', (req, res) => { + try { + const stmt = db.prepare('DELETE FROM plant_observations WHERE id = ?'); + stmt.run(req.params.id); + res.json({ message: 'Observation deleted successfully' }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + // Plants app.get('/api/plants', (req, res) => { - const sql = 'SELECT * FROM plants ORDER BY created_at DESC'; - db.all(sql, [], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } + try { + const stmt = db.prepare('SELECT * FROM plants ORDER BY created_at DESC'); + const rows = stmt.all(); + res.json(rows.map(row => ({ id: row.id, type: row.type, @@ -209,311 +342,26 @@ app.get('/api/plants', (req, res) => { createdAt: row.created_at, updatedAt: row.updated_at }))); - }); + } catch (err) { + res.status(500).json({ error: err.message }); + } }); app.post('/api/plants', (req, res) => { - const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body; - const sql = `INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, photo_url, notes) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; - - db.run(sql, [type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes], function(err) { - if (err) { - res.status(400).json({ error: err.message }); - return; - } + try { + const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body; + + const stmt = db.prepare(` + INSERT INTO plants (type, variety, purchase_location, seedling_age, seedling_height, planting_date, health_status, current_height, photo_url, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const result = stmt.run(type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes); // Get the created plant - db.get('SELECT * FROM plants WHERE id = ?', [this.lastID], (err, row) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ - id: row.id, - type: row.type, - variety: row.variety, - purchaseLocation: row.purchase_location, - seedlingAge: row.seedling_age, - seedlingHeight: row.seedling_height, - plantingDate: row.planting_date, - healthStatus: row.health_status, - currentHeight: row.current_height, - photoUrl: row.photo_url, - currentPhotoUrl: row.current_photo_url, - notes: row.notes, - createdAt: row.created_at, - updatedAt: row.updated_at - }); - }); - }); -}); - -app.put('/api/plants/:id', (req, res) => { - const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body; - const sql = `UPDATE plants SET type = ?, variety = ?, purchase_location = ?, seedling_age = ?, seedling_height = ?, planting_date = ?, - health_status = ?, current_height = ?, photo_url = ?, notes = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = ?`; - - db.run(sql, [type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes, req.params.id], function(err) { - if (err) { - res.status(400).json({ error: err.message }); - return; - } + const getStmt = db.prepare('SELECT * FROM plants WHERE id = ?'); + const row = getStmt.get(result.lastInsertRowid); - // Get the updated plant - db.get('SELECT * FROM plants WHERE id = ?', [req.params.id], (err, row) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ - id: row.id, - type: row.type, - variety: row.variety, - purchaseLocation: row.purchase_location, - seedlingAge: row.seedling_age, - seedlingHeight: row.seedling_height, - plantingDate: row.planting_date, - healthStatus: row.health_status, - currentHeight: row.current_height, - photoUrl: row.photo_url, - currentPhotoUrl: row.current_photo_url, - notes: row.notes, - createdAt: row.created_at, - updatedAt: row.updated_at - }); - }); - }); -}); - -app.delete('/api/plants/:id', (req, res) => { - db.run('DELETE FROM plants WHERE id = ?', [req.params.id], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ message: 'Plant deleted successfully' }); - }); -}); - -// Tasks -app.get('/api/tasks', (req, res) => { - const sql = 'SELECT * FROM tasks ORDER BY deadline ASC'; - db.all(sql, [], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json(rows.map(row => ({ - id: row.id, - plantId: row.plant_id, - title: row.title, - description: row.description, - deadline: row.deadline, - completed: Boolean(row.completed), - createdAt: row.created_at, - updatedAt: row.updated_at - }))); - }); -}); - -app.post('/api/tasks', (req, res) => { - const { plantId, title, description, deadline, completed = false } = req.body; - const sql = `INSERT INTO tasks (plant_id, title, description, deadline, completed) - VALUES (?, ?, ?, ?, ?)`; - - db.run(sql, [plantId, title, description, deadline, completed ? 1 : 0], function(err) { - if (err) { - res.status(400).json({ error: err.message }); - return; - } - - db.get('SELECT * FROM tasks WHERE id = ?', [this.lastID], (err, row) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ - id: row.id, - plantId: row.plant_id, - title: row.title, - description: row.description, - deadline: row.deadline, - completed: Boolean(row.completed), - createdAt: row.created_at, - updatedAt: row.updated_at - }); - }); - }); -}); - -app.put('/api/tasks/:id', (req, res) => { - const { plantId, title, description, deadline, completed } = req.body; - const sql = `UPDATE tasks SET plant_id = ?, title = ?, description = ?, - deadline = ?, completed = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = ?`; - - db.run(sql, [plantId, title, description, deadline, completed ? 1 : 0, req.params.id], function(err) { - if (err) { - res.status(400).json({ error: err.message }); - return; - } - - db.get('SELECT * FROM tasks WHERE id = ?', [req.params.id], (err, row) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ - id: row.id, - plantId: row.plant_id, - title: row.title, - description: row.description, - deadline: row.deadline, - completed: Boolean(row.completed), - createdAt: row.created_at, - updatedAt: row.updated_at - }); - }); - }); -}); - -app.delete('/api/tasks/:id', (req, res) => { - db.run('DELETE FROM tasks WHERE id = ?', [req.params.id], function(err) { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ message: 'Task deleted successfully' }); - }); -}); - -// Maintenance Records -app.get('/api/maintenance', (req, res) => { - const sql = 'SELECT * FROM maintenance_records ORDER BY date DESC'; - db.all(sql, [], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json(rows.map(row => ({ - id: row.id, - plantId: row.plant_id, - date: row.date, - type: row.type, - description: row.description, - amount: row.amount, - isPlanned: Boolean(row.is_planned), - isCompleted: Boolean(row.is_completed), - createdAt: row.created_at, - updatedAt: row.updated_at - }))); - }); -}); - -app.post('/api/maintenance', (req, res) => { - const { plantId, date, type, description, amount, isPlanned, isCompleted } = req.body; - const sql = `INSERT INTO maintenance_records (plant_id, date, type, description, amount, is_planned, is_completed) - VALUES (?, ?, ?, ?, ?, ?, ?)`; - - db.run(sql, [plantId, date, type, description, amount, isPlanned ? 1 : 0, isCompleted ? 1 : 0], function(err) { - if (err) { - res.status(400).json({ error: err.message }); - return; - } - - db.get('SELECT * FROM maintenance_records WHERE id = ?', [this.lastID], (err, row) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ - id: row.id, - plantId: row.plant_id, - date: row.date, - type: row.type, - description: row.description, - amount: row.amount, - isPlanned: Boolean(row.is_planned), - isCompleted: Boolean(row.is_completed), - createdAt: row.created_at, - updatedAt: row.updated_at - }); - }); - }); -}); - -// Harvest Records -app.get('/api/harvests', (req, res) => { - const sql = 'SELECT * FROM harvest_records ORDER BY date DESC'; - db.all(sql, [], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json(rows.map(row => ({ - id: row.id, - plantId: row.plant_id, - date: row.date, - quantity: row.quantity, - unit: row.unit, - notes: row.notes, - createdAt: row.created_at, - updatedAt: row.updated_at - }))); - }); -}); - -app.post('/api/harvests', (req, res) => { - const { plantId, date, quantity, unit, notes } = req.body; - const sql = `INSERT INTO harvest_records (plant_id, date, quantity, unit, notes) - VALUES (?, ?, ?, ?, ?)`; - - db.run(sql, [plantId, date, quantity, unit, notes], function(err) { - if (err) { - res.status(400).json({ error: err.message }); - return; - } - - db.get('SELECT * FROM harvest_records WHERE id = ?', [this.lastID], (err, row) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - res.json({ - id: row.id, - plantId: row.plant_id, - date: row.date, - quantity: row.quantity, - unit: row.unit, - notes: row.notes, - createdAt: row.created_at, - updatedAt: row.updated_at - }); - }); - }); -}); - -// Health check -app.get('/api/health', (req, res) => { - res.json({ status: 'OK', message: 'GardenTrack API is running' }); -}); - -// Additional endpoints for enhanced functionality - -// Get plant by ID -app.get('/api/plants/:id', (req, res) => { - const sql = 'SELECT * FROM plants WHERE id = ?'; - db.get(sql, [req.params.id], (err, row) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - if (!row) { - res.status(404).json({ error: 'Plant not found' }); - return; - } res.json({ id: row.id, type: row.type, @@ -530,25 +378,306 @@ app.get('/api/plants/:id', (req, res) => { createdAt: row.created_at, updatedAt: row.updated_at }); - }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +app.put('/api/plants/:id', (req, res) => { + try { + const { type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes } = req.body; + + const stmt = db.prepare(` + UPDATE plants SET type = ?, variety = ?, purchase_location = ?, seedling_age = ?, seedling_height = ?, planting_date = ?, + health_status = ?, current_height = ?, photo_url = ?, notes = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + `); + + stmt.run(type, variety, purchaseLocation, seedlingAge, seedlingHeight, plantingDate, healthStatus, currentHeight, photoUrl, notes, req.params.id); + + // Get the updated plant + const getStmt = db.prepare('SELECT * FROM plants WHERE id = ?'); + const row = getStmt.get(req.params.id); + + res.json({ + id: row.id, + type: row.type, + variety: row.variety, + purchaseLocation: row.purchase_location, + seedlingAge: row.seedling_age, + seedlingHeight: row.seedling_height, + plantingDate: row.planting_date, + healthStatus: row.health_status, + currentHeight: row.current_height, + photoUrl: row.photo_url, + currentPhotoUrl: row.current_photo_url, + notes: row.notes, + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +app.delete('/api/plants/:id', (req, res) => { + try { + const stmt = db.prepare('DELETE FROM plants WHERE id = ?'); + stmt.run(req.params.id); + res.json({ message: 'Plant deleted successfully' }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Tasks +app.get('/api/tasks', (req, res) => { + try { + const stmt = db.prepare('SELECT * FROM tasks ORDER BY deadline ASC'); + const rows = stmt.all(); + + res.json(rows.map(row => ({ + id: row.id, + plantId: row.plant_id, + title: row.title, + description: row.description, + deadline: row.deadline, + completed: Boolean(row.completed), + createdAt: row.created_at, + updatedAt: row.updated_at + }))); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +app.post('/api/tasks', (req, res) => { + try { + const { plantId, title, description, deadline, completed = false } = req.body; + + const stmt = db.prepare(` + INSERT INTO tasks (plant_id, title, description, deadline, completed) + VALUES (?, ?, ?, ?, ?) + `); + + const result = stmt.run(plantId, title, description, deadline, completed ? 1 : 0); + + const getStmt = db.prepare('SELECT * FROM tasks WHERE id = ?'); + const row = getStmt.get(result.lastInsertRowid); + + res.json({ + id: row.id, + plantId: row.plant_id, + title: row.title, + description: row.description, + deadline: row.deadline, + completed: Boolean(row.completed), + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +app.put('/api/tasks/:id', (req, res) => { + try { + const { plantId, title, description, deadline, completed } = req.body; + + const stmt = db.prepare(` + UPDATE tasks SET plant_id = ?, title = ?, description = ?, + deadline = ?, completed = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + `); + + stmt.run(plantId, title, description, deadline, completed ? 1 : 0, req.params.id); + + const getStmt = db.prepare('SELECT * FROM tasks WHERE id = ?'); + const row = getStmt.get(req.params.id); + + res.json({ + id: row.id, + plantId: row.plant_id, + title: row.title, + description: row.description, + deadline: row.deadline, + completed: Boolean(row.completed), + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +app.delete('/api/tasks/:id', (req, res) => { + try { + const stmt = db.prepare('DELETE FROM tasks WHERE id = ?'); + stmt.run(req.params.id); + res.json({ message: 'Task deleted successfully' }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Maintenance Records +app.get('/api/maintenance', (req, res) => { + try { + const stmt = db.prepare('SELECT * FROM maintenance_records ORDER BY date DESC'); + const rows = stmt.all(); + + res.json(rows.map(row => ({ + id: row.id, + plantId: row.plant_id, + date: row.date, + type: row.type, + description: row.description, + amount: row.amount, + isPlanned: Boolean(row.is_planned), + isCompleted: Boolean(row.is_completed), + createdAt: row.created_at, + updatedAt: row.updated_at + }))); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +app.post('/api/maintenance', (req, res) => { + try { + const { plantId, date, type, description, amount, isPlanned, isCompleted } = req.body; + + const stmt = db.prepare(` + INSERT INTO maintenance_records (plant_id, date, type, description, amount, is_planned, is_completed) + VALUES (?, ?, ?, ?, ?, ?, ?) + `); + + const result = stmt.run(plantId, date, type, description, amount, isPlanned ? 1 : 0, isCompleted ? 1 : 0); + + const getStmt = db.prepare('SELECT * FROM maintenance_records WHERE id = ?'); + const row = getStmt.get(result.lastInsertRowid); + + res.json({ + id: row.id, + plantId: row.plant_id, + date: row.date, + type: row.type, + description: row.description, + amount: row.amount, + isPlanned: Boolean(row.is_planned), + isCompleted: Boolean(row.is_completed), + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +// Harvest Records +app.get('/api/harvests', (req, res) => { + try { + const stmt = db.prepare('SELECT * FROM harvest_records ORDER BY date DESC'); + const rows = stmt.all(); + + res.json(rows.map(row => ({ + id: row.id, + plantId: row.plant_id, + date: row.date, + quantity: row.quantity, + unit: row.unit, + notes: row.notes, + createdAt: row.created_at, + updatedAt: row.updated_at + }))); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +app.post('/api/harvests', (req, res) => { + try { + const { plantId, date, quantity, unit, notes } = req.body; + + const stmt = db.prepare(` + INSERT INTO harvest_records (plant_id, date, quantity, unit, notes) + VALUES (?, ?, ?, ?, ?) + `); + + const result = stmt.run(plantId, date, quantity, unit, notes); + + const getStmt = db.prepare('SELECT * FROM harvest_records WHERE id = ?'); + const row = getStmt.get(result.lastInsertRowid); + + res.json({ + id: row.id, + plantId: row.plant_id, + date: row.date, + quantity: row.quantity, + unit: row.unit, + notes: row.notes, + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +// Health check +app.get('/api/health', (req, res) => { + res.json({ status: 'OK', message: 'GardenTrack API is running' }); +}); + +// Additional endpoints for enhanced functionality + +// Get plant by ID +app.get('/api/plants/:id', (req, res) => { + try { + const stmt = db.prepare('SELECT * FROM plants WHERE id = ?'); + const row = stmt.get(req.params.id); + + if (!row) { + res.status(404).json({ error: 'Plant not found' }); + return; + } + + res.json({ + id: row.id, + type: row.type, + variety: row.variety, + purchaseLocation: row.purchase_location, + seedlingAge: row.seedling_age, + seedlingHeight: row.seedling_height, + plantingDate: row.planting_date, + healthStatus: row.health_status, + currentHeight: row.current_height, + photoUrl: row.photo_url, + currentPhotoUrl: row.current_photo_url, + notes: row.notes, + createdAt: row.created_at, + updatedAt: row.updated_at + }); + } catch (err) { + res.status(500).json({ error: err.message }); + } }); // Search plants app.get('/api/plants/search', (req, res) => { - const query = req.query.q; - if (!query) { - res.status(400).json({ error: 'Search query is required' }); - return; - } - - const sql = `SELECT * FROM plants WHERE variety LIKE ? OR type LIKE ? OR notes LIKE ? ORDER BY created_at DESC`; - const searchTerm = `%${query}%`; - - db.all(sql, [searchTerm, searchTerm, searchTerm], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); + try { + const query = req.query.q; + if (!query) { + res.status(400).json({ error: 'Search query is required' }); return; } + + const stmt = db.prepare(` + SELECT * FROM plants WHERE variety LIKE ? OR type LIKE ? OR notes LIKE ? ORDER BY created_at DESC + `); + const searchTerm = `%${query}%`; + const rows = stmt.all(searchTerm, searchTerm, searchTerm); + res.json(rows.map(row => ({ id: row.id, type: row.type, @@ -565,21 +694,21 @@ app.get('/api/plants/search', (req, res) => { createdAt: row.created_at, updatedAt: row.updated_at }))); - }); + } catch (err) { + res.status(500).json({ error: err.message }); + } }); // Get upcoming tasks app.get('/api/tasks/upcoming', (req, res) => { - const days = parseInt(req.query.days) || 7; - const endDate = new Date(); - endDate.setDate(endDate.getDate() + days); - - const sql = 'SELECT * FROM tasks WHERE completed = 0 AND deadline <= ? ORDER BY deadline ASC'; - db.all(sql, [endDate.toISOString().split('T')[0]], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } + try { + const days = parseInt(req.query.days) || 7; + const endDate = new Date(); + endDate.setDate(endDate.getDate() + days); + + const stmt = db.prepare('SELECT * FROM tasks WHERE completed = 0 AND deadline <= ? ORDER BY deadline ASC'); + const rows = stmt.all(endDate.toISOString().split('T')[0]); + res.json(rows.map(row => ({ id: row.id, plantId: row.plant_id, @@ -590,19 +719,18 @@ app.get('/api/tasks/upcoming', (req, res) => { createdAt: row.created_at, updatedAt: row.updated_at }))); - }); + } catch (err) { + res.status(500).json({ error: err.message }); + } }); // Get overdue tasks app.get('/api/tasks/overdue', (req, res) => { - const today = new Date().toISOString().split('T')[0]; - const sql = 'SELECT * FROM tasks WHERE completed = 0 AND deadline < ? ORDER BY deadline ASC'; - - db.all(sql, [today], (err, rows) => { - if (err) { - res.status(500).json({ error: err.message }); - return; - } + try { + const today = new Date().toISOString().split('T')[0]; + const stmt = db.prepare('SELECT * FROM tasks WHERE completed = 0 AND deadline < ? ORDER BY deadline ASC'); + const rows = stmt.all(today); + res.json(rows.map(row => ({ id: row.id, plantId: row.plant_id, @@ -613,41 +741,29 @@ app.get('/api/tasks/overdue', (req, res) => { createdAt: row.created_at, updatedAt: row.updated_at }))); - }); + } catch (err) { + res.status(500).json({ error: err.message }); + } }); // Dashboard summary app.get('/api/dashboard/summary', (req, res) => { - const queries = { - totalPlants: 'SELECT COUNT(*) as count FROM plants', - healthyPlants: 'SELECT COUNT(*) as count FROM plants WHERE health_status = "good"', - plantsNeedingAttention: 'SELECT COUNT(*) as count FROM plants WHERE health_status = "needs-attention"', - totalTasks: 'SELECT COUNT(*) as count FROM tasks', - completedTasks: 'SELECT COUNT(*) as count FROM tasks WHERE completed = 1', - upcomingTasks: 'SELECT COUNT(*) as count FROM tasks WHERE completed = 0 AND deadline >= date("now")', - totalHarvests: 'SELECT COUNT(*) as count FROM harvest_records', - recentMaintenanceCount: 'SELECT COUNT(*) as count FROM maintenance_records WHERE date >= date("now", "-30 days")' - }; - - const results = {}; - const queryKeys = Object.keys(queries); - let completed = 0; - - queryKeys.forEach(key => { - db.get(queries[key], [], (err, row) => { - if (err) { - console.error(`Error in ${key} query:`, err); - results[key] = 0; - } else { - results[key] = row.count; - } - - completed++; - if (completed === queryKeys.length) { - res.json(results); - } - }); - }); + try { + const results = { + totalPlants: db.prepare('SELECT COUNT(*) as count FROM plants').get().count, + healthyPlants: db.prepare('SELECT COUNT(*) as count FROM plants WHERE health_status = "good"').get().count, + plantsNeedingAttention: db.prepare('SELECT COUNT(*) as count FROM plants WHERE health_status = "needs-attention"').get().count, + totalTasks: db.prepare('SELECT COUNT(*) as count FROM tasks').get().count, + completedTasks: db.prepare('SELECT COUNT(*) as count FROM tasks WHERE completed = 1').get().count, + upcomingTasks: db.prepare('SELECT COUNT(*) as count FROM tasks WHERE completed = 0 AND deadline >= date("now")').get().count, + totalHarvests: db.prepare('SELECT COUNT(*) as count FROM harvest_records').get().count, + recentMaintenanceCount: db.prepare('SELECT COUNT(*) as count FROM maintenance_records WHERE date >= date("now", "-30 days")').get().count + }; + + res.json(results); + } catch (err) { + res.status(500).json({ error: err.message }); + } }); // Error handling middleware @@ -663,11 +779,9 @@ app.listen(PORT, () => { // Graceful shutdown process.on('SIGINT', () => { - db.close((err) => { - if (err) { - console.error(err.message); - } + if (db) { + db.close(); console.log('Database connection closed.'); - process.exit(0); - }); + } + process.exit(0); }); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4bbb12b..672a3e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,18 +27,7 @@ services: volumes: - ./backend/database:/app/database - ./backend:/app - command: ["node", "server.js"] - - db-init: - build: - context: ./backend - dockerfile: Dockerfile - volumes: - - ./backend/database:/app/database - - ./backend:/app - command: ["node", "scripts/init-db.js"] - depends_on: - - backend + command: ["sh", "-c", "node scripts/init-db.js && node server.js"] volumes: db_data: \ No newline at end of file diff --git a/package.json b/package.json index a78ebae..9cac8f2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vite-react-typescript-starter", "private": true, - "version": "0.0.0", + "version": "0.1.0", "type": "module", "scripts": { "dev": "vite --mode development", diff --git a/src/App.tsx b/src/App.tsx index 574473d..329414b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,11 @@ -import { useState, useEffect } from 'react'; -import { Sprout, Calendar, CheckSquare, Activity, Plus } from 'lucide-react'; -import { Plant, Task, MaintenanceRecord, HarvestRecord } from './types'; +import React, { useState, useEffect } from 'react'; +import { Sprout, Calendar, CheckSquare, Activity, Plus, BookOpen } from 'lucide-react'; +import { Plant, Task, MaintenanceRecord, HarvestRecord, PlantObservation } from './types'; import Dashboard from './components/Dashboard'; import PlantRegistry from './components/PlantRegistry'; import TaskPlanner from './components/TaskPlanner'; import MaintenanceLog from './components/MaintenanceLog'; +import ObservationJournal from './components/ObservationJournal'; import { apiService } from './services/api'; function App() { @@ -13,6 +14,7 @@ function App() { const [tasks, setTasks] = useState([]); const [maintenanceRecords, setMaintenanceRecords] = useState([]); const [harvestRecords, setHarvestRecords] = useState([]); + const [observations, setObservations] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -26,17 +28,19 @@ function App() { // Test backend connection first await apiService.healthCheck(); - const [plantsData, tasksData, maintenanceData, harvestData] = await Promise.all([ + const [plantsData, tasksData, maintenanceData, harvestData, observationsData] = await Promise.all([ apiService.getPlants(), apiService.getTasks(), apiService.getMaintenanceRecords(), - apiService.getHarvestRecords() + apiService.getHarvestRecords(), + apiService.getPlantObservations() ]); setPlants(plantsData); setTasks(tasksData); setMaintenanceRecords(maintenanceData); setHarvestRecords(harvestData); + setObservations(observationsData); } catch (error) { console.error('Error loading data:', error); // You could add a toast notification or error state here @@ -47,10 +51,11 @@ function App() { }; const navItems = [ - { id: 'dashboard', label: 'Dashboard', icon: Activity }, - { id: 'plants', label: 'Plant Registry', icon: Sprout }, - { id: 'tasks', label: 'Task Planner', icon: CheckSquare }, - { id: 'maintenance', label: 'Maintenance Log', icon: Calendar }, + { id: 'dashboard', label: 'Панель', icon: Activity }, + { id: 'plants', label: 'Реестр растений', icon: Sprout }, + { id: 'tasks', label: 'План работ', icon: CheckSquare }, + { id: 'maintenance', label: 'Журнал', icon: Calendar }, + { id: 'observations', label: 'Дневник', icon: BookOpen }, ]; if (loading) { @@ -129,6 +134,13 @@ function App() { onMaintenanceChange={setMaintenanceRecords} /> )} + {activeTab === 'observations' && ( + + )} {/* Mobile Navigation */} diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index a73a4cd..d30efec 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -30,28 +30,28 @@ const Dashboard: React.FC = ({ const stats = [ { - label: 'Total Plants', + label: 'Всего растений', value: plants.length, icon: Sprout, color: 'text-green-600', bgColor: 'bg-green-100' }, { - label: 'Pending Tasks', + label: 'Отложенные работы', value: tasks.filter(task => !task.completed).length, icon: Calendar, color: 'text-blue-600', bgColor: 'bg-blue-100' }, { - label: 'Need Attention', + label: 'Требует внимания', value: plantsNeedingAttention.length, icon: AlertTriangle, color: 'text-yellow-600', bgColor: 'bg-yellow-100' }, { - label: 'This Year Harvests', + label: ' Урожай этого года', value: harvestRecords.filter(record => new Date(record.date).getFullYear() === new Date().getFullYear() ).length, @@ -101,12 +101,12 @@ const Dashboard: React.FC = ({
-

Upcoming Tasks

+

Предстоящие работы

@@ -125,7 +125,7 @@ const Dashboard: React.FC = ({

{plant.variety}

)}

- Due: {new Date(task.deadline).toLocaleDateString()} + Срок: {new Date(task.deadline).toLocaleDateString()}

@@ -133,7 +133,7 @@ const Dashboard: React.FC = ({ })} ) : ( -

No upcoming tasks

+

Никаких предстоящих работ нет

)} @@ -142,12 +142,12 @@ const Dashboard: React.FC = ({
-

Plants Needing Attention

+

Растения, требующие внимания

@@ -178,12 +178,12 @@ const Dashboard: React.FC = ({
-

Recent Maintenance

+

Недавнии работы

@@ -209,7 +209,7 @@ const Dashboard: React.FC = ({ })}
) : ( -

No maintenance records yet

+

Записей о выполненных работах пока нет

)}
diff --git a/src/components/MaintenanceForm.tsx b/src/components/MaintenanceForm.tsx index 48a2a01..a91dcba 100644 --- a/src/components/MaintenanceForm.tsx +++ b/src/components/MaintenanceForm.tsx @@ -42,7 +42,7 @@ const MaintenanceForm: React.FC = ({ plants, onSave, onCan
-

Log Maintenance Activity

+

Выполненная работа