diff --git a/package-lock.json b/package-lock.json index 19314de..d386378 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,24 +8,32 @@ "name": "campfirecritics", "version": "0.0.0", "dependencies": { + "@react-three/fiber": "^8.18.0", + "@react-three/postprocessing": "^2.19.1", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "chart.js": "^4.4.3", + "cors": "^2.8.5", "dompurify": "^3.2.5", + "express": "^5.1.0", "framer-motion": "^11.18.2", "gsap": "^3.13.0", + "node-telegram-bot-api": "^0.66.0", "pocketbase": "^0.21.3", + "postprocessing": "^6.37.3", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-chartjs-2": "^5.2.0", "react-datepicker": "^8.3.0", "react-dom": "^18.2.0", - "react-fast-marquee": "^1.6.4", + "react-fast-marquee": "^1.6.5", "react-hot-toast": "^2.5.2", "react-icons": "^5.5.0", "react-quill": "^2.0.0", "react-router-dom": "^6.23.1", "react-select": "^5.10.1", - "react-toastify": "^10.0.5", + "react-toastify": "^11.0.5", + "three": "^0.167.1", "uuid": "^11.1.0" }, "devDependencies": { @@ -349,6 +357,105 @@ "node": ">=6.9.0" } }, + "node_modules/@cypress/request": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz", + "integrity": "sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.0", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request-promise": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@cypress/request-promise/-/request-promise-5.0.0.tgz", + "integrity": "sha512-eKdYVpa9cBEw2kTBlHeu1PP16Blwtum6QHg/u9s/MoHkZfuo1pRGka1VlUHXF5kdew82BvOJVVGk0x8X0nbp+w==", + "license": "ISC", + "dependencies": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "@cypress/request": "^3.0.0" + } + }, + "node_modules/@cypress/request-promise/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@cypress/request/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@cypress/request/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@cypress/request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -1130,6 +1237,82 @@ "node": ">=14" } }, + "node_modules/@react-three/fiber": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.18.0.tgz", + "integrity": "sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.7", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=18 <19", + "react-dom": ">=18 <19", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/@react-three/postprocessing": { + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-2.19.1.tgz", + "integrity": "sha512-7P25LOSToH/I6b3UipNK17IIFlX4FDUmWcaomfwu82+CzhXTOz8Fcc1ZXEZ7vFA/5Fr/2peNlXgXZJvoa+aCdA==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "maath": "^0.6.0", + "n8ao": "^1.6.6", + "postprocessing": "^6.32.1", + "three-stdlib": "^2.23.4" + }, + "peerDependencies": { + "@react-three/fiber": "^8.0", + "react": "^18.0", + "three": ">= 0.138.0" + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -1459,6 +1642,13 @@ "node": ">=4" } }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1504,6 +1694,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/draco3d": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", + "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1511,6 +1707,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/node": { "version": "22.15.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", @@ -1523,6 +1729,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1564,6 +1776,27 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-redux": { + "version": "7.1.34", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", + "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", @@ -1573,6 +1806,29 @@ "@types/react": "*" } }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/three": { + "version": "0.176.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.176.0.tgz", + "integrity": "sha512-FwfPXxCqOtP7EdYMagCFePNKoG1AGBDUEVKtluv2BTVRpSt7b+X27xNsirPCTCqY1pGYsPUzaM3jgWP7dXSxlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@dimforge/rapier3d-compat": "^0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": "*", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.18.1" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1580,6 +1836,12 @@ "license": "MIT", "optional": true }, + "node_modules/@types/webxr": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.22.tgz", + "integrity": "sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==", + "license": "MIT" + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1607,6 +1869,26 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/@webgpu/types": { + "version": "0.1.60", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.60.tgz", + "integrity": "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -1634,7 +1916,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -1710,7 +1991,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -1744,6 +2024,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findindex": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.2.4.tgz", + "integrity": "sha512-LLm4mhxa9v8j0A/RPnpQAP4svXToJFh+Hp1pNYl5ZD5qpB4zdx/D4YjpVcETkhFbUKWO3iGMVLvrOnnmkAJT6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -1824,7 +2121,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -1842,16 +2138,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -1894,7 +2213,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -1906,6 +2224,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -1947,6 +2280,35 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1959,6 +2321,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2015,6 +2413,39 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2101,6 +2532,12 @@ ], "license": "CC-BY-4.0" }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2202,6 +2639,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2218,6 +2667,27 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2225,6 +2695,43 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -2264,6 +2771,15 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "license": "MIT", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2282,11 +2798,22 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -2304,7 +2831,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -2322,7 +2848,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -2424,6 +2949,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2460,14 +3003,20 @@ } }, "node_modules/dompurify": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", - "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, + "node_modules/draco3d": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", + "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", + "license": "Apache-2.0" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2488,6 +3037,22 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.143", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.143.tgz", @@ -2501,6 +3066,24 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2514,7 +3097,6 @@ "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", - "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -2638,7 +3220,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2654,7 +3235,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -2667,7 +3247,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7", @@ -2730,6 +3309,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2985,23 +3570,82 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/eventemitter3": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", "license": "MIT" }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -3042,7 +3686,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -3061,6 +3704,13 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT", + "peer": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3074,6 +3724,15 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3086,6 +3745,23 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -3135,7 +3811,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -3163,6 +3838,60 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3204,6 +3933,15 @@ } } }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3238,7 +3976,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -3315,7 +4052,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3329,6 +4065,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -3405,7 +4150,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -3452,11 +4196,35 @@ "integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==", "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3491,7 +4259,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" @@ -3551,6 +4318,68 @@ "react-is": "^16.7.0" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3603,14 +4432,12 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3621,6 +4448,15 @@ "node": ">= 0.4" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -3641,7 +4477,6 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -3665,7 +4500,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, "license": "MIT", "dependencies": { "async-function": "^1.0.0", @@ -3685,7 +4519,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" @@ -3713,7 +4546,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3730,7 +4562,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3758,7 +4589,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -3801,7 +4631,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -3826,7 +4655,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3857,7 +4685,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3879,7 +4706,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3902,6 +4728,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -3924,7 +4756,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3937,7 +4768,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -3953,7 +4783,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3970,7 +4799,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -3988,7 +4816,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -4000,11 +4827,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4017,7 +4849,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -4033,7 +4864,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -4050,7 +4880,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -4059,6 +4888,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -4077,6 +4912,27 @@ "node": ">= 0.4" } }, + "node_modules/its-fine": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", + "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -4120,6 +4976,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4145,11 +5007,16 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -4159,6 +5026,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4172,6 +5045,21 @@ "node": ">=6" } }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -4292,6 +5180,16 @@ "yallist": "^3.0.2" } }, + "node_modules/maath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", + "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.144.0", + "three": ">=0.144.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4301,12 +5199,33 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4316,6 +5235,13 @@ "node": ">= 8" } }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "license": "MIT", + "peer": true + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4329,6 +5255,39 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -4392,6 +5351,16 @@ "thenify-all": "^1.0.0" } }, + "node_modules/n8ao": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/n8ao/-/n8ao-1.10.0.tgz", + "integrity": "sha512-zWdSfXkCUz6E7LM/Occ0JRWcp0HFlS03EUZ/LrqS8WwWFK74V4h7uzwRPugkdaBsJnclab+CJnWzNnlh7ozWVQ==", + "license": "ISC", + "peerDependencies": { + "postprocessing": ">=6.30.0", + "three": ">=0.137" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4417,6 +5386,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -4424,6 +5402,41 @@ "dev": true, "license": "MIT" }, + "node_modules/node-telegram-bot-api": { + "version": "0.66.0", + "resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.66.0.tgz", + "integrity": "sha512-s4Hrg5q+VPl4/tJVG++pImxF6eb8tNJNj4KnDqAOKL6zGU34lo9RXmyAN158njwGN+v8hdNf8s9fWIYW9hPb5A==", + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.1", + "@cypress/request-promise": "^5.0.0", + "array.prototype.findindex": "^2.0.2", + "bl": "^1.2.3", + "debug": "^3.2.7", + "eventemitter3": "^3.0.0", + "file-type": "^3.9.0", + "mime": "^1.6.0", + "pump": "^2.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/node-telegram-bot-api/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/node-telegram-bot-api/node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4443,6 +5456,16 @@ "node": ">=0.10.0" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4465,7 +5488,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4503,7 +5525,6 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -4574,11 +5595,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4606,7 +5638,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", @@ -4694,6 +5725,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4751,6 +5791,15 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -4760,6 +5809,12 @@ "node": ">=8" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4806,7 +5861,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4975,6 +6029,21 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/postprocessing": { + "version": "6.37.3", + "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.37.3.tgz", + "integrity": "sha512-2FzfZg41Ml/ddvSalLdCz7H53Wmr/ZvK238fauTG1CI97+65BDgo1dNH6AKhAWc5q9ngpyG5EYiXVbNCl2t1iA==", + "license": "Zlib", + "peerDependencies": { + "three": ">= 0.157.0 < 0.177.0" + } + }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4985,6 +6054,12 @@ "node": ">= 0.8.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4996,16 +6071,71 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5054,6 +6184,36 @@ "node": ">=0.10" } }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -5066,6 +6226,32 @@ "node": ">=0.10.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "deprecated": "react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-beautiful-dnd/node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, "node_modules/react-chartjs-2": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", @@ -5161,6 +6347,62 @@ "react-dom": "^16 || ^17 || ^18" } }, + "node_modules/react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/react-reconciler/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -5225,16 +6467,16 @@ } }, "node_modules/react-toastify": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", - "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", "license": "MIT", "dependencies": { - "clsx": "^2.1.0" + "clsx": "^2.1.1" }, "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "react": "^18 || ^19", + "react-dom": "^18 || ^19" } }, "node_modules/react-transition-group": { @@ -5253,6 +6495,21 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -5262,6 +6519,33 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5274,11 +6558,19 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5317,6 +6609,165 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "license": "ISC", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/request/node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/request/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/request/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "peer": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -5433,6 +6884,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5460,7 +6927,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5476,11 +6942,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5497,7 +6982,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5511,6 +6995,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -5530,6 +7020,43 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5566,7 +7093,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5577,6 +7103,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5602,7 +7134,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5622,7 +7153,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5639,7 +7169,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5658,7 +7187,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5704,6 +7232,64 @@ "node": ">=0.10.0" } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "license": "ISC", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -5806,7 +7392,6 @@ "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5828,7 +7413,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5847,7 +7431,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -5964,6 +7547,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -6055,6 +7647,59 @@ "node": ">=0.8" } }, + "node_modules/three": { + "version": "0.167.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.167.1.tgz", + "integrity": "sha512-gYTLJA/UQip6J/tJvl91YYqlZF47+D/kxiWrbTon35ZHlXEN0VOo+Qke2walF1/x92v55H6enomymg4Dak52kw==", + "license": "MIT" + }, + "node_modules/three-stdlib": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.0.tgz", + "integrity": "sha512-kv0Byb++AXztEGsULgMAs8U2jgUdz6HPpAB/wDJnLiLlaWQX2APHhiTJIN7rqW+Of0eRgcp7jn05U1BsCP3xBA==", + "license": "MIT", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "potpack": "^1.0.1" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/three-stdlib/node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6067,6 +7712,27 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -6079,6 +7745,24 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6105,11 +7789,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6124,7 +7821,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -6144,7 +7840,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -6166,7 +7861,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -6187,7 +7881,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6211,6 +7904,24 @@ "optional": true, "peer": true }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -6246,12 +7957,21 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", @@ -6266,6 +7986,15 @@ } } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6285,6 +8014,35 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.18", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", @@ -6364,7 +8122,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", @@ -6384,7 +8141,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6412,7 +8168,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, "license": "MIT", "dependencies": { "is-map": "^2.0.3", @@ -6431,7 +8186,6 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -6551,7 +8305,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/yallist": { @@ -6585,6 +8338,23 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 31deacc..32e0a1b 100644 --- a/package.json +++ b/package.json @@ -7,27 +7,37 @@ "dev": "vite", "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "preview": "vite preview", + "server": "node server.js", + "dev:full": "concurrently \"npm run dev\" \"npm run server\"" }, "dependencies": { + "@react-three/fiber": "^8.18.0", + "@react-three/postprocessing": "^2.19.1", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "chart.js": "^4.4.3", + "cors": "^2.8.5", "dompurify": "^3.2.5", + "express": "^5.1.0", "framer-motion": "^11.18.2", "gsap": "^3.13.0", + "node-telegram-bot-api": "^0.66.0", "pocketbase": "^0.21.3", + "postprocessing": "^6.37.3", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-chartjs-2": "^5.2.0", "react-datepicker": "^8.3.0", "react-dom": "^18.2.0", - "react-fast-marquee": "^1.6.4", + "react-fast-marquee": "^1.6.5", "react-hot-toast": "^2.5.2", "react-icons": "^5.5.0", "react-quill": "^2.0.0", "react-router-dom": "^6.23.1", "react-select": "^5.10.1", - "react-toastify": "^10.0.5", + "react-toastify": "^11.0.5", + "three": "^0.167.1", "uuid": "^11.1.0" }, "devDependencies": { diff --git a/public/icon.png b/public/icon.png index 8b13789..b85a870 100644 Binary files a/public/icon.png and b/public/icon.png differ diff --git a/public/logo.png b/public/logo.png index 8b13789..72b37c2 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/server.js b/server.js new file mode 100644 index 0000000..757f09b --- /dev/null +++ b/server.js @@ -0,0 +1,176 @@ +import express from 'express'; +import cors from 'cors'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import generateReviewRouter from './src/api/generate-review.js'; +import TelegramBot from 'node-telegram-bot-api'; +import PocketBase from 'pocketbase'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const app = express(); +const port = process.env.PORT || 3000; + +// Инициализация Telegram бота +const bot = new TelegramBot('8057681898:AAE0BCbcg6M0BgREaOeXsjK7yXBHVpOIs9c', { polling: false }); + +// Инициализация PocketBase +const pb = new PocketBase('https://pocketbase.campfiregg.ru'); + +// Middleware +app.use(cors()); +app.use(express.json()); + +// API routes +app.use('/api', generateReviewRouter); + +// Telegram авторизация +app.post('/api/auth/telegram', async (req, res) => { + try { + const { id, first_name, username, photo_url, auth_date, hash } = req.body; + + if (!id) { + return res.status(400).json({ + success: false, + error: 'Отсутствует ID пользователя Telegram' + }); + } + + console.log('Получены данные от Telegram:', { id, first_name, username }); + + // Очищаем и валидируем данные + const cleanUsername = (username || `user_${id}`).replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase(); + const cleanFirstName = (first_name || '').replace(/[<>]/g, ''); + + // Проверяем, существует ли пользователь с таким Telegram ID + const users = await pb.collection('users').getList(1, 1, { + filter: `telegram_id = ${id}` + }); + + let user; + if (users.items.length > 0) { + // Если пользователь существует, используем его + user = users.items[0]; + console.log('Найден существующий пользователь:', user.id); + } else { + // Генерируем случайный пароль для пользователя + const randomPassword = Math.random().toString(36).slice(-8); + + // Если пользователь не существует, создаем нового + try { + const userData = { + username: cleanUsername, + login: cleanUsername, + telegram_id: id, + telegram_username: username, + telegram_first_name: cleanFirstName, + telegram_photo_url: photo_url, + email: `${cleanUsername}@telegram.user`, + password: randomPassword, + passwordConfirm: randomPassword, + verified: true + }; + + console.log('Создаем пользователя с данными:', userData); + + user = await pb.collection('users').create(userData); + console.log('Создан новый пользователь:', user.id); + } catch (createError) { + console.error('Ошибка при создании пользователя:', createError); + return res.status(500).json({ + success: false, + error: 'Ошибка при создании пользователя: ' + (createError.data?.message || createError.message) + }); + } + } + + // Генерируем JWT токен + const token = `telegram_${id}_${Date.now()}`; + + res.json({ + success: true, + token, + user: { + id: user.id, + username: user.username, + login: user.login, + telegram_id: id, + telegram_username: username, + telegram_first_name: first_name, + telegram_photo_url: photo_url + } + }); + } catch (error) { + console.error('Ошибка авторизации через Telegram:', error); + res.status(500).json({ + success: false, + error: 'Ошибка авторизации: ' + (error.data?.message || error.message) + }); + } +}); + +// Привязка Telegram к существующему профилю +app.post('/api/auth/telegram/link', async (req, res) => { + try { + const { id, first_name, username, photo_url, auth_date, hash, userId } = req.body; + + if (!userId) { + return res.status(400).json({ + success: false, + error: 'Не указан ID пользователя' + }); + } + + // Проверяем, не привязан ли уже этот Telegram ID к другому аккаунту + const existingUsers = await pb.collection('users').getList(1, 1, { + filter: `telegram_id = ${id}` + }); + + if (existingUsers.items.length > 0) { + return res.status(400).json({ + success: false, + error: 'Этот Telegram аккаунт уже привязан к другому профилю' + }); + } + + // Обновляем профиль пользователя + const user = await pb.collection('users').update(userId, { + telegram_id: id, + telegram_username: username, + telegram_first_name: first_name, + telegram_photo_url: photo_url + }); + + res.json({ + success: true, + user: { + id: user.id, + username: user.username, + telegram_id: id, + telegram_username: username, + telegram_first_name: first_name, + telegram_photo_url: photo_url + } + }); + } catch (error) { + console.error('Ошибка привязки Telegram:', error); + res.status(500).json({ + success: false, + error: 'Ошибка привязки Telegram' + }); + } +}); + +// Serve static files in production +if (process.env.NODE_ENV === 'production') { + app.use(express.static(join(__dirname, 'dist'))); + + app.get('*', (req, res) => { + res.sendFile(join(__dirname, 'dist', 'index.html')); + }); +} + +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index da0aa8a..94efd6d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,116 +1,54 @@ -import React, { useEffect } from 'react'; -import { Routes, Route } from 'react-router-dom'; +import React, { useRef, useEffect, useState } from 'react'; import { AuthProvider } from './contexts/AuthContext'; import { ProfileActionsProvider } from './contexts/ProfileActionsContext'; -import { ClickSparkProvider, useClickSpark } from './contexts/ClickSparkContext'; -import ClickSpark from './components/reactbits/Animations/ClickSpark/ClickSpark'; -import AuthRoute from './components/auth/AuthRoute'; -import GuestRoute from './components/auth/GuestRoute'; +import { ClickSparkProvider } from './contexts/ClickSparkContext'; +import { MediaProvider } from './contexts/MediaContext'; +import { withErrorBoundary } from './hooks/useErrorBoundary'; import Header from './components/layout/Header'; import Footer from './components/layout/Footer'; -import HomePage from './pages/HomePage'; -import CatalogPage from './pages/CatalogPage'; -import MediaOverviewPage from './pages/MediaOverviewPage'; -import ProfilePage from './pages/ProfilePage'; -import RatingPage from './pages/RatingPage'; -import AdminLayout from './components/admin/AdminLayout'; -import AdminDashboard from './pages/admin/AdminDashboard'; -import AdminMediaPage from './pages/admin/AdminMediaPage'; -import AdminUsersPage from './pages/admin/AdminUsersPage'; -import AdminSeasonsPage from './pages/admin/AdminSeasonsPage'; -import AdminAchievementsPage from './pages/admin/AdminAchievementsPage'; -import AdminSupportPage from './pages/admin/AdminSupportPage'; -import NotFoundPage from './pages/NotFoundPage'; -import LoginPage from './pages/LoginPage'; -import RegisterPage from './pages/RegisterPage'; -import ForgotPasswordPage from './pages/ForgotPasswordPage'; -import SupportTicketForm from './components/support/SupportTicketForm'; -import PrivacyPolicyPage from './pages/legal/PrivacyPolicyPage'; -import TermsOfServicePage from './pages/legal/TermsOfServicePage'; -import UserAgreementPage from './pages/legal/UserAgreementPage'; -import ProfileSettingsPage from './pages/ProfileSettingsPage'; -import AdminSuggestionsPage from './pages/admin/AdminSuggestionsPage'; -import AdminRoute from './components/auth/AdminRoute'; -import SupportPage from './pages/SupportPage'; +import AppRoutes from './routes'; +import MobileApp from './mobile/App'; import './index.css'; const AppContent = () => { - const { addSpark } = useClickSpark(); + const [isMobile, setIsMobile] = useState(false); useEffect(() => { - const handleClick = (e) => { - console.log('[ClickSpark] Добавление искры:', e.clientX, e.clientY); - addSpark(e.clientX, e.clientY); + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); }; - document.addEventListener('click', handleClick); - return () => document.removeEventListener('click', handleClick); - }, [addSpark]); + checkMobile(); + window.addEventListener('resize', checkMobile); + + return () => window.removeEventListener('resize', checkMobile); + }, []); + + if (isMobile) { + return ; + } return ( -
- -
+
+
- - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - - }> - } /> - } /> - - - }> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - }> - } /> - - - } /> - } /> - } /> - - } /> - -
-
-
+ + +
+
); }; +const AppContentWithErrorBoundary = withErrorBoundary(AppContent); + function App() { return ( - + + + diff --git a/src/api/generate-review.js b/src/api/generate-review.js new file mode 100644 index 0000000..cf756dc --- /dev/null +++ b/src/api/generate-review.js @@ -0,0 +1,95 @@ +import { Router } from 'express'; + +const router = Router(); + +// Функция для задержки +const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +router.post('/generate-review', async (req, res) => { + try { + const { prompt, model, temperature, max_tokens } = req.body; + + const headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer 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', + 'HTTP-Referer': 'http://localhost:5173', + 'X-Title': 'Campfire Critics' + }; + + console.log('Request headers:', headers); + + // Максимальное количество попыток + const maxRetries = 3; + let retryCount = 0; + let lastError = null; + + while (retryCount < maxRetries) { + try { + const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { + method: 'POST', + headers, + body: JSON.stringify({ + model, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: prompt + } + ] + } + ], + temperature, + max_tokens + }) + }); + + if (response.status === 429) { + console.log(`Rate limit hit, attempt ${retryCount + 1} of ${maxRetries}`); + // Увеличиваем время ожидания с каждой попыткой + await delay(Math.pow(2, retryCount) * 1000); + retryCount++; + continue; + } + + if (!response.ok) { + const errorData = await response.json().catch(() => null); + console.error('OpenRouter API Error:', errorData); + console.error('Response status:', response.status); + console.error('Response headers:', response.headers); + return res.status(response.status).json({ + error: { + message: errorData?.error?.message || 'Ошибка при генерации рецензии', + code: response.status + } + }); + } + + const data = await response.json(); + return res.json(data); + } catch (error) { + lastError = error; + console.error(`Attempt ${retryCount + 1} failed:`, error); + retryCount++; + if (retryCount < maxRetries) { + await delay(Math.pow(2, retryCount) * 1000); + } + } + } + + // Если все попытки исчерпаны + throw lastError || new Error('Превышен лимит запросов к API'); + } catch (error) { + console.error('Error in generate-review endpoint:', error); + res.status(500).json({ + error: { + message: error.message || 'Внутренняя ошибка сервера', + code: 500 + } + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/assets/cat.png b/src/assets/cat.png new file mode 100644 index 0000000..d069010 Binary files /dev/null and b/src/assets/cat.png differ diff --git a/src/components/LatestReviewsMarquee.jsx b/src/components/LatestReviewsMarquee.jsx index 92d01e9..bf69710 100644 --- a/src/components/LatestReviewsMarquee.jsx +++ b/src/components/LatestReviewsMarquee.jsx @@ -64,7 +64,7 @@ const LatestReviewsMarquee = () => { const username = user.username || 'Анонимный пользователь'; const avatarUrl = user.profile_picture ? getFileUrl(user, 'profile_picture', { thumb: '40x40' }) : null; - const userProfileLink = user.username ? `/profile/${user.username}` : '#'; + const userProfileLink = user.login ? `/profile/${user.login}` : '#'; const mediaTitle = media.title || 'Неизвестное произведение'; const mediaLink = media.path ? `/media/${media.path}` : '#'; diff --git a/src/components/TelegramAuth.jsx b/src/components/TelegramAuth.jsx new file mode 100644 index 0000000..be8d3c0 --- /dev/null +++ b/src/components/TelegramAuth.jsx @@ -0,0 +1,100 @@ +import React, { useEffect, useRef } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useAuth } from '../contexts/AuthContext'; + +const TelegramAuth = () => { + const navigate = useNavigate(); + const location = useLocation(); + const scriptRef = useRef(null); + const containerRef = useRef(null); + const { user, userProfile } = useAuth(); + + // Определяем, является ли это привязкой к существующему профилю + const isLinking = location.pathname === '/profile/telegram-link'; + + useEffect(() => { + // Создаем скрипт для Telegram Login Widget + const script = document.createElement('script'); + script.src = 'https://telegram.org/js/telegram-widget.js?22'; + script.setAttribute('data-telegram-login', 'CampFireAuthBot'); + script.setAttribute('data-size', 'large'); + script.setAttribute('data-radius', '8'); + script.setAttribute('data-request-access', 'write'); + script.setAttribute('data-userpic', 'false'); + script.setAttribute('data-onauth', 'onTelegramAuth(user)'); + script.async = true; + scriptRef.current = script; + + // Добавляем функцию обратного вызова + window.onTelegramAuth = async (user) => { + try { + const endpoint = isLinking ? '/api/auth/telegram/link' : '/api/auth/telegram'; + + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...user, + userId: isLinking ? userProfile?.id : undefined + }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Ошибка авторизации'); + } + + if (data.success) { + if (isLinking) { + // После привязки возвращаемся на страницу профиля + navigate(`/profile/${userProfile.login}`); + } else { + // При авторизации сохраняем токен и перенаправляем на главную + localStorage.setItem('token', data.token); + // Используем setTimeout для гарантии сохранения токена перед редиректом + setTimeout(() => { + window.location.href = '/'; + }, 100); + } + } else { + throw new Error(data.error || 'Ошибка авторизации'); + } + } catch (error) { + console.error('Ошибка авторизации:', error); + alert(error.message || 'Произошла ошибка при авторизации. Пожалуйста, попробуйте снова.'); + } + }; + + // Добавляем скрипт на страницу + if (containerRef.current) { + containerRef.current.appendChild(script); + } + + return () => { + // Удаляем скрипт при размонтировании компонента + if (containerRef.current && scriptRef.current) { + try { + containerRef.current.removeChild(scriptRef.current); + } catch (error) { + console.error('Ошибка при удалении скрипта:', error); + } + } + }; + }, [navigate, isLinking, userProfile]); + + return ( +
+
+

+ {isLinking ? 'Привязка Telegram' : 'Вход через Telegram'} +

+
+
+
+ ); +}; + +export default TelegramAuth; \ No newline at end of file diff --git a/src/components/admin/AIReviewModal.jsx b/src/components/admin/AIReviewModal.jsx new file mode 100644 index 0000000..6780183 --- /dev/null +++ b/src/components/admin/AIReviewModal.jsx @@ -0,0 +1,187 @@ +import React, { useState } from 'react'; +import { FaRobot, FaSpinner } from 'react-icons/fa'; +import { createReviewViaAPI, getReviewsByMediaId } from '../../services/pocketbaseService'; + +const AIReviewModal = ({ media, onClose, onSuccess }) => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [review, setReview] = useState(null); + + const generateAIReview = async () => { + setLoading(true); + setError(null); + + try { + // Проверяем, является ли characteristics уже объектом + let characteristics; + try { + characteristics = typeof media.characteristics === 'string' + ? JSON.parse(media.characteristics) + : media.characteristics; + } catch (e) { + console.error('Ошибка при парсинге characteristics:', e); + characteristics = media.characteristics; + } + + // Получаем существующие рецензии для обучения ИИ + const existingReviews = await getReviewsByMediaId(media.id); + const reviewsContent = existingReviews.map(r => r.content).join('\n\n'); + + const prompt = `Ты - эксперт по оценке ${media.type === 'movie' ? 'фильмов' : media.type === 'tv' ? 'сериалов' : media.type === 'anime' ? 'аниме' : 'игр'} на сайте Campfire Critics. + Твоя задача - написать рецензию на "${media.title}", основываясь на мнении пользователей сайта. + + Вот существующие рецензии пользователей на это произведение: + ${reviewsContent} + + Напиши рецензию, которая отражает общее мнение пользователей сайта, но при этом сохраняет профессиональный тон. + Оцени следующие характеристики по шкале от 1 до 10: ${Object.keys(characteristics).join(', ')}. + + Ответ должен быть в формате JSON: + { + "content": "текст рецензии", + "ratings": { + "характеристика1": число, + "характеристика2": число, + ... + } + }`; + + const response = await fetch('/api/generate-review', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + prompt, + model: 'google/gemma-3-27b-it:free', + temperature: 0.7, + max_tokens: 1000 + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error?.message || 'Ошибка при генерации рецензии'); + } + + const data = await response.json(); + + // Извлекаем JSON из markdown-формата + let aiResponseText = data.choices[0].message.content; + + // Ищем JSON в тексте (может быть обернут в ```json или просто быть чистым JSON) + const jsonMatch = aiResponseText.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/) || + aiResponseText.match(/(\{[\s\S]*\})/); + + if (!jsonMatch) { + throw new Error('Не удалось найти JSON в ответе ИИ'); + } + + try { + const aiResponse = JSON.parse(jsonMatch[1] || jsonMatch[0]); + + if (!aiResponse || typeof aiResponse !== 'object') { + console.error('Неверный формат ответа:', aiResponse); + throw new Error('Неверный формат ответа от ИИ'); + } + + if (!aiResponse.content || !aiResponse.ratings) { + console.error('Отсутствуют обязательные поля:', aiResponse); + throw new Error('Отсутствуют обязательные поля в ответе ИИ'); + } + + // Проверяем, что все оценки являются числами + const ratings = aiResponse.ratings; + for (const [key, value] of Object.entries(ratings)) { + if (typeof value !== 'number' || isNaN(value)) { + ratings[key] = Number(value); + if (isNaN(ratings[key])) { + throw new Error(`Некорректное значение оценки для характеристики "${key}"`); + } + } + } + + // Рассчитываем общую оценку + const overallRating = Object.values(ratings).reduce((sum, value) => sum + value, 0) / Object.keys(ratings).length; + + // Создаем рецензию + const reviewData = { + media_id: media.id, + user_id: 'g520s25pzm0t6e1', + content: aiResponse.content, + ratings: aiResponse.ratings, + has_spoilers: false, + media_type: media.type, + overall_rating: parseFloat(overallRating.toFixed(2)), + progress: media.type === 'game' ? 'completed' : 'watched' + }; + + const createdReview = await createReviewViaAPI(reviewData); + setReview(createdReview); + onSuccess(createdReview); + } catch (parseError) { + console.error('Ошибка парсинга JSON:', parseError); + console.error('Проблемный текст:', aiResponseText); + throw new Error('Неверный формат JSON в ответе ИИ'); + } + } catch (err) { + console.error('Ошибка при генерации рецензии:', err); + setError(err.message || 'Произошла ошибка при генерации рецензии'); + } finally { + setLoading(false); + } + }; + + return ( +
+

ИИ-рецензия для {media.title}

+ + {error && ( +
+ {error} +
+ )} + + {!review ? ( +
+

+ Нажмите кнопку ниже, чтобы сгенерировать рецензию с помощью ИИ +

+ +
+ ) : ( +
+
+

Рецензия:

+

{review.content}

+
+
+

Оценки:

+
+ {Object.entries(review.ratings).map(([key, value]) => ( +
+ {key}: + {value}/10 +
+ ))} +
+
+
+ +
+
+ )} +
+ ); +}; + +export default AIReviewModal; \ No newline at end of file diff --git a/src/components/admin/AchievementForm.jsx b/src/components/admin/AchievementForm.jsx index 93ae688..4719d3d 100644 --- a/src/components/admin/AchievementForm.jsx +++ b/src/components/admin/AchievementForm.jsx @@ -1,15 +1,57 @@ import React, { useState, useEffect } from 'react'; import { pb } from '../../services/pocketbaseService'; // Import pb for create/update +import * as FaIcons from 'react-icons/fa'; +import * as MdIcons from 'react-icons/md'; +import * as IoIcons from 'react-icons/io5'; +import * as BiIcons from 'react-icons/bi'; +import * as HiIcons from 'react-icons/hi'; +import * as RiIcons from 'react-icons/ri'; +import { FaSearch } from 'react-icons/fa'; const AchievementForm = ({ onSuccess, onCancel, achievementToEdit }) => { const [formData, setFormData] = useState({ title: '', description: '', xp_reward: 0, - // Add other fields like icon if you have them + icon: 'FaTrophy' }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [showIconPicker, setShowIconPicker] = useState(false); + const [selectedIconSet, setSelectedIconSet] = useState('fa'); + + // Объединяем все доступные иконки + const allIcons = { + fa: Object.keys(FaIcons), + md: Object.keys(MdIcons), + io: Object.keys(IoIcons), + bi: Object.keys(BiIcons), + hi: Object.keys(HiIcons), + ri: Object.keys(RiIcons) + }; + + // Получаем текущий набор иконок + const currentIcons = allIcons[selectedIconSet] || allIcons.fa; + + // Фильтруем иконки по поисковому запросу + const filteredIcons = currentIcons.filter(iconName => + iconName.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Получаем компонент иконки по имени + const getIconComponent = (iconName) => { + const prefix = iconName.substring(0, 2).toLowerCase(); + switch (prefix) { + case 'fa': return FaIcons[iconName]; + case 'md': return MdIcons[iconName]; + case 'io': return IoIcons[iconName]; + case 'bi': return BiIcons[iconName]; + case 'hi': return HiIcons[iconName]; + case 'ri': return RiIcons[iconName]; + default: return FaIcons.FaTrophy; + } + }; useEffect(() => { if (achievementToEdit) { @@ -17,15 +59,15 @@ const AchievementForm = ({ onSuccess, onCancel, achievementToEdit }) => { title: achievementToEdit.title, description: achievementToEdit.description, xp_reward: achievementToEdit.xp_reward, - // Load other fields here + icon: achievementToEdit.icon || 'FaTrophy' }); } else { - // Reset form if creating a new achievement - setFormData({ - title: '', - description: '', - xp_reward: 0, - }); + setFormData({ + title: '', + description: '', + xp_reward: 0, + icon: 'FaTrophy' + }); } }, [achievementToEdit]); @@ -33,10 +75,18 @@ const AchievementForm = ({ onSuccess, onCancel, achievementToEdit }) => { const { name, value, type } = e.target; setFormData({ ...formData, - [name]: type === 'number' ? parseInt(value, 10) || 0 : value, // Parse number input + [name]: type === 'number' ? parseInt(value, 10) || 0 : value, }); }; + const handleIconSelect = (iconName) => { + setFormData(prev => ({ + ...prev, + icon: iconName + })); + setShowIconPicker(false); + }; + const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); @@ -45,17 +95,11 @@ const AchievementForm = ({ onSuccess, onCancel, achievementToEdit }) => { try { let record; if (achievementToEdit) { - // Update existing achievement - console.log('PocketBase: Updating achievement:', achievementToEdit.id, formData); record = await pb.collection('achievements').update(achievementToEdit.id, formData); - console.log('PocketBase: Achievement updated:', record); } else { - // Create new achievement - console.log('PocketBase: Creating achievement:', formData); record = await pb.collection('achievements').create(formData); - console.log('PocketBase: Achievement created:', record); } - onSuccess(); // Call success callback + onSuccess(); } catch (err) { console.error('Error saving achievement:', err); setError(err.message); @@ -64,6 +108,8 @@ const AchievementForm = ({ onSuccess, onCancel, achievementToEdit }) => { } }; + const IconComponent = getIconComponent(formData.icon); + return (
{error && ( @@ -112,20 +158,74 @@ const AchievementForm = ({ onSuccess, onCancel, achievementToEdit }) => { /> - {/* Add file input for icon if needed */} - {/*
- - -
- */} + +
+ + {showIconPicker && ( +
+
+
+ {Object.keys(allIcons).map(set => ( + + ))} +
+
+ setSearchQuery(e.target.value)} + placeholder="Поиск иконок..." + className="w-full px-3 py-2 pl-8 bg-campfire-charcoal text-campfire-light border border-campfire-ash/30 rounded-md focus:outline-none focus:ring-campfire-amber focus:border-campfire-amber" + /> + +
+
+
+ {filteredIcons.map(iconName => { + const Icon = getIconComponent(iconName); + return ( + + ); + })} +
+
+ )} +
+
- +
+ ) : ( -
- -
+
+ handleFileChange(e, 'poster')} + className="hidden" + id="poster-upload" + /> -

или перетащите сюда

-
-

PNG, JPG, GIF до 10MB

)}
- {/* Backdrop Upload */} + {/* Backdrop Upload with Controls */}
-
+
{backdropPreview ? ( - <> - Backdrop Preview +
+ Backdrop preview +
+ - +
+
) : ( -
- -
+
+ handleFileChange(e, 'backdrop')} + className="hidden" + id="backdrop-upload" + /> -

или перетащите сюда

-
-

PNG, JPG, GIF до 10MB

)}
@@ -706,6 +826,34 @@ function MediaForm({ media, onSuccess }) {
+ + {showPosterCropper && tempPosterFile && ( + handleCrop(blob, 'poster')} + onCancel={() => { + setShowPosterCropper(false); + setTempPosterFile(null); + }} + aspectRatio={2/3} + maxWidth={500} + maxHeight={750} + /> + )} + + {showBackdropCropper && tempBackdropFile && ( + handleCrop(blob, 'backdrop')} + onCancel={() => { + setShowBackdropCropper(false); + setTempBackdropFile(null); + }} + aspectRatio={16/9} + maxWidth={1920} + maxHeight={1080} + /> + )}
); } diff --git a/src/components/admin/NotificationManager.jsx b/src/components/admin/NotificationManager.jsx new file mode 100644 index 0000000..73dea3b --- /dev/null +++ b/src/components/admin/NotificationManager.jsx @@ -0,0 +1,332 @@ +import React, { useState, useEffect } from 'react'; +import { pb } from '../../services/pocketbaseService'; +import { FaBell, FaUsers, FaFilter, FaPaperPlane, FaPlus, FaTrash } from 'react-icons/fa'; + +const NotificationManager = () => { + const [title, setTitle] = useState(''); + const [message, setMessage] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + const [filters, setFilters] = useState([{ field: '', operator: '=', value: '' }]); + const [mediaList, setMediaList] = useState([]); + const [selectedMedia, setSelectedMedia] = useState([]); + const [minReviews, setMinReviews] = useState(1); + + useEffect(() => { + loadMediaList(); + }, []); + + const loadMediaList = async () => { + try { + const records = await pb.collection('media').getList(1, 1000, { + sort: 'title' + }); + setMediaList(records.items); + } catch (error) { + console.error('Error loading media list:', error); + } + }; + + const operators = [ + { value: '=', label: 'Равно' }, + { value: '!=', label: 'Не равно' }, + { value: '>', label: 'Больше' }, + { value: '<', label: 'Меньше' }, + { value: '>=', label: 'Больше или равно' }, + { value: '<=', label: 'Меньше или равно' }, + { value: '~', label: 'Содержит' }, + { value: '!~', label: 'Не содержит' } + ]; + + const fields = [ + { value: 'level', label: 'Уровень' }, + { value: 'review_count', label: 'Количество рецензий' }, + { value: 'email', label: 'Email' }, + { value: 'showcase', label: 'Витрина' }, + { value: 'created', label: 'Дата регистрации' }, + { value: 'has_reviewed', label: 'Оставил рецензию на медиа' } + ]; + + const addFilter = () => { + setFilters([...filters, { field: '', operator: '=', value: '' }]); + }; + + const removeFilter = (index) => { + setFilters(filters.filter((_, i) => i !== index)); + }; + + const updateFilter = (index, field, value) => { + const newFilters = [...filters]; + newFilters[index][field] = value; + setFilters(newFilters); + }; + + const buildFilterString = () => { + return filters + .filter(f => f.field && f.operator && f.value) + .map(f => { + let value = f.value; + // Если значение не число и не булево, заключаем в кавычки + if (isNaN(value) && value !== 'true' && value !== 'false') { + value = `"${value}"`; + } + return `${f.field} ${f.operator} ${value}`; + }) + .join(' && '); + }; + + const handleMediaSelect = (mediaId) => { + setSelectedMedia(prev => { + if (prev.includes(mediaId)) { + return prev.filter(id => id !== mediaId); + } else { + return [...prev, mediaId]; + } + }); + }; + + const sendNotification = async () => { + if (!title || !message) { + setError('Заполните заголовок и сообщение'); + return; + } + + setLoading(true); + setError(null); + setSuccess(null); + + try { + let filterString = buildFilterString(); + + // Если выбран фильтр по медиа, добавляем подзапрос для получения пользователей с рецензиями + if (selectedMedia.length > 0) { + const reviews = await pb.collection('reviews').getList(1, 1000, { + filter: `media_id ?~ "${selectedMedia.join('" || media_id ?~ "')}"`, + fields: 'user_id' + }); + + // Группируем рецензии по пользователям + const userReviews = reviews.items.reduce((acc, review) => { + acc[review.user_id] = (acc[review.user_id] || 0) + 1; + return acc; + }, {}); + + // Фильтруем пользователей по минимальному количеству рецензий + const userIds = Object.entries(userReviews) + .filter(([_, count]) => count >= minReviews) + .map(([userId]) => userId); + + if (userIds.length > 0) { + const mediaFilter = `id ?~ "${userIds.join('" || id ?~ "')}"`; + filterString = filterString ? `${filterString} && ${mediaFilter}` : mediaFilter; + } else { + throw new Error('Нет пользователей, соответствующих выбранным критериям'); + } + } + + // Получаем список пользователей по фильтру + const users = await pb.collection('users').getList(1, 1000, { + filter: filterString || undefined + }); + + if (users.items.length === 0) { + throw new Error('Нет пользователей, соответствующих выбранным фильтрам'); + } + + // Отправляем уведомления каждому пользователю + for (const user of users.items) { + await pb.collection('notifications').create({ + user_id: user.id, + type: 'system', + title, + message, + is_read: false + }); + } + + setSuccess(`Уведомления отправлены ${users.items.length} пользователям`); + setTitle(''); + setMessage(''); + setFilters([{ field: '', operator: '=', value: '' }]); + setSelectedMedia([]); + setMinReviews(1); + } catch (error) { + console.error('Error sending notifications:', error); + setError(error.message || 'Произошла ошибка при отправке уведомлений'); + } finally { + setLoading(false); + } + }; + + return ( +
+

+ + Отправка уведомлений +

+ + {error && ( +
+ {error} +
+ )} + + {success && ( +
+ {success} +
+ )} + +
+
+ + setTitle(e.target.value)} + className="w-full p-2 bg-campfire-charcoal border border-campfire-ash/30 rounded-md text-campfire-light focus:ring-2 focus:ring-campfire-amber focus:border-transparent" + placeholder="Введите заголовок уведомления" + /> +
+ +
+ + -
- - {/* Stats (Placeholder) */} -
-

Статистика

-
-
-

Всего обзоров:

-

{userProfile.review_count || 0}

-
-
-

Средняя оценка:

-

{userProfile.average_rating ? userProfile.average_rating.toFixed(1) : 'N/A'}

-
- {/* Add more stats here if available in userProfile */} -
-
- - - {/* Save Button */} -
- -
- - )} -
-
-
- ); -}; - -export default ProfileSettingsPage; diff --git a/src/pages/RatingPage.jsx b/src/pages/RatingPage.jsx index 5b423f0..dea497f 100644 --- a/src/pages/RatingPage.jsx +++ b/src/pages/RatingPage.jsx @@ -87,7 +87,7 @@ const RatingPage = () => { const renderUserList = (userItems, rankType) => (
{userItems.map((user, index) => ( - + {index + 1}. { - console.log('RegisterPage useEffect: Running. State:', { user: !!user, userProfile: !!userProfile, authLoading, isInitialized }); // Add useEffect log - if (!authLoading && isInitialized) { // Wait for auth to be initialized and not loading - if (user && userProfile) { - console.log('RegisterPage useEffect: User and profile loaded, redirecting to home.'); - navigate("/"); // Redirect after successful registration or if already logged in - } else { - console.log('RegisterPage useEffect: No user and auth initialized, staying on register.'); - } - } else if (authLoading) { - console.log('RegisterPage useEffect: Auth is loading...'); - } else if (!isInitialized) { - console.log('RegisterPage useEffect: Auth is not initialized...'); + if (!authLoading && isInitialized) { + if (user && userProfile) { + navigate("/"); + } } - }, [user, userProfile, authLoading, isInitialized, navigate]); // Add isInitialized to dependencies - + }, [user, userProfile, authLoading, isInitialized, navigate]); const handleSubmit = async (e) => { e.preventDefault(); @@ -51,13 +39,11 @@ function RegisterPage() { return; } - // Проверка на допустимые символы в логине if (!/^[a-zA-Z0-9_]+$/.test(login)) { setError("Логин может содержать только латинские буквы, цифры и знак подчеркивания"); return; } - // Проверка длины логина if (login.length < 3 || login.length > 20) { setError("Логин должен быть от 3 до 20 символов"); return; @@ -66,14 +52,8 @@ function RegisterPage() { try { setError(""); setLoading(true); - console.log('RegisterPage handleSubmit: Attempting sign up...'); await signUp(login, password, email || null); - console.log('RegisterPage handleSubmit: Sign up process initiated. Redirect handled by useEffect.'); - // Redirect is now handled by the useEffect based on auth state change - } catch (err) { - console.error("Registration error:", err); - // More specific error handling for PocketBase unique constraints if (err.response && err.response.data) { const errorData = err.response.data; if (errorData.login && errorData.login.code === 'validation_not_unique') { @@ -91,20 +71,13 @@ function RegisterPage() { } }; - // Render loading state based on AuthContext loading - // Removed the check here to allow the form to render even if auth is loading, - // relying on the AuthProvider's own loading state handling. - // This might help diagnose if the component itself is not rendering. - // if (authLoading || !isInitialized) { - // console.log('RegisterPage: Rendering initial auth loading state.'); - // return ( - //
- // Загрузка авторизации... - //
- // ); - // } - - console.log('RegisterPage: Rendering registration form.'); // Log before rendering form + if (authLoading || !isInitialized) { + return ( +
+
+
+ ); + } return (
diff --git a/src/pages/admin/AdminAchievementsPage.jsx b/src/pages/admin/AdminAchievementsPage.jsx index 5b89676..2b4bbc2 100644 --- a/src/pages/admin/AdminAchievementsPage.jsx +++ b/src/pages/admin/AdminAchievementsPage.jsx @@ -2,7 +2,13 @@ import React, { useState, useEffect } from 'react'; import { getAllAchievements, pb } from '../../services/pocketbaseService'; // Import pb for file URL import AchievementForm from '../../components/admin/AchievementForm'; // Import the form component import Modal from '../../components/common/Modal'; // Assuming you have a Modal component -import { FaTrophy, FaEdit, FaTrash } from 'react-icons/fa'; // Icons +import { FaEdit, FaTrash } from 'react-icons/fa'; // Icons +import * as FaIcons from 'react-icons/fa'; +import * as MdIcons from 'react-icons/md'; +import * as IoIcons from 'react-icons/io5'; +import * as BiIcons from 'react-icons/bi'; +import * as HiIcons from 'react-icons/hi'; +import * as RiIcons from 'react-icons/ri'; const AdminAchievementsPage = () => { const [achievements, setAchievements] = useState([]); @@ -11,6 +17,20 @@ const AdminAchievementsPage = () => { const [showFormModal, setShowFormModal] = useState(false); // State for form modal const [achievementToEdit, setAchievementToEdit] = useState(null); // State for editing + // Получаем компонент иконки по имени + const getIconComponent = (iconName) => { + const prefix = iconName.substring(0, 2).toLowerCase(); + switch (prefix) { + case 'fa': return FaIcons[iconName]; + case 'md': return MdIcons[iconName]; + case 'io': return IoIcons[iconName]; + case 'bi': return BiIcons[iconName]; + case 'hi': return HiIcons[iconName]; + case 'ri': return RiIcons[iconName]; + default: return FaIcons.FaTrophy; + } + }; + useEffect(() => { loadAchievements(); }, []); @@ -90,37 +110,38 @@ const AdminAchievementsPage = () => {
) : (
- {achievements.map((achievement) => ( -
- {/* Achievement Icon (using a placeholder for now) */} -
- {/* You would ideally use the achievement.icon here if available */} - -
-
-

{achievement.title}

-

{achievement.description}

-

+ {achievement.xp_reward} XP

-
-
- - + +
-
- ))} + ); + })}
)} diff --git a/src/pages/admin/AdminDashboard.jsx b/src/pages/admin/AdminDashboard.jsx index 495c0e5..23d75f1 100644 --- a/src/pages/admin/AdminDashboard.jsx +++ b/src/pages/admin/AdminDashboard.jsx @@ -1,11 +1,13 @@ import React, { useState, useEffect } from 'react'; import { getAdminStats } from '../../services/pocketbaseService'; -import { FaUsers, FaFilm, FaComment, FaHeadset, FaLightbulb } from 'react-icons/fa'; +import { FaUsers, FaFilm, FaComment, FaHeadset, FaLightbulb, FaBell, FaTachometerAlt } from 'react-icons/fa'; +import NotificationManager from '../../components/admin/NotificationManager'; const AdminDashboard = () => { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [activeTab, setActiveTab] = useState('dashboard'); useEffect(() => { const loadStats = async () => { @@ -47,68 +49,98 @@ const AdminDashboard = () => {

Административная панель

Добро пожаловать в административную панель CampFire Critics.

- -
-
-
-
- -
-
-

Пользователи

-

{stats?.users || 0}

-
-
-
-
-
-
- -
-
-

Медиа

-

{stats?.media || 0}

-
-
-
- -
-
-
- -
-
-

Обзоры

-

{stats?.reviews || 0}

-
-
-
- -
-
-
- -
-
-

Тикеты

-

{stats?.tickets || 0}

-
-
-
- -
-
-
- -
-
-

Предложения

-

{stats?.suggestions || 0}

-
-
-
+ {/* Tabs */} +
+ +
+ + {activeTab === 'dashboard' && ( +
+
+
+
+ +
+
+

Пользователи

+

{stats?.users || 0}

+
+
+
+ +
+
+
+ +
+
+

Медиа

+

{stats?.media || 0}

+
+
+
+ +
+
+
+ +
+
+

Обзоры

+

{stats?.reviews || 0}

+
+
+
+ +
+
+
+ +
+
+

Тикеты

+

{stats?.tickets || 0}

+
+
+
+ +
+
+
+ +
+
+

Предложения

+

{stats?.suggestions || 0}

+
+
+
+
+ )} + + {activeTab === 'notifications' && ( +
+ +
+ )}
); }; diff --git a/src/pages/admin/AdminMediaPage.jsx b/src/pages/admin/AdminMediaPage.jsx index 3896150..c9f6136 100644 --- a/src/pages/admin/AdminMediaPage.jsx +++ b/src/pages/admin/AdminMediaPage.jsx @@ -4,7 +4,9 @@ import { useAuth } from '../../contexts/AuthContext'; import { listMedia, deleteMedia, mediaTypes } from '../../services/pocketbaseService'; import Modal from '../../components/common/Modal'; import MediaForm from '../../components/admin/MediaForm'; -import { FaEdit, FaTrashAlt, FaPlus, FaTv } from 'react-icons/fa'; // Import FaTv icon +import TMDBImportModal from '../../components/admin/TMDBImportModal'; +import AIReviewModal from '../../components/admin/AIReviewModal'; +import { FaEdit, FaTrashAlt, FaPlus, FaTv, FaDownload, FaRobot } from 'react-icons/fa'; // Import FaTv icon const AdminMediaPage = () => { const navigate = useNavigate(); @@ -16,10 +18,12 @@ const AdminMediaPage = () => { const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [mediaToEdit, setMediaToEdit] = useState(null); const [filterType, setFilterType] = useState(''); // State for type filter const [filterPublished, setFilterPublished] = useState(''); // State for published filter ('', 'true', 'false') - + const [isAIReviewModalOpen, setIsAIReviewModalOpen] = useState(false); + const [selectedMedia, setSelectedMedia] = useState(null); // Check if the current user is admin const isAdmin = userProfile?.role === 'admin'; @@ -115,6 +119,21 @@ const AdminMediaPage = () => { navigate(`/admin/media/${mediaId}/seasons`); }; + const handleImportSuccess = (importData) => { + setMediaToEdit(importData); + setIsAddModalOpen(true); + }; + + const handleAIReview = (media) => { + setSelectedMedia(media); + setIsAIReviewModalOpen(true); + }; + + const handleAIReviewSuccess = () => { + setIsAIReviewModalOpen(false); + setSelectedMedia(null); + loadMediaData(); // Перезагружаем данные после создания рецензии + }; if (authLoading || loading) { return
Загрузка...
; @@ -137,16 +156,30 @@ const AdminMediaPage = () => { return ( -
{/* Removed container-custom and pt-20 */} +
-

Управление контентом

+

+ Управление контентом +

+
+ +
{/* Filters */} @@ -273,6 +306,14 @@ const AdminMediaPage = () => { > + {/* AI Review Button */} +
@@ -289,7 +330,7 @@ const AdminMediaPage = () => { title="Добавить новый контент" size="lg" // Use lg size > - + {/* Edit Media Modal */} @@ -302,6 +343,35 @@ const AdminMediaPage = () => { {/* Pass mediaToEdit to the form */} + + {/* Import Modal */} + setIsImportModalOpen(false)} + title="Импорт из TMDB" + size="lg" + > + setIsImportModalOpen(false)} + onImport={handleImportSuccess} + /> + + + {/* AI Review Modal */} + setIsAIReviewModalOpen(false)} + title="ИИ-рецензия" + size="lg" + > + {selectedMedia && ( + setIsAIReviewModalOpen(false)} + onSuccess={handleAIReviewSuccess} + /> + )} +
); }; diff --git a/src/routes/index.jsx b/src/routes/index.jsx new file mode 100644 index 0000000..9bfd6ff --- /dev/null +++ b/src/routes/index.jsx @@ -0,0 +1,130 @@ +import React, { lazy, Suspense } from 'react'; +import { Routes, Route } from 'react-router-dom'; +import AuthRoute from '../components/auth/AuthRoute'; +import GuestRoute from '../components/auth/GuestRoute'; +import AdminRoute from '../components/auth/AdminRoute'; +import AdminLayout from '../components/admin/AdminLayout'; +import LoadingSpinner from '../components/common/LoadingSpinner'; +import TelegramAuth from '../components/TelegramAuth'; + +// Ленивая загрузка страниц +const HomePage = lazy(() => import('../pages/HomePage')); +const CatalogPage = lazy(() => import('../pages/CatalogPage')); +const MediaOverviewPage = lazy(() => import('../pages/MediaOverviewPage')); +const ProfilePage = lazy(() => import('../pages/ProfilePage')); +const RatingPage = lazy(() => import('../pages/RatingPage')); +const LoginPage = lazy(() => import('../pages/LoginPage')); +const RegisterPage = lazy(() => import('../pages/RegisterPage')); +const ForgotPasswordPage = lazy(() => import('../pages/ForgotPasswordPage')); +const SupportPage = lazy(() => import('../pages/SupportPage')); +const NotFoundPage = lazy(() => import('../pages/NotFoundPage')); + +// Админские страницы +const AdminDashboard = lazy(() => import('../pages/admin/AdminDashboard')); +const AdminMediaPage = lazy(() => import('../pages/admin/AdminMediaPage')); +const AdminUsersPage = lazy(() => import('../pages/admin/AdminUsersPage')); +const AdminSeasonsPage = lazy(() => import('../pages/admin/AdminSeasonsPage')); +const AdminAchievementsPage = lazy(() => import('../pages/admin/AdminAchievementsPage')); +const AdminSupportPage = lazy(() => import('../pages/admin/AdminSupportPage')); +const AdminSuggestionsPage = lazy(() => import('../pages/admin/AdminSuggestionsPage')); + +// Легальные страницы +const PrivacyPolicyPage = lazy(() => import('../pages/legal/PrivacyPolicyPage')); +const TermsOfServicePage = lazy(() => import('../pages/legal/TermsOfServicePage')); +const UserAgreementPage = lazy(() => import('../pages/legal/UserAgreementPage')); + +// Компонент для обработки ошибок +const ErrorBoundary = ({ children }) => { + const [hasError, setHasError] = React.useState(false); + const [error, setError] = React.useState(null); + + React.useEffect(() => { + const handleError = (error) => { + setHasError(true); + setError(error); + // Здесь можно добавить логирование ошибки + console.error('Route Error:', error); + }; + + window.addEventListener('error', handleError); + return () => window.removeEventListener('error', handleError); + }, []); + + if (hasError) { + return ( +
+
+

Произошла ошибка

+

{error?.message || 'Неизвестная ошибка'}

+ +
+
+ ); + } + + return children; +}; + +// Компонент для обертки ленивой загрузки +const LazyLoad = ({ children }) => ( + + }> + {children} + + +); + +const AppRoutes = () => { + return ( + + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } /> + + }> + } /> + } /> + + + {/* Временно отключена авторизация через Telegram + } /> + */} + + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + }> + } /> + + + } /> + } /> + } /> + + } /> + + ); +}; + +export default AppRoutes; \ No newline at end of file diff --git a/src/services/notificationService.js b/src/services/notificationService.js new file mode 100644 index 0000000..5f732d0 --- /dev/null +++ b/src/services/notificationService.js @@ -0,0 +1,193 @@ +import { pb } from './pocketbaseService'; + +export const createNotification = async (userId, type, title, message, data = {}) => { + try { + // Проверяем настройки уведомлений пользователя + const settings = await pb.collection('notification_settings').getFirstListItem(`user_id = "${userId}"`); + + // Проверяем, включены ли уведомления данного типа + if (!settings[type]) { + return null; + } + + // Создаем уведомление + const notification = await pb.collection('notifications').create({ + user_id: userId, + type, + title, + message, + data, + is_read: false + }); + + // Если включены email-уведомления, отправляем email + if (settings.email_notifications) { + // TODO: Добавить отправку email + } + + return notification; + } catch (error) { + console.error('Error creating notification:', error); + if (error.status === 403) { + console.error('Permission denied: User does not have access to notifications'); + } + return null; + } +}; + +export const createAchievementNotification = async (userId, achievement) => { + return createNotification( + userId, + 'achievement', + 'Новое достижение!', + `Вы получили достижение "${achievement.name}"`, + { achievement_id: achievement.id } + ); +}; + +export const createReviewLikeNotification = async (userId, review, likedBy) => { + return createNotification( + userId, + 'like', + 'Новый лайк', + `${likedBy.username} оценил вашу рецензию на "${review.expand?.media_id?.title}"`, + { review_id: review.id } + ); +}; + +export const createReviewCommentNotification = async (userId, review, comment, commentedBy) => { + return createNotification( + userId, + 'review_comment', + 'Новый комментарий', + `${commentedBy.username} прокомментировал вашу рецензию на "${review.expand?.media_id?.title}"`, + { review_id: review.id, comment_id: comment.id, commented_by_id: commentedBy.id } + ); +}; + +export const createLevelUpNotification = async (userId, newLevel) => { + return createNotification( + userId, + 'level_up', + 'Повышение уровня!', + `Поздравляем! Вы достигли ${newLevel} уровня`, + { level: newLevel } + ); +}; + +export const createSystemNotification = async (userId, title, message, data = {}) => { + return createNotification( + userId, + 'system', + title, + message, + data + ); +}; + +export const createNewFollowerNotification = async (userId, follower) => { + return createNotification( + userId, + 'follower', + 'Новый подписчик', + `${follower.username} подписался на вас`, + { follower_id: follower.id } + ); +}; + +export const createCommentReplyNotification = async (userId, comment, reply, repliedBy) => { + return createNotification( + userId, + 'comment_reply', + 'Новый ответ на комментарий', + `${repliedBy.username} ответил на ваш комментарий`, + { comment_id: comment.id, reply_id: reply.id, replied_by_id: repliedBy.id } + ); +}; + +export const createModerationNotification = async (userId, type, content) => { + const messages = { + review_approved: 'Ваша рецензия была одобрена', + review_rejected: 'Ваша рецензия была отклонена', + comment_approved: 'Ваш комментарий был одобрен', + comment_rejected: 'Ваш комментарий был отклонен' + }; + + return createNotification( + userId, + 'moderation', + 'Модерация контента', + messages[type] || 'Ваш контент был проверен модератором', + { type, content_id: content.id } + ); +}; + +export const markNotificationAsRead = async (notificationId) => { + try { + return await pb.collection('notifications').update(notificationId, { is_read: true }); + } catch (error) { + console.error('Error marking notification as read:', error); + return null; + } +}; + +export const markAllNotificationsAsRead = async (userId) => { + try { + const notifications = await pb.collection('notifications').getList(1, 1000, { + filter: `user_id = "${userId}" && is_read = false` + }); + + await Promise.all( + notifications.items.map(n => + pb.collection('notifications').update(n.id, { is_read: true }) + ) + ); + + return true; + } catch (error) { + console.error('Error marking all notifications as read:', error); + return false; + } +}; + +export const getNotificationSettings = async (userId) => { + try { + // Сначала проверяем, есть ли уже настройки + const existingSettings = await pb.collection('notification_settings').getList(1, 1, { + filter: `user_id = "${userId}"` + }); + + if (existingSettings.items.length > 0) { + return existingSettings.items[0]; + } + + // Если настроек нет, создаем новые + return await pb.collection('notification_settings').create({ + user_id: userId, + achievement: true, + like: true, + review_comment: true, + system: true, + level_up: true, + follower: true, + comment_reply: true, + moderation: true, + email_notifications: false + }); + } catch (error) { + console.error('Error in getNotificationSettings:', error); + throw error; + } +}; + +export const updateNotificationSettings = async (settingsId, settings) => { + try { + return await pb.collection('notification_settings').update(settingsId, settings); + } catch (error) { + console.error('Error updating notification settings:', error); + if (error.status === 403) { + throw new Error('Нет доступа к настройкам уведомлений'); + } + throw error; + } +}; \ No newline at end of file diff --git a/src/services/pocketbaseService.js b/src/services/pocketbaseService.js index bcffc12..5978c7c 100644 --- a/src/services/pocketbaseService.js +++ b/src/services/pocketbaseService.js @@ -12,16 +12,6 @@ const pb = new PocketBase(pbUrl); // Disable auto-cancellation to prevent issues with concurrent requests pb.autoCancellation(false); -console.log('[PocketBase] Инициализирован с URL:', pbUrl); - -// --- REMOVED: Explicit cookie loading and saving --- -// pb.authStore.loadFromCookie(document.cookie); -// pb.authStore.onChange(() => { -// document.cookie = pb.authStore.exportToCookie({ httpOnly: false }); -// }); -// --- END REMOVED --- - -console.log('PocketBase client initialized with URL:', pbUrl); // --- Media Types Definition --- // Define the available media types and their display labels @@ -88,12 +78,9 @@ export const getXpForCurrentLevel = (currentLevel) => { // PocketBase uses a single sign-up function for users export const signUp = async (login, password, email = null) => { try { - console.log('PocketBase: Начало регистрации:', { login, email }); - - // Создаем FormData для загрузки дефолтного аватара const formData = new FormData(); formData.append('login', login); - formData.append('username', login); // Дублируем login в username + formData.append('username', login); formData.append('email', email || ''); formData.append('password', password); formData.append('passwordConfirm', password); @@ -107,39 +94,24 @@ export const signUp = async (login, password, email = null) => { formData.append('level', '1'); formData.append('showcase', '[]'); - // Создаем пользователя const record = await pb.collection('users').create(formData); - console.log('PocketBase: Пользователь создан:', record); - - // Очищаем текущую сессию перед авторизацией pb.authStore.clear(); - - // Auto-login after successful signup const authData = await pb.collection('users').authWithPassword(login, password); - console.log('PocketBase: Автоматический вход после регистрации успешен:', authData); - - // Return the authenticated user and their record return { user: authData.record, profile: authData.record }; } catch (error) { console.error('PocketBase: Ошибка при регистрации:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); - } throw error; } }; export const signIn = async (login, password) => { try { - console.log('PocketBase: Попытка входа для', login); const authData = await pb.collection('users').authWithPassword(login, password); - console.log('PocketBase: Вход успешен:', authData); return authData.record; } catch (error) { console.error('PocketBase: Ошибка входа:', error); if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); if (error.response.data.message) { if (error.response.data.message.includes('Invalid login credentials')) { throw new Error('Неверный логин или пароль'); @@ -156,9 +128,7 @@ export const signIn = async (login, password) => { export const signOut = async () => { try { - console.log('PocketBase: Попытка выхода'); pb.authStore.clear(); - console.log('PocketBase: Выход успешен'); } catch (error) { console.error('PocketBase: Ошибка выхода:', error); throw error; @@ -166,23 +136,16 @@ export const signOut = async () => { }; export const getCurrentUser = () => { - // PocketBase stores the authenticated user in authStore.model return pb.authStore.isValid ? pb.authStore.model : null; }; // New function to request password reset export const requestPasswordReset = async (email) => { try { - console.log('PocketBase: Попытка запроса сброса пароля для', email); - // PocketBase SDK function for password reset request await pb.collection('users').requestPasswordReset(email); - console.log('PocketBase: Запрос на сброс пароля отправлен для', email); - return true; // Indicate success + return true; } catch (error) { console.error('PocketBase: Ошибка при запроса сброса пароля:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); - } throw error; } }; @@ -191,7 +154,6 @@ export const requestPasswordReset = async (email) => { // --- User Functions (PocketBase 'users' collection) --- export const getUserProfile = async (userId) => { try { - console.log('[PocketBase] Загрузка профиля:', userId); const record = await pb.collection('users').getOne(userId, { expand: 'showcase,showcase.media_id,showcase.season_id' }); @@ -205,7 +167,6 @@ export const getUserProfile = async (userId) => { // New function to get user profile by username export const getUserProfileByUsername = async (username) => { try { - console.log('[PocketBase] Поиск профиля по имени:', username); const userRecord = await pb.collection('users').getFirstListItem(`username="${username}"`, { expand: 'showcase,showcase.media_id,showcase.season_id' }); @@ -216,21 +177,27 @@ export const getUserProfileByUsername = async (username) => { } }; +export const getUserProfileByLogin = async (login) => { + try { + const records = await pb.collection('users').getList(1, 1, { + filter: `login = "${login}"`, + expand: 'showcase,showcase.media_id,showcase.season_id' + }); + return records.items[0] || null; + } catch (error) { + console.error('Error fetching user profile by login:', error); + throw error; + } +}; export const updateUserProfile = async (userId, profileData) => { try { - console.log('PocketBase: Обновление профиля пользователя:', userId, 'с данными:', profileData); - // PocketBase update requires the record ID - // Note: This function is intended for updating non-file fields. - // File uploads/deletions are handled by uploadFile/deleteFile. const record = await pb.collection('users').update(userId, profileData); - console.log('PocketBase: Профиль пользователя обновлен успешно:', record); - console.log('PocketBase: Обновленная профиль статистика:', { average_rating: record.average_rating, review_count: record.review_count, xp: record.xp, level: record.level }); // Log new fields return record; } catch (error) { console.error('PocketBase: Ошибка обновления профиля:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -247,16 +214,13 @@ export const updateUserShowcase = async (userId, reviewIds) => { throw new Error('Invalid reviewIds array'); } try { - console.log('PocketBase: Обновление витрины пользователя:', userId, 'с рецензиями:', reviewIds); - // Update the 'showcase' multiple relation field const updatedUser = await pb.collection('users').update(userId, { showcase: reviewIds // Pass the array of review IDs }); - console.log('PocketBase: Витрина пользователя обновлена успешно:', updatedUser); return updatedUser; } catch (error) { console.error('PocketBase: Ошибка обновления витрины пользователя:', error); - if (error.response && error.response.data) { + if (error.response && error.response.data) { console.error('PocketBase: Response data:', error.response.data); } throw error; @@ -265,16 +229,11 @@ export const updateUserShowcase = async (userId, reviewIds) => { // New function to list users ranked by review count export const listUsersRankedByReviews = async (limit = 20) => { - console.log('PocketBase: Получение списка пользователей по количеству рецензий (лимит:', limit, ')'); try { - // Fetch users, sorted by review_count descending const records = await pb.collection('users').getList(1, limit, { sort: '-review_count', filter: 'review_count > 0', // Only include users with at least one review - // You might want to select specific fields to reduce payload size - // fields: 'id,username,profile_picture,review_count' }); - console.log('PocketBase: Получен список пользователей по рецензиям:', records.items); return records.items; } catch (error) { console.error('PocketBase: Ошибка получения списка пользователей по рецензиям:', error); @@ -284,16 +243,11 @@ export const listUsersRankedByReviews = async (limit = 20) => { // New function to list users ranked by level export const listUsersRankedByLevel = async (limit = 20) => { - console.log('PocketBase: Получение списка пользователей по уровню (лимит:', limit, ')'); try { - // Fetch users, sorted by level descending, then XP descending const records = await pb.collection('users').getList(1, limit, { sort: '-level,-xp', // Sort by level first, then XP filter: 'level > 1 || xp > 0', // Only include users above level 1 or with some XP - // You might want to select specific fields to reduce payload size - // fields: 'id,username,profile_picture,level,xp' }); - console.log('PocketBase: Получен список пользователей по уровню:', records.items); return records.items; } catch (error) { console.error('PocketBase: Ошибка получения списка пользователей по уровню:', error); @@ -306,33 +260,70 @@ export const listUsersRankedByLevel = async (limit = 20) => { // Placeholder/Example validation function - adjust based on your actual media schema // Now accepts characteristics as an object { key: label } -export const validateMediaData = (mediaData, characteristicsObject) => { +export const validateMediaData = (formData, characteristics) => { const errors = []; - if (!mediaData.get('title')) { + if (!formData.get('title')) { errors.push('Название обязательно.'); } - if (!mediaData.get('type')) { + if (!formData.get('type')) { errors.push('Тип медиа обязателен.'); } - if (!mediaData.get('progress_type')) { // Validate new field + if (!formData.get('progress_type')) { // Validate new field errors.push('Тип прогресса обязателен.'); } // Add more validation rules as needed based on your schema // Validate characteristic count (min 6, max 8) using the passed object - const charCount = Object.keys(characteristicsObject).length; + const charCount = Object.keys(characteristics).length; if (charCount < 6 || charCount > 8) { errors.push(`Должно быть от 6 до 8 характеристик (сейчас: ${charCount}).`); } // Validate that characteristic keys are not empty - for (const key in characteristicsObject) { + for (const key in characteristics) { if (!key.trim()) { errors.push('Ключ характеристики не может быть пустым.'); break; // Stop checking keys after finding one empty } } + // Validate position and scale values if they exist + const posterPosition = formData.get('poster_position'); + const backdropPosition = formData.get('backdrop_position'); + const posterScale = formData.get('poster_scale'); + const backdropScale = formData.get('backdrop_scale'); + + if (posterPosition) { + try { + const position = JSON.parse(posterPosition); + if (typeof position.x !== 'number' || typeof position.y !== 'number' || + position.x < 0 || position.x > 100 || position.y < 0 || position.y > 100) { + errors.push('Некорректные значения позиции постера'); + } + } catch (e) { + errors.push('Некорректный формат позиции постера'); + } + } + + if (backdropPosition) { + try { + const position = JSON.parse(backdropPosition); + if (typeof position.x !== 'number' || typeof position.y !== 'number' || + position.x < 0 || position.x > 100 || position.y < 0 || position.y > 100) { + errors.push('Некорректные значения позиции баннера'); + } + } catch (e) { + errors.push('Некорректный формат позиции баннера'); + } + } + + if (posterScale && (isNaN(posterScale) || posterScale < 0.5 || posterScale > 2)) { + errors.push('Некорректное значение масштаба постера'); + } + + if (backdropScale && (isNaN(backdropScale) || backdropScale < 0.5 || backdropScale > 2)) { + errors.push('Некорректное значение масштаба баннера'); + } return errors; }; @@ -340,30 +331,53 @@ export const validateMediaData = (mediaData, characteristicsObject) => { // Placeholder/Example formatting function - adjust based on your actual media schema // This function is less critical if you're building FormData correctly in the component // but can be used for final checks or transformations. -export const formatMediaData = (mediaData) => { - // Assuming mediaData is already a FormData object prepared in the component - // You might add default values or transform data here if needed - // Example: Ensure is_published is boolean - // if (mediaData.has('is_published')) { - // mediaData.set('is_published', mediaData.get('is_published') === 'true'); - // } - return mediaData; // Return the FormData object +export const formatMediaData = (formData) => { + const data = { + title: formData.get('title'), + path: formData.get('path'), + type: formData.get('type'), + overview: formData.get('overview'), + is_published: formData.get('is_published') === 'true', + is_popular: formData.get('is_popular') === 'true', + progress_type: formData.get('progress_type'), + release_date: formData.get('release_date'), + characteristics: formData.get('characteristics'), + tmdb_id: formData.get('tmdb_id'), + tmdb_type: formData.get('tmdb_type') + }; + + // Add position and scale data if they exist + const posterPosition = formData.get('poster_position'); + const backdropPosition = formData.get('backdrop_position'); + const posterScale = formData.get('poster_scale'); + const backdropScale = formData.get('backdrop_scale'); + + if (posterPosition) { + data.poster_position = posterPosition; + } + if (backdropPosition) { + data.backdrop_position = backdropPosition; + } + if (posterScale) { + data.poster_scale = parseFloat(posterScale); + } + if (backdropScale) { + data.backdrop_scale = parseFloat(backdropScale); + } + + return data; }; // createMedia using FormData export const createMedia = async (mediaData) => { try { - console.log('PocketBase: Создание медиа:', mediaData); - // PocketBase create with FormData handles file uploads - // Ensure mediaData is FormData and includes 'created_by', 'is_published', etc. const record = await pb.collection('media').create(mediaData); - console.log('PocketBase: Медиа создано с ID:', record.id); return record; } catch (error) { console.error('PocketBase: Ошибка создания медиа:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -372,27 +386,20 @@ export const createMedia = async (mediaData) => { // Assuming 'path' is a field in your PocketBase 'media' collection export const getMediaByPath = async (path) => { try { - console.log('PocketBase: Получение медиа по пути:', path); - // Fetch media record directly, relying on stored average_rating and review_count const records = await pb.collection('media').getList(1, 1, { filter: `path = "${path}"`, }); if (records.items.length > 0) { const item = records.items[0]; - console.log('PocketBase: Медиа найдено по пути:', item); - console.log('PocketBase: Медиа статистика по пути:', { average_rating: item.average_rating, review_count: item.review_count }); return item; // Return the media item with stored stats } else { - console.log('PocketBase: Медиа не найдено по пути:', path); return null; } } catch (error) { - console.error('PocketBase: Ошибка получения медиа по пути:', error); - if (error.status === 404) { // List returns 200 with empty items, getOne returns 404 - console.log('PocketBase: Медиа не найдено по пути (404):', path); + if (error.status === 404) { // List returns 200 with empty items, getOne returns 404 return null; - } + } throw error; } }; @@ -400,19 +407,13 @@ export const getMediaByPath = async (path) => { export const getMediaById = async (id) => { try { - console.log('PocketBase: Получение медиа по ID:', id); - // Fetch media record directly, relying on stored average_rating and review_count const record = await pb.collection('media').getOne(id); - console.log('PocketBase: Медиа найдено по ID:', record); - console.log('PocketBase: Медиа статистика по ID:', { average_rating: record.average_rating, review_count: record.review_count }); return record; // Return the media item with stored stats } catch (error) { - console.error('PocketBase: Ошибка получения медиа по ID:', error); - if (error.status === 404) { - console.log('PocketBase: Медиа не найдено по ID:', id); + if (error.status === 404) { return null; - } + } throw error; } }; @@ -431,17 +432,6 @@ export const listMedia = async ( filterString = '' ) => { try { - console.log('[PocketBase] Запрос медиа:', { - type, - page, - limit, - user, - isPopular, - published, - sort, - filterString - }); - let filter = []; // Базовые фильтры @@ -464,7 +454,6 @@ export const listMedia = async ( // Объединяем все фильтры const finalFilter = filter.join(' && '); - console.log('PocketBase: listMedia filter:', finalFilter); const response = await pb.collection('media').getList(page, limit, { filter: finalFilter, @@ -472,23 +461,18 @@ export const listMedia = async ( expand: 'created_by' }); - console.log('PocketBase: Получен список медиа:', response.items); return { data: response.items, count: response.totalItems, totalPages: response.totalPages }; } catch (error) { - console.error('[PocketBase] Ошибка получения списка медиа:', error); throw error; } }; // New function to list popular media export const listPopularMedia = async (limit = 10, user = null) => { - console.log('PocketBase: Получение списка популярных медиа (лимит:', limit, ')'); - // Call listMedia with isPopular = true - // Pass user to respect published status for non-admins/critics return listMedia(null, 1, limit, user, true, true); // Ensure popular media is also published }; @@ -496,17 +480,12 @@ export const listPopularMedia = async (limit = 10, user = null) => { // updateMedia using FormData export const updateMedia = async (id, mediaData) => { try { - console.log('PocketBase: Обновление медиа:', id, 'с данными:', mediaData); - // PocketBase update requires the record ID - // Pass FormData directly if mediaData is FormData const record = await pb.collection('media').update(id, mediaData); - console.log('PocketBase: Медиа обновлено успешно:', record); - console.log('PocketBase: Обновленная медиа статистика:', { average_rating: record.average_rating, review_count: record.review_count, is_published: record.is_published, is_popular: record.is_popular }); return record; } catch (error) { console.error('PocketBase: Ошибка обновления медиа:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -514,10 +493,7 @@ export const updateMedia = async (id, mediaData) => { export const deleteMedia = async (id) => { try { - console.log('PocketBase: Удаление медиа:', id); - // PocketBase delete requires the record ID await pb.collection('media').delete(id); - console.log('PocketBase: Медиа удалено успешно:', id); return { success: true }; // Indicate success } catch (error) { console.error('PocketBase: Ошибка удаления медиа:', error); @@ -531,9 +507,7 @@ export const deleteMedia = async (id) => { // MODIFIED: If collectionName is 'media', fetch ALL reviews related to this media_id, // regardless of whether they have a season_id or not. export const updateRatingStats = async (collectionName, recordId) => { - console.log(`PocketBase: Попытка обновления статистики рейтинга для ${collectionName} ID:`, recordId); if (!recordId) { - console.error(`PocketBase: updateRatingStats вызван с undefined ${collectionName}Id`); return; } try { @@ -555,7 +529,6 @@ export const updateRatingStats = async (collectionName, recordId) => { fields: 'ratings' // Only fetch the ratings field }); } else { - console.error(`PocketBase: Неизвестное имя коллекции для обновления статистики рейтинга: ${collectionName}`); return; } @@ -600,14 +573,12 @@ export const updateRatingStats = async (collectionName, recordId) => { review_count: reviewCount // review_count is the total number of reviews, regardless of N/A }); - console.log(`PocketBase: Средний рейтинг ${collectionName} обновлен для ID:`, recordId, '->', updatedRecord); - console.log(`PocketBase: Обновленная статистика ${collectionName} (updateRatingStats):`, { average_rating: updatedRecord.average_rating, review_count: updatedRecord.review_count }); return updatedRecord; } catch (error) { console.error(`PocketBase: Ошибка обновления среднего рейтинга ${collectionName}:`, error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -617,10 +588,8 @@ export const updateRatingStats = async (collectionName, recordId) => { // --- User Review Stats Update --- const updateUserReviewStats = async (userId) => { if (!userId) { - console.warn('PocketBase: updateUserReviewStats called with undefined userId'); return; } - console.log('PocketBase: Попытка обновления статистики обзоров для пользователя ID:', userId); try { // Fetch all reviews by this user const reviewsRecords = await pb.collection('reviews').getList(1, 500, { // Fetch up to 500 reviews by user @@ -668,13 +637,11 @@ const updateUserReviewStats = async (userId) => { review_count: reviewCount // review_count is the total number of reviews by the user }); - console.log('PocketBase: Статистика обзоров пользователя обновлена для ID:', userId, '->', updatedUser); - console.log('PocketBase: Обновленная статистика пользователя (updateUserReviewStats):', { average_rating: updatedUser.average_rating, review_count: updatedUser.review_count, xp: updatedUser.xp, level: updatedUser.level }); // Log new fields return updatedUser; } catch (error) { console.error('PocketBase: Ошибка обновления статистики обзоров пользователя:', error); - if (error.response && error.response.data) { + if (error.response && error.response.data) { console.error('PocketBase: Response data:', error.response.data); } throw error; @@ -685,21 +652,14 @@ const updateUserReviewStats = async (userId) => { // --- Review Functions (PocketBase 'reviews' collection) --- export const getLatestReviews = async (limit = 10) => { try { - console.log('PocketBase: Получение последних обзоров (лимит:', limit, ')'); - // Fetch reviews, sorted by creation date, EXPANDING user_id, media_id, and season_id const records = await pb.collection('reviews').getList(1, limit, { sort: '-created', expand: 'user_id,media_id,season_id' // Use the actual relation field names }); - console.log('PocketBase: Получены последние обзоры с expand:', records.items); - - // The items now contain the expanded data in the 'expand' field - // No need for manual fetching and mapping return records.items; } catch (error) { - console.error('PocketBase: Ошибка получения последних обзоров:', error); throw error; } }; @@ -708,28 +668,21 @@ export const getLatestReviews = async (limit = 10) => { // Modified to potentially filter by media_id OR season_id export const getReviewsByMediaId = async (mediaId, seasonId = null, getAllReviews = false) => { try { - console.log('PocketBase: Получение рецензий для медиа:', { mediaId, seasonId, getAllReviews }); - - let filter = `media_id = "${mediaId}"`; + let filter = `media_id = "${mediaId}"`; // Если не запрашиваем все рецензии и указан сезон, добавляем фильтр по сезону if (!getAllReviews && seasonId) { filter += ` && season_id = "${seasonId}"`; } - // Если запрашиваем все рецензии, не добавляем фильтр по сезону - // Если запрашиваем общие рецензии (seasonId = null), добавляем фильтр season_id = null - const records = await pb.collection('reviews').getList(1, 100, { filter: filter, sort: '-created', expand: 'user_id,media_id,season_id' }); - console.log('PocketBase: Получены рецензии:', records.items); return records.items; } catch (error) { - console.error('PocketBase: Ошибка при получении рецензий:', error); throw error; } }; @@ -737,22 +690,15 @@ export const getReviewsByMediaId = async (mediaId, seasonId = null, getAllReview // New function to get reviews by user ID export const getReviewsByUserId = async (userId) => { try { - console.log('PocketBase: Получение обзоров для пользователя ID:', userId); - // Fetch reviews filtered by user_id, sorted by creation date, EXPANDING media_id, user_id, and season_id const records = await pb.collection('reviews').getList(1, 500, { // Fetch up to 500 reviews, adjust limit as needed filter: `user_id = "${userId}"`, sort: '-created', expand: 'media_id,user_id,season_id' // Use the actual relation field names }); - console.log('PocketBase: Получены обзоры по пользователю ID с expand:', records.items); - - // The items now contain the expanded data in the 'expand' field - // No need for manual fetching and mapping return records.items; } catch (error) { - console.error('PocketBase: Ошибка получения обзоров по пользователю ID:', error); throw error; } }; @@ -761,7 +707,6 @@ export const getReviewsByUserId = async (userId) => { // Modified to potentially get user review for media OR season export const getUserReviewForMedia = async (userId, mediaId, seasonId = null) => { try { - console.log('PocketBase: Получение обзора пользователя', userId, 'для медиа', mediaId, 'и сезона', seasonId); let filter = `user_id = "${userId}" && media_id = "${mediaId}"`; if (seasonId) { filter += ` && season_id = "${seasonId}"`; // Filter for a specific season @@ -777,16 +722,13 @@ export const getUserReviewForMedia = async (userId, mediaId, seasonId = null) => if (records.items.length > 0) { const item = records.items[0]; - console.log('PocketBase: Найден обзор пользователя с expand:', item); // The item now contains the expanded data in the 'expand' field // No need for manual fetching and mapping return item; } else { - console.log('PocketBase: Обзор пользователя не найден'); return null; } } catch (error) { - console.error('PocketBase: Ошибка получения обзора пользователя для медиа/сезона:', error); throw error; } }; @@ -797,8 +739,6 @@ const REVIEW_XP_REWARD = 25; // Modified to accept season_id export const createReview = async (reviewData) => { try { - console.log('PocketBase: Создание обзора:', reviewData); - // Add the current user's ID to the review data const currentUser = pb.authStore.model; if (!currentUser) { @@ -834,11 +774,8 @@ export const createReview = async (reviewData) => { // media_id, season_id, content, ratings, has_spoilers, progress fields are expected in reviewData }; - console.log('PocketBase: Данные для создания обзора:', dataToSend); - // PocketBase create const record = await pb.collection('reviews').create(dataToSend); - console.log('PocketBase: Обзор создан с ID:', record.id); // --- Level and XP Logic --- // Fetch the user's current XP and level @@ -847,14 +784,11 @@ export const createReview = async (reviewData) => { const newXP = currentXP + REVIEW_XP_REWARD; const newLevel = calculateLevel(newXP); - console.log(`PocketBase: Начисление XP за рецензию. Текущий XP: ${currentXP}, Начислено: ${REVIEW_XP_REWARD}, Новый XP: ${newXP}, Новый уровень: ${newLevel}`); - // Update the user's XP and level await pb.collection('users').update(currentUser.id, { xp: newXP, level: newLevel }); - console.log('PocketBase: XP и уровень пользователя обновлены.'); // --- End Level and XP Logic --- @@ -866,8 +800,6 @@ export const createReview = async (reviewData) => { } else if (reviewData.media_id) { // If it's an overall media review, just update the media stats await updateRatingStats('media', reviewData.media_id); - } else { - console.warn('PocketBase: Обзор создан без media_id или season_id.'); } @@ -879,8 +811,8 @@ export const createReview = async (reviewData) => { return record; } catch (error) { console.error('PocketBase: Ошибка создания обзора:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -889,8 +821,6 @@ export const createReview = async (reviewData) => { // Modified to accept season_id in reviewData export const updateReview = async (reviewId, reviewData) => { try { - console.log('PocketBase: Обновление обзора:', reviewId, 'с данными:', reviewData); - // Calculate overall_rating from the ratings object if it's being updated let calculatedOverallRating = undefined; // Use undefined so it's not included if ratings isn't in reviewData if (reviewData.ratings && typeof reviewData.ratings === 'object') { @@ -920,12 +850,9 @@ export const updateReview = async (reviewId, reviewData) => { // media_id, season_id, content, ratings, has_spoilers, progress fields are expected in reviewData }; - console.log('PocketBase: Данные для обновления обзора:', dataToSend); - // PocketBase update requires the record ID const record = await pb.collection('reviews').update(reviewId, dataToSend); - console.log('PocketBase: Обзор обновлен успешно:', record); // After updating a review, update the average rating and review count for the associated media OR season // Need media_id/season_id from reviewData or fetch the review first @@ -941,8 +868,6 @@ export const updateReview = async (reviewId, reviewData) => { } else if (mediaIdToUpdate) { // If it's an overall media review, just update the media stats await updateRatingStats('media', mediaIdToUpdate); - } else { - console.warn('PocketBase: media_id или season_id не найден для обновления статистики после обновления обзора.'); } @@ -951,16 +876,14 @@ export const updateReview = async (reviewId, reviewData) => { const userIdToUpdate = reviewData.user_id || record.user_id; // Use user_id from update data or the updated record if (userIdToUpdate) { await updateUserReviewStats(userIdToUpdate); - } else { - console.warn('PocketBase: user_id не найден для обновления статистики пользователя после удаления обзора.'); } return record; } catch (error) { console.error('PocketBase: Ошибка обновления обзора:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -969,8 +892,6 @@ export const updateReview = async (reviewId, reviewData) => { // Modified to accept mediaId (optional, for convenience) export const deleteReview = async (reviewId, mediaId = null) => { try { - console.log('PocketBase: Удаление обзора:', reviewId); - // Get the review first to get the user_id, media_id, and season_id before deleting const reviewToDelete = await pb.collection('reviews').getOne(reviewId); const userIdToUpdate = reviewToDelete?.user_id; @@ -980,7 +901,6 @@ export const deleteReview = async (reviewId, mediaId = null) => { // PocketBase delete requires the record ID await pb.collection('reviews').delete(reviewId); - console.log('PocketBase: Обзор удален успешно:', reviewId); // After deleting a review, update the average rating and review count for the associated media OR season if (seasonIdToUpdate) { @@ -992,22 +912,17 @@ export const deleteReview = async (reviewId, mediaId = null) => { } else if (mediaIdToUpdate) { // If it was an overall media review, just update the media stats await updateRatingStats('media', mediaIdToUpdate); - } else { - console.warn('PocketBase: mediaId или seasonId не найден для обновления среднего рейтинга после удаления обзора.'); } // After deleting a review, update the user's review stats (average rating and review count) // XP and Level are NOT decreased on review deletion in this basic implementation. if (userIdToUpdate) { await updateUserReviewStats(userIdToUpdate); - } else { - console.warn('PocketBase: user_id не найден для обновления статистики пользователя после удаления обзора.'); } return true; // Indicate success } catch (error) { - console.error('PocketBase: Ошибка удаления обзора:', error); throw error; } }; @@ -1021,14 +936,11 @@ export const deleteReview = async (reviewId, mediaId = null) => { */ export const getAllAchievements = async () => { try { - console.log('PocketBase: Получение всех достижений'); const records = await pb.collection('achievements').getFullList({ sort: 'created', // Or sort by xp_reward, etc. }); - console.log('PocketBase: Получены достижения:', records); return records; } catch (error) { - console.error('PocketBase: Ошибка получения достижений:', error); throw error; } }; @@ -1040,27 +952,25 @@ export const getAllAchievements = async () => { */ export const getUserAchievements = async (userId) => { if (!userId) { - console.warn('PocketBase: getUserAchievements called with undefined userId'); return []; } try { - console.log('PocketBase: Получение достижений пользователя:', userId); // Fetch user_achievements records, expanding the 'achievement_id' relation - const records = await pb.collection('user_achievements').getList(1, 500, { // Fetch up to 500 user achievements + const records = await pb.collection('user_achievements').getList(1, 500, { filter: `user_id = "${userId}"`, - sort: '-awarded_at', // Sort by when they were awarded - expand: 'achievement_id' // Expand the related achievement record + sort: '-awarded_at', + expand: 'achievement_id' }); - console.log('PocketBase: Получены достижения пользователя с expand:', records.items); - - // The items now contain the expanded data in the 'expand' field - // e.g., item.expand.achievement_id will be the achievement record - return records.items; + // Преобразуем данные, чтобы использовать достижение напрямую + return records.items.map(item => ({ + ...item.expand.achievement_id, + awarded_at: item.awarded_at + })); } catch (error) { - console.error('PocketBase: Ошибка получения достижений пользователя:', error); - throw error; + console.error('Error fetching user achievements:', error); + return []; } }; @@ -1073,19 +983,15 @@ export const getUserAchievements = async (userId) => { */ export const awardAchievement = async (userId, achievementId, awardedByUserId = null) => { if (!userId || !achievementId) { - console.error('PocketBase: awardAchievement called with missing userId or achievementId'); throw new Error('Missing userId or achievementId'); } try { - console.log(`PocketBase: Попытка выдать достижение ${achievementId} пользователю ${userId}`); - // Check if the user already has this achievement to prevent duplicates const existing = await pb.collection('user_achievements').getList(1, 1, { filter: `user_id = "${userId}" && achievement_id = "${achievementId}"` }); if (existing.items.length > 0) { - console.log(`PocketBase: Пользователь ${userId} уже имеет достижение ${achievementId}. Пропуск.`); return existing.items[0]; // Return the existing record } @@ -1098,7 +1004,6 @@ export const awardAchievement = async (userId, achievementId, awardedByUserId = awarded_at: new Date().toISOString(), // Set current time }); - console.log('PocketBase: Запись user_achievement создана:', userAchievementRecord); // Fetch the achievement details to get the XP reward const achievement = await pb.collection('achievements').getOne(achievementId); @@ -1110,22 +1015,19 @@ export const awardAchievement = async (userId, achievementId, awardedByUserId = const newXP = currentXP + xpReward; const newLevel = calculateLevel(newXP); - console.log(`PocketBase: Начисление XP за достижение. Текущий XP: ${currentXP}, Начислено: ${xpReward}, Новый XP: ${newXP}, Новый уровень: ${newLevel}`); - // Update the user's XP and level await pb.collection('users').update(userId, { xp: newXP, level: newLevel }); - console.log('PocketBase: XP и уровень пользователя обновлены после выдачи достижения.'); return userAchievementRecord; } catch (error) { console.error('PocketBase: Ошибка при выдаче достижения:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -1145,12 +1047,9 @@ export const awardAchievement = async (userId, achievementId, awardedByUserId = */ export const uploadFile = async (file, collectionName, recordId, fieldName) => { if (!file) { - console.warn(`PocketBase: No file provided for ${fieldName} upload.`); return null; // Or throw an error if file is required } - console.log(`PocketBase: Attempting to upload file for record ${recordId} in collection "${collectionName}" field "${fieldName}"`); - try { // PocketBase file upload is part of updating the record using FormData const formData = new FormData(); @@ -1159,15 +1058,12 @@ export const uploadFile = async (file, collectionName, recordId, fieldName) => { // Use update to add/replace the file const record = await pb.collection(collectionName).update(recordId, formData); - console.log(`PocketBase: File uploaded successfully for field "${fieldName}". Record updated:`, record); - - // Return the updated record return record; } catch (error) { console.error(`PocketBase: Caught error during ${fieldName} upload process:`, error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -1182,7 +1078,6 @@ export const uploadFile = async (file, collectionName, recordId, fieldName) => { * @throws {Error} If the update fails. */ export const deleteFile = async (collectionName, recordId, fieldName) => { - console.log(`PocketBase: Attempting to delete file for record ${recordId} in collection "${collectionName}" field "${fieldName}"`); try { // To delete a file in PocketBase, you update the record and set the file field to null // The JS SDK update method with { fieldName: null } handles this correctly. @@ -1192,12 +1087,11 @@ export const deleteFile = async (collectionName, recordId, fieldName) => { const record = await pb.collection(collectionName).update(recordId, updateData); - console.log(`PocketBase: File reference removed for field "${fieldName}". Record updated:`, record); return record; } catch (error) { console.error(`PocketBase: Caught error during ${fieldName} deletion process:`, error); - if (error.response && error.response.data) { + if (error.response && error.response.data) { console.error('PocketBase: Response data:', error.response.data); } throw error; @@ -1221,7 +1115,6 @@ export const getFileUrl = (record, fieldName, options = {}) => { // The fieldName itself holds the filename(s) return pb.getFileUrl(record, record[fieldName], options); } catch (e) { - console.error('Error generating file URL:', e); return null; } }; @@ -1236,7 +1129,6 @@ export const searchMedia = async (query) => { if (!query || query.trim() === '') { return []; } - console.log('PocketBase: Поиск медиа по запросу:', query); try { // PocketBase search is done using filters // We can search in 'title' OR 'overview' fields @@ -1244,15 +1136,11 @@ export const searchMedia = async (query) => { const records = await pb.collection('media').getList(1, 50, { // Limit results to 50 for quick search filter: `title ~ "${query}" || overview ~ "${query}"`, // Case-insensitive search using '~' sort: '-created', // Optional: sort results - // You might want to select specific fields to reduce payload size - // fields: 'id,title,type,path,poster,release_date' }); - console.log('PocketBase: Результаты поиска:', records.items); return records.items; } catch (error) { - console.error('PocketBase: Ошибка при поиске медиа:', error); throw error; } }; @@ -1283,7 +1171,6 @@ export const searchMedia = async (query) => { */ export const getSeasonsByMediaId = async (mediaId, options = {}) => { try { - console.log('PocketBase: Получение сезонов для медиа:', mediaId); const filter = `media_id = "${mediaId}"`; const queryOptions = { @@ -1293,10 +1180,8 @@ export const getSeasonsByMediaId = async (mediaId, options = {}) => { }; const records = await pb.collection('seasons').getList(1, 100, queryOptions); - console.log('PocketBase: Получены сезоны:', records.items); return records.items; } catch (err) { - console.error('Error getting seasons by media ID:', err); throw err; } }; @@ -1308,21 +1193,16 @@ export const getSeasonsByMediaId = async (mediaId, options = {}) => { */ export const getSeasonById = async (seasonId) => { if (!seasonId) { - console.warn('PocketBase: getSeasonById called with undefined seasonId'); return null; } try { - console.log('PocketBase: Получение сезона по ID:', seasonId); const record = await pb.collection('seasons').getOne(seasonId); - console.log('PocketBase: Сезон найден по ID:', record); return record; } catch (error) { - console.error('PocketBase: Ошибка получения сезона по ID:', error); - if (error.status === 404) { - console.log('PocketBase: Сезон не найден по ID:', seasonId); + if (error.status === 404) { return null; - } - if (error.response && error.response.data) { + } + if (error.response && error.response.data) { console.error('PocketBase: Response data:', error.response.data); } throw error; @@ -1336,15 +1216,12 @@ export const getSeasonById = async (seasonId) => { */ export const createSeason = async (seasonData) => { try { - console.log('PocketBase: Создание сезона:', seasonData); - // PocketBase create with FormData handles file uploads const record = await pb.collection('seasons').create(seasonData); - console.log('PocketBase: Сезон создан с ID:', record.id); return record; } catch (error) { console.error('PocketBase: Ошибка создания сезона:', error); - if (error.response && error.response.data) { - console.error('PocketBase: Response data:', error.response.data); + if (error.response && error.response.data) { + console.error('PocketBase: Response data:', error.response.data); } throw error; } @@ -1358,22 +1235,16 @@ export const createSeason = async (seasonData) => { */ export const updateSeason = async (seasonId, seasonData) => { try { - console.log('PocketBase: Обновление сезона:', seasonId, 'с данными:', seasonData); - // PocketBase update requires the record ID - // Pass FormData directly if seasonData is FormData const record = await pb.collection('seasons').update(seasonId, seasonData); - console.log('PocketBase: Сезон обновлен успешно:', record); return record; } catch (error) { console.error('PocketBase: Ошибка обновления сезона:', error); - // --- Added Logging --- - if (error.response) { - console.error('PocketBase: Response status:', error.response.status); - console.error('PocketBase: Response data:', error.response.data); - } else { - console.error('PocketBase: No response data available.'); - } - // --- End Added Logging --- + if (error.response) { + console.error('PocketBase: Response status:', error.response.status); + console.error('PocketBase: Response data:', error.response.data); + } else { + console.error('PocketBase: No response data available.'); + } throw error; } }; @@ -1385,21 +1256,16 @@ export const updateSeason = async (seasonId, seasonData) => { */ export const deleteSeason = async (seasonId) => { try { - console.log('PocketBase: Удаление сезона:', seasonId); - // PocketBase delete requires the record ID await pb.collection('seasons').delete(seasonId); - console.log('PocketBase: Сезон удален успешно:', seasonId); return true; // Indicate success } catch (error) { console.error('PocketBase: Ошибка удаления сезона:', error); - // --- Added Logging --- - if (error.response) { - console.error('PocketBase: Response status:', error.response.status); - console.error('PocketBase: Response data:', error.response.data); - } else { - console.error('PocketBase: No response data available.'); - } - // --- End Added Logging --- + if (error.response) { + console.error('PocketBase: Response status:', error.response.status); + console.error('PocketBase: Response data:', error.response.data); + } else { + console.error('PocketBase: No response data available.'); + } throw error; } }; @@ -1414,7 +1280,6 @@ export const deleteSeason = async (seasonId) => { */ export const getSeasons = async (options = {}) => { try { - console.log('PocketBase: Получение сезонов с опциями:', options); let filter = ''; // Build filter string based on options @@ -1437,7 +1302,6 @@ export const getSeasons = async (options = {}) => { const records = await pb.collection('seasons').getFullList(queryOptions); - console.log('PocketBase: Получены сезоны:', records); return records; } catch (error) { console.error('PocketBase: Ошибка получения сезонов:', error); @@ -1457,7 +1321,6 @@ export const getSuggestions = async (page = 1, perPage = 20, sort = '-created') }); return records.items; } catch (error) { - console.error('Error fetching suggestions:', error); throw error; } }; @@ -1469,7 +1332,6 @@ export const getSuggestionById = async (id) => { }); return record; } catch (error) { - console.error('Error fetching suggestion:', error); throw error; } }; @@ -1479,7 +1341,6 @@ export const createSuggestion = async (data) => { const record = await pb.collection('suggestions').create(data); return record; } catch (error) { - console.error('Error creating suggestion:', error); throw error; } }; @@ -1489,7 +1350,6 @@ export const updateSuggestion = async (id, data) => { const record = await pb.collection('suggestions').update(id, data); return record; } catch (error) { - console.error('Error updating suggestion:', error); throw error; } }; @@ -1498,7 +1358,6 @@ export const deleteSuggestion = async (id) => { try { await pb.collection('suggestions').delete(id); } catch (error) { - console.error('Error deleting suggestion:', error); throw error; } }; @@ -1506,15 +1365,12 @@ export const deleteSuggestion = async (id) => { // Функции для работы с пользователями export const getUsers = async (page = 1, perPage = 20, sort = '-created') => { try { - console.log('PocketBase: Получение списка пользователей'); const records = await pb.collection('users').getList(page, perPage, { sort }); - console.log('PocketBase: Получены пользователи:', records.items); return records.items; } catch (error) { - console.error('PocketBase: Ошибка получения пользователей:', error); throw error; } }; @@ -1549,7 +1405,6 @@ export const getMediaCount = async () => { }); return records.totalItems; } catch (error) { - console.error('Error getting media count:', error); return 0; } }; @@ -1561,7 +1416,6 @@ export const getReviewsCount = async () => { }); return records.totalItems; } catch (error) { - console.error('Error getting reviews count:', error); return 0; } }; @@ -1574,14 +1428,12 @@ export const getFeaturedMedia = async () => { }); return records.items; } catch (error) { - console.error('Error fetching featured media:', error); return []; } }; export const getReviewsByLikes = async (limit = 20) => { try { - console.log('PocketBase: Получение рецензий по лайкам (лимит:', limit, ')'); const records = await pb.collection('reviews').getList(1, 100, { expand: 'media_id,user_id' }); @@ -1593,10 +1445,8 @@ export const getReviewsByLikes = async (limit = 20) => { return bLikes - aLikes; }).slice(0, limit); - console.log('PocketBase: Получены рецензии по лайкам:', sortedReviews); return sortedReviews; } catch (error) { - console.error('PocketBase: Ошибка получения рецензий по лайкам:', error); throw error; } }; @@ -1663,7 +1513,88 @@ export const getAdminStats = async () => { return stats; } catch (error) { - console.error('Error fetching admin stats:', error); + throw error; + } +}; + +// Функция для создания рецензии через API +export const createReviewViaAPI = async (reviewData) => { + try { + // Рассчитываем общую оценку, если она не предоставлена + if (!reviewData.overall_rating && reviewData.ratings) { + const ratings = reviewData.ratings; + const validRatings = Object.values(ratings).filter(value => typeof value === 'number' && !isNaN(value)); + if (validRatings.length > 0) { + reviewData.overall_rating = parseFloat((validRatings.reduce((sum, value) => sum + value, 0) / validRatings.length).toFixed(2)); + } + } + + // Убеждаемся, что media_type установлен + if (!reviewData.media_type && reviewData.media_id) { + const media = await pb.collection('media').getOne(reviewData.media_id); + reviewData.media_type = media.type; + } + + // Устанавливаем progress по умолчанию, если не предоставлен + if (!reviewData.progress) { + reviewData.progress = reviewData.media_type === 'game' ? 'completed' : 'watched'; + } + + const record = await pb.collection('reviews').create(reviewData); + + // Обновляем статистику медиа + if (record.media_id) { + await updateRatingStats('media', record.media_id); + } + + return record; + } catch (error) { + console.error('Error creating review:', error); + throw error; + } +}; + +// Функции для работы с лайками рецензий +export const likeReview = async (reviewId) => { + try { + const currentUser = pb.authStore.model; + if (!currentUser) { + throw new Error('User not authenticated'); + } + + const review = await pb.collection('reviews').getOne(reviewId); + const likes = review.likes || []; + + if (!likes.includes(currentUser.id)) { + likes.push(currentUser.id); + await pb.collection('reviews').update(reviewId, { likes }); + } + + return true; + } catch (error) { + console.error('Error liking review:', error); + throw error; + } +}; + +export const unlikeReview = async (reviewId) => { + try { + const currentUser = pb.authStore.model; + if (!currentUser) { + throw new Error('User not authenticated'); + } + + const review = await pb.collection('reviews').getOne(reviewId); + const likes = review.likes || []; + + if (likes.includes(currentUser.id)) { + const updatedLikes = likes.filter(id => id !== currentUser.id); + await pb.collection('reviews').update(reviewId, { likes: updatedLikes }); + } + + return true; + } catch (error) { + console.error('Error unliking review:', error); throw error; } }; diff --git a/vite.config.js b/vite.config.js index b9e8006..f9aa083 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,6 +6,30 @@ export default defineConfig({ plugins: [react()], server: { allowedHosts: ['mneie.campfiregg.site'], - host: true + host: true, + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true, + secure: false + } + } + }, + build: { + rollupOptions: { + output: { + manualChunks: { + 'react-vendor': ['react', 'react-dom', 'react-router-dom'], + 'ui-vendor': ['framer-motion', 'react-icons', 'react-select'], + 'media-vendor': ['@react-three/fiber', '@react-three/postprocessing', 'three', 'postprocessing'], + 'utils-vendor': ['chart.js', 'react-chartjs-2', 'dompurify', 'pocketbase'] + } + } + }, + chunkSizeWarningLimit: 1000, + sourcemap: false + }, + optimizeDeps: { + include: ['react', 'react-dom', 'react-router-dom'] } })