diff --git a/admin/package-lock.json b/admin/package-lock.json index 5385d0c..9bfda42 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -19,10 +19,55 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.1.4", + "@vue/test-utils": "^2.4.6", + "jsdom": "^27.2.0", "sass": "^1.83.0", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vitest": "^4.0.15" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.24", + "resolved": "https://registry.npmmirror.com/@acemir/cssom/-/cssom-0.9.24.tgz", + "integrity": "sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", + "integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.5", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/dom-selector/-/dom-selector-6.7.5.tgz", + "integrity": "sha512-Eks6dY8zau4m4wNRQjRVaKQRTalNcPcBvU1ZQ35w5kKRk1gUeNCkVLsRiATurjASTp3TKM4H10wsI50nx3NZdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.2" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -87,6 +132,141 @@ "vue": "^3.0.11" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.20.tgz", + "integrity": "sha512-8BHsjXfSciZxjmHQOuVdW2b8WLUPts9a+mfL13/PzEviufUEW2xnvQuOlKs9dRBHgRqJ53SF/DUoK9+MZk72oQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", @@ -382,6 +562,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -399,6 +596,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -416,6 +630,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -505,6 +736,24 @@ "vue": ">=3" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -517,6 +766,13 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "license": "Apache-2.0" }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -827,6 +1083,17 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.52.5", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", @@ -1135,6 +1402,31 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1177,6 +1469,90 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/expect": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-4.0.15.tgz", + "integrity": "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-4.0.15.tgz", + "integrity": "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.15", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.0.15.tgz", + "integrity": "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.15", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-4.0.15.tgz", + "integrity": "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-4.0.15.tgz", + "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.15", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", @@ -1283,6 +1659,73 @@ "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", "license": "MIT" }, + "node_modules/@vue/test-utils": { + "version": "2.4.6", + "resolved": "https://registry.npmmirror.com/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", @@ -1306,21 +1749,48 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "require-from-string": "^2.0.2" } }, - "node_modules/call-bind-apply-helpers": { + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", @@ -1333,6 +1803,16 @@ "node": ">= 0.4" } }, + "node_modules/chai": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1349,6 +1829,26 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1361,6 +1861,42 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-render": { "version": "0.15.14", "resolved": "https://registry.npmjs.org/css-render/-/css-render-0.15.14.tgz", @@ -1377,12 +1913,55 @@ "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", "license": "MIT" }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-5.3.3.tgz", + "integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/date-fns": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", @@ -1408,6 +1987,31 @@ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1445,6 +2049,39 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1475,6 +2112,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1553,6 +2197,16 @@ "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==", "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1587,6 +2241,23 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -1664,6 +2335,43 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmmirror.com/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1724,6 +2432,60 @@ "node": ">=12.0.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immutable": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", @@ -1731,6 +2493,13 @@ "dev": true, "license": "MIT" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1742,6 +2511,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1767,6 +2546,108 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmmirror.com/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/jsdom": { + "version": "27.2.0", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-27.2.0.tgz", + "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.23", + "@asamuzakjp/dom-selector": "^6.7.4", + "cssstyle": "^5.3.3", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1779,6 +2660,16 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1797,6 +2688,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -1833,6 +2731,39 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/naive-ui": { "version": "2.43.1", "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.43.1.tgz", @@ -1889,25 +2820,126 @@ "license": "MIT", "optional": true }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8.6" + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/pinia": { "version": "2.3.1", @@ -1959,12 +2991,29 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -1979,6 +3028,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.52.5", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", @@ -2021,6 +3080,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sass": { "version": "1.93.3", "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz", @@ -2042,161 +3108,1137 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/seemly": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.10.tgz", "integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==", "license": "MIT" }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "is-number": "^7.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=8.0" + "node": ">=8" } }, - "node_modules/treemate": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/treemate/-/treemate-0.3.11.tgz", - "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", - "license": "MIT" - }, - "node_modules/vdirs": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/vdirs/-/vdirs-0.1.8.tgz", - "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", - "dependencies": { - "evtd": "^0.2.2" + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" }, - "peerDependencies": { - "vue": "^3.0.11" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/vfonts": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/vfonts/-/vfonts-0.0.3.tgz", - "integrity": "sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, "license": "MIT" }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": ">=12" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vooks": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/vooks/-/vooks-0.2.12.tgz", - "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { - "evtd": "^0.2.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "peerDependencies": { - "vue": "^3.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/vue": { - "version": "3.5.22", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", - "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.22", - "@vue/compiler-sfc": "3.5.22", - "@vue/runtime-dom": "3.5.22", - "@vue/server-renderer": "3.5.22", - "@vue/shared": "3.5.22" - }, - "peerDependencies": { - "typescript": "*" + "ansi-regex": "^5.0.1" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=8" } }, - "node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmmirror.com/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "dev": true, + "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", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/treemate": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/treemate/-/treemate-0.3.11.tgz", + "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", + "license": "MIT" + }, + "node_modules/vdirs": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/vdirs/-/vdirs-0.1.8.tgz", + "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/vfonts": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/vfonts/-/vfonts-0.0.3.tgz", + "integrity": "sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-4.0.15.tgz", + "integrity": "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.15", + "@vitest/mocker": "4.0.15", + "@vitest/pretty-format": "4.0.15", + "@vitest/runner": "4.0.15", + "@vitest/snapshot": "4.0.15", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.15", + "@vitest/browser-preview": "4.0.15", + "@vitest/browser-webdriverio": "4.0.15", + "@vitest/ui": "4.0.15", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.0.15.tgz", + "integrity": "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.15", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/vitest/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.2.6", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.2.6.tgz", + "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vooks": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/vooks/-/vooks-0.2.12.tgz", + "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", + "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-sfc": "3.5.22", + "@vue/runtime-dom": "3.5.22", + "@vue/server-renderer": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "2.2.12", + "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz", + "integrity": "sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" }, "engines": { "node": ">=12" @@ -2246,6 +4288,236 @@ "peerDependencies": { "vue": "^3.0.11" } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" } } } diff --git a/admin/package.json b/admin/package.json index ababe21..378e5b5 100644 --- a/admin/package.json +++ b/admin/package.json @@ -6,7 +6,9 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest", + "test:run": "vitest run" }, "dependencies": { "@iconify/vue": "^4.1.2", @@ -20,7 +22,10 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.1.4", + "@vue/test-utils": "^2.4.6", + "jsdom": "^27.2.0", "sass": "^1.83.0", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vitest": "^4.0.15" } } diff --git a/admin/src/setupTests.js b/admin/src/setupTests.js new file mode 100644 index 0000000..bb58519 --- /dev/null +++ b/admin/src/setupTests.js @@ -0,0 +1,41 @@ +// src/setupTests.js +import { describe, expect, it, vi, beforeEach } from 'vitest' +import { createApp } from 'vue' + +// 全局测试设置 +globalThis.describe = describe +globalThis.it = it +globalThis.expect = expect +globalThis.vi = vi +globalThis.beforeEach = beforeEach + +// 模拟常用的全局函数或对象 +vi.mock('axios', () => ({ + default: { + get: vi.fn(() => Promise.resolve({ data: {} })), + post: vi.fn(() => Promise.resolve({ data: {} })), + put: vi.fn(() => Promise.resolve({ data: {} })), + delete: vi.fn(() => Promise.resolve({ data: {} })) + } +})) + +// 模拟Vue Router +vi.mock('vue-router', () => ({ + useRouter: vi.fn(() => ({ + push: vi.fn(), + replace: vi.fn() + })), + useRoute: vi.fn(() => ({ + params: {}, + query: {} + })) +})) + +// 模拟Pinia +vi.mock('pinia', () => ({ + defineStore: vi.fn(() => ({ + state: vi.fn(() => ({})), + getters: {}, + actions: {} + })) +})) diff --git a/admin/src/utils/__tests__/request.test.js b/admin/src/utils/__tests__/request.test.js new file mode 100644 index 0000000..337d586 --- /dev/null +++ b/admin/src/utils/__tests__/request.test.js @@ -0,0 +1,29 @@ +// src/utils/__tests__/request.test.js +import { describe, it, expect, vi } from 'vitest' +import request from '../request' + +describe('request 工具函数', () => { + it('应该存在', () => { + expect(request).toBeDefined() + }) + + it('应该有 get 方法', () => { + expect(request.get).toBeDefined() + expect(typeof request.get).toBe('function') + }) + + it('应该有 post 方法', () => { + expect(request.post).toBeDefined() + expect(typeof request.post).toBe('function') + }) + + it('应该有 put 方法', () => { + expect(request.put).toBeDefined() + expect(typeof request.put).toBe('function') + }) + + it('应该有 delete 方法', () => { + expect(request.delete).toBeDefined() + expect(typeof request.delete).toBe('function') + }) +}) diff --git a/admin/vite.config.js b/admin/vite.config.js index 1f0e884..da7780f 100644 --- a/admin/vite.config.js +++ b/admin/vite.config.js @@ -1,6 +1,6 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -import { resolve } from 'path' // 👈 添加这一行 +import { resolve } from 'path' export default defineConfig({ plugins: [ @@ -31,5 +31,13 @@ export default defineConfig({ changeOrigin: true } } + }, + test: { + environment: 'jsdom', + include: ['src/**/*.spec.js', 'src/**/*.test.js'], + setupFiles: ['src/setupTests.js'], + coverage: { + reporter: ['text', 'json', 'html'] + } } }) \ No newline at end of file diff --git a/miniprogram/project.config.json b/miniprogram/project.config.json index d3ed3bc..5ed1fd3 100644 --- a/miniprogram/project.config.json +++ b/miniprogram/project.config.json @@ -1,33 +1,52 @@ { - "miniprogramRoot": "unpackage/dist/dev/mp-weixin/", - "projectname": "CampusGuide", - "description": "校园导览小程序", - "appid": "wxe6084d4844b6b9cc", - "setting": { - "urlCheck": false, - "es6": true, - "enhance": true, - "postcss": true, - "minified": true, - "newFeature": true, - "coverView": true, - "nodeModules": false, - "autoAudits": false, - "showShadowRootInWxmlPanel": true, - "scopeDataCheck": false, - "checkInvalidKey": true, - "checkSiteMap": true, - "uploadWithSourceMap": true, - "compileHotReLoad": true, - "useMultiFrameRuntime": true, - "useApiHook": true, - "babelSetting": { - "ignore": [], - "disablePlugins": [], - "outputPath": "" + "miniprogramRoot": "unpackage/dist/dev/mp-weixin/", + "projectname": "CampusGuide", + "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", + "appid": "touristappid", + "setting": { + "urlCheck": false, + "es6": true, + "enhance": true, + "postcss": true, + "minified": true, + "newFeature": true, + "coverView": true, + "nodeModules": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "scopeDataCheck": false, + "checkInvalidKey": true, + "checkSiteMap": true, + "uploadWithSourceMap": true, + "compileHotReLoad": true, + "useMultiFrameRuntime": true, + "useApiHook": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "lazyloadPlaceholderEnable": false, + "preloadBackgroundData": false, + "uglifyFileName": false, + "packNpmManually": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "useStaticServer": true, + "showES6CompileOption": false, + "disableUseStrict": false, + "useCompilerPlugins": false + }, + "compileType": "miniprogram", + "libVersion": "3.7.7", + "condition": {}, + "srcMiniprogramRoot": "unpackage/dist/dev/mp-weixin/", + "packOptions": { + "ignore": [], + "include": [] + }, + "editorSetting": { + "tabIndent": "insertSpaces", + "tabSize": 4 } - }, - "compileType": "miniprogram", - "libVersion": "3.9.3", - "condition": {} -} +} \ No newline at end of file diff --git a/miniprogram/project.private.config.json b/miniprogram/project.private.config.json new file mode 100644 index 0000000..5d292e7 --- /dev/null +++ b/miniprogram/project.private.config.json @@ -0,0 +1,7 @@ +{ + "projectname": "miniprogram", + "setting": { + "compileHotReLoad": true + }, + "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html" +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8ace9f2..5e0a168 100644 --- a/pom.xml +++ b/pom.xml @@ -271,6 +271,7 @@ 1.18.30 + true diff --git a/src/main/java/cn/cxdproject/coder/common/aspect/ServiceLogAspect.java b/src/main/java/cn/cxdproject/coder/common/aspect/ServiceLogAspect.java index a642dd8..8b0232b 100644 --- a/src/main/java/cn/cxdproject/coder/common/aspect/ServiceLogAspect.java +++ b/src/main/java/cn/cxdproject/coder/common/aspect/ServiceLogAspect.java @@ -1,45 +1,207 @@ package cn.cxdproject.coder.common.aspect; +import cn.cxdproject.coder.common.anno.Loggable; +import cn.cxdproject.coder.common.context.AuthContext; +import cn.cxdproject.coder.common.enums.LogType; +import cn.cxdproject.coder.model.entity.OperationLog; +import cn.cxdproject.coder.model.entity.User; +import cn.cxdproject.coder.service.OperationLogService; +import cn.cxdproject.coder.utils.IpUtils; +import cn.cxdproject.coder.utils.JsonUtils; +import cn.cxdproject.coder.utils.StringUtils; +import cn.hutool.core.date.LocalDateTimeUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.context.expression.MethodBasedEvaluationContext; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.Arrays; import java.util.Date; +import java.util.concurrent.CompletableFuture; + -/** - * 服务层日志切面 - * 记录所有Service方法的调用信息 - * - * @author heathcetide - */ -@Slf4j @Aspect @Component +@Slf4j public class ServiceLogAspect { - @Pointcut("execution(* cn.cxdproject.coder.service.*.*(..))") - public void pointcut() { + private OperationLogService operationLogService; + private final ExpressionParser spelParser = new SpelExpressionParser(); + + public ServiceLogAspect(OperationLogService operationLogService) { + this.operationLogService = operationLogService; } - @Before("pointcut()") - public void before(JoinPoint joinPoint) { - // 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()]. - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (attributes == null) { - return; + @Pointcut("@annotation(cn.cxdproject.coder.common.anno.Loggable)") + public void loggablePointcut() { + + } + + @Around("loggablePointcut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Class targetClass = AopProxyUtils.ultimateTargetClass(joinPoint.getTarget()); + method = targetClass.getMethod(method.getName(), method.getParameterTypes()); + Loggable loggable = method.getAnnotation(Loggable.class); + + OperationLog logEntity = new OperationLog(); + logEntity.setTimestamp(LocalDateTime.now()); + logEntity.setDeleted(false); + + // 1. 操作类型 + LogType logType = loggable.type(); + logEntity.setType(logType.getCode()); + + // 2. 操作描述 + String desc = resolveSpel(loggable.value(), method, joinPoint.getArgs()); + logEntity.setDescription(StringUtils.hasText(desc) ? desc : logType.getDescription()); + + // 3. 操作人信息 + User currentUser = AuthContext.getCurrentUser(); + + if (currentUser == null){ + log.warn("未登录"); + logEntity.setUserId(0L); + }else { + logEntity.setUserId(currentUser.getId()); + } + String clientIp = IpUtils.getIpAddr(); + logEntity.setOperator(clientIp); + + + + // ===== 4. 请求参数快照(使用你的 JsonUtils)===== + try { + logEntity.setParams(JsonUtils.toJson(joinPoint.getArgs())); + } catch (Exception e) { + log.warn("序列化操作日志参数失败", e); + logEntity.setParams("[PARAM_SERIALIZE_ERROR]"); } - HttpServletRequest request = attributes.getRequest(); - String ip = request.getRemoteHost(); - String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); - String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(); - log.info("用户[{}],在[{}],访问了[{}]", ip, now, target); + + // ===== 5. 执行目标方法并记录结果 ===== + Object result; + try { + result = joinPoint.proceed(); + logEntity.setSuccess(true); + + // 避免记录大数据量结果 + if (result == null) { + logEntity.setResult("null"); + } else if (isLargeResult(result)) { + // 是大结果 + logEntity.setResult("数据量过大,暂不记录"); + } else { + // 小结果 + logEntity.setResult(safeToJson(result)); + } + } catch (Exception ex) { + logEntity.setSuccess(false); + logEntity.setResult("{\"error\": \"" + escapeJson(ex.getMessage()) + "\"}"); + throw ex; + } + + // ===== 6. 异步保存日志 ===== + saveLogAsync(logEntity); + + return result; + } + + private String resolveSpel(String expression, Method method, Object[] args) { + if (!StringUtils.hasText(expression) || !expression.contains("#{")) { + return expression; + } + + try { + // 使用 Spring 的 MethodBasedEvaluationContext + EvaluationContext context = new MethodBasedEvaluationContext(null, method, args, + new LocalVariableTableParameterNameDiscoverer()); + + ExpressionParser parser = new SpelExpressionParser(); + return parser.parseExpression(expression, new TemplateParserContext()) + .getValue(context, String.class); + } catch (Exception e) { + log.warn("SpEL 解析失败: {}", expression, e); + return expression; // 降级 + } + } + + /** + * 获取方法参数名(需编译时保留参数名:-parameters) + */ + private String[] getParameterNames(Method method) { + return Arrays.stream(method.getParameters()) + .map(Parameter::getName) + .toArray(String[]::new); + } + + /** + * 判断结果是否过大(避免记录分页、列表等) + */ + private boolean isLargeResult(Object result) { + if (result instanceof Iterable) { + return true; + } + if (result.getClass().isArray()) { + return true; + } + // 可根据需要扩展,例如 Map size > 100 + return false; + } + + /** + * 安全地将对象转为 JSON 字符串 + */ + private String safeToJson(Object obj) { + try { + return JsonUtils.toJson(obj); + } catch (Exception e) { + log.warn("结果序列化失败", e); + return "[RESULT_SERIALIZE_ERROR]"; + } + } + + /** + * 转义双引号,用于嵌入 JSON 字符串 + */ + private String escapeJson(String str) { + if (str == null) return ""; + return str.replace("\"", "\\\""); + } + + + protected void saveLogAsync(OperationLog operationLog) { + CompletableFuture.runAsync(() -> { + try { + operationLogService.save(operationLog); + } catch (Exception e) { + log.error("异步保存操作日志失败", e); + } + }); } } diff --git a/src/main/java/cn/cxdproject/coder/common/enums/LogType.java b/src/main/java/cn/cxdproject/coder/common/enums/LogType.java index 10337cf..82b4cf5 100644 --- a/src/main/java/cn/cxdproject/coder/common/enums/LogType.java +++ b/src/main/java/cn/cxdproject/coder/common/enums/LogType.java @@ -1,54 +1,107 @@ package cn.cxdproject.coder.common.enums; +import lombok.Getter; + /** * 通用操作日志类型枚举 * * @author heathcetide */ +@Getter public enum LogType { - USER_LOGIN("用户登录"), - - USER_REGISTER("用户注册"), - - USER_UPDATE("用户信息更新"), - - USER_DELETE("用户删除"), - - USER_LOGOUT("用户退出登录"), - - AUTH("认证与授权"), - - SYSTEM_MESSAGE("系统管理"), - - USER_MANAGEMENT("用户管理"), - - DATA_MANAGEMENT("数据管理"), - - CONFIGURATION("系统配置"), - - MONITORING("系统监控"), - - SECURITY("安全审计"), - - API_CALL("接口调用"), - - ORG_MODULE("组织接口调用"), - - TASK_EXECUTION("任务执行"), - - THIRD_PARTY("第三方服务"), - - OTHER("其他"); - + // 用户相关 + USER_LOGIN("USER_LOGIN", "用户登录"), + USER_LOGOUT("USER_LOGOUT", "用户登出"), + USER_CREATE("USER_CREATE", "创建用户"), + USER_UPDATE("USER_UPDATE", "更新用户信息"), + USER_DELETE("USER_DELETE", "删除用户"), + AVATAR_UPDATE("AVATAR_UPDATE","用户更新头像"), + + // 文章内容相关 + ARTICLE_GET("ARTICLE_GET", "根据id查找文章"), + ARTICLE_ADMIN_GET_PAGE("ARTICLE_ADMIN_GET_PAGE", "管理员分页查询"), + ARTICLE_USER_GET_PAGE("ARTICLE_USER_GET_PAGE", "用户分页查询"), + ARTICLE_UPDATE("ARTICLE_UPDATE", "更新文章"), + ARTICLE_DELETE("ARTICLE_DELETE","删除文章"), + ARTICLE_CREATE("ARTICLE_CREATE","新建文章"), + + // 系统操作 + FILE_UPLOAD("FILE_UPLOAD", "上传文件"), + + //影视相关操作 + DRAMA_GET("DRAMA_GET", "根据ID查找影视"), + DRAMA_ADMIN_GET_PAGE("DRAMA_ADMIN_GET_PAGE", "管理员分页查询影视"), + DRAMA_USER_GET_PAGE("DRAMA_USER_GET_PAGE", "用户分页查询影视"), + DRAMA_UPDATE("DRAMA_UPDATE", "更新影视"), + DRAMA_DELETE("DRAMA_DELETE", "删除影视"), + DRAMA_CREATE("DRAMA_CREATE", "新建影视"), + + //酒店相关操作 + HOTEL_GET("HOTEL_GET", "根据ID查找酒店"), + HOTEL_ADMIN_GET_PAGE("HOTEL_ADMIN_GET_PAGE", "管理员分页查询酒店"), + HOTEL_USER_GET_PAGE("HOTEL_USER_GET_PAGE", "用户分页查询酒店"), + HOTEL_UPDATE("HOTEL_UPDATE", "更新酒店"), + HOTEL_DELETE("HOTEL_DELETE", "删除酒店"), + HOTEL_CREATE("HOTEL_CREATE", "新建酒店"), + + //场地相关操作 + LOCATION_GET("LOCATION_GET", "根据ID查找场地"), + LOCATION_ADMIN_GET_PAGE("LOCATION_ADMIN_GET_PAGE", "管理员分页查询场地"), + LOCATION_USER_GET_PAGE("LOCATION_USER_GET_PAGE", "用户分页查询场地"), + LOCATION_UPDATE("LOCATION_UPDATE", "更新场地"), + LOCATION_DELETE("LOCATION_DELETE", "删除场地"), + LOCATION_CREATE("LOCATION_CREATE", "新建场地"), + + //政策相关操作 + POLICY_GET("POLICY_GET", "根据ID查找政策"), + POLICY_ADMIN_GET_PAGE("POLICY_ADMIN_GET_PAGE", "管理员分页查询政策"), + POLICY_USER_GET_PAGE("POLICY_USER_GET_PAGE", "用户分页查询政策"), + POLICY_UPDATE("POLICY_UPDATE", "更新政策"), + POLICY_DELETE("POLICY_DELETE", "删除政策"), + POLICY_CREATE("POLICY_CREATE", "新建政策"), + + //申请相关操作 + REPORT_USER_CREATE("REPORT_USER_CREATE", "用户提交申请"), + REPORT_USER_UPDATE("REPORT_USER_UPDATE", "用户更新申请"), + REPORT_USER_DELETE("REPORT_USER_DELETE", "用户撤销申请"), + REPORT_USER_GET("REPORT_USER_GET", "用户根据ID查询申请"), + REPORT_USER_GET_PAGE("REPORT_USER_GET_PAGE", "查询自己的全部申请"), + REPORT_ADMIN_UPDATE("REPORT_ADMIN_UPDATE", "管理员处理申请"), + REPORT_ADMIN_GET("REPORT_ADMIN_GET", "管理员根据ID查询申请"), + REPORT_ADMIN_GET_PAGE("REPORT_ADMIN_GET_PAGE", "管理员分页查询申请"), + + //协拍服务相关操作 + SHOOT_GET("SHOOT_GET", "根据ID查找协拍"), + SHOOT_ADMIN_GET_PAGE("SHOOT_ADMIN_GET_PAGE", "管理员分页查询协拍"), + SHOOT_USER_GET_PAGE("SHOOT_USER_GET_PAGE", "用户分页查询协拍"), + SHOOT_UPDATE("SHOOT_UPDATE", "更新协拍"), + SHOOT_DELETE("SHOOT_DELETE", "删除协拍"), + SHOOT_CREATE("SHOOT_CREATE", "新建协拍"), + + //旅游路线相关服务 + TOUR_GET("TOUR_GET", "根据ID查找旅游路线"), + TOUR_ADMIN_GET_PAGE("TOUR_ADMIN_GET_PAGE", "管理员分页查询旅游路线"), + TOUR_USER_GET_PAGE("TOUR_USER_GET_PAGE", "用户分页查询旅游路线"), + TOUR_UPDATE("TOUR_UPDATE", "更新旅游路线"), + TOUR_DELETE("TOUR_DELETE", "删除旅游路线"), + TOUR_CREATE("TOUR_CREATE", "新建旅游路线"), + ; + + /** + * 存入数据库的 code(建议用大写下划线或驼峰,保持一致性) + */ + private final String code; + + /** + * 默认描述(当 @Loggable.value() 未指定时使用) + */ private final String description; - LogType(String description) { + LogType(String code, String description) { + this.code = code; this.description = description; } - public String getDescription() { - return description; - } } diff --git a/src/main/java/cn/cxdproject/coder/config/WebConfig.java b/src/main/java/cn/cxdproject/coder/config/WebConfig.java index fcf36cd..811ec94 100644 --- a/src/main/java/cn/cxdproject/coder/config/WebConfig.java +++ b/src/main/java/cn/cxdproject/coder/config/WebConfig.java @@ -93,9 +93,10 @@ public void addInterceptors(InterceptorRegistry registry) { // 政策模块管理员接口 .addPathPatterns("/api/policy/admin/**") //banner模块接口 - . addPathPatterns("/api/banner/**"); - registry.addInterceptor(registrationInterceptor) - .addPathPatterns("/api/admin/register/email"); + .addPathPatterns("/api/banner/**") + //操作日志相关接口 + .addPathPatterns("/api/operationlog/admin/**"); + registry.addInterceptor(registrationInterceptor); } @Bean diff --git a/src/main/java/cn/cxdproject/coder/controller/ArticleController.java b/src/main/java/cn/cxdproject/coder/controller/ArticleController.java index 296a489..3301e93 100644 --- a/src/main/java/cn/cxdproject/coder/controller/ArticleController.java +++ b/src/main/java/cn/cxdproject/coder/controller/ArticleController.java @@ -95,6 +95,7 @@ public ApiResponse createArticleByAdmin(@Valid @RequestBody CreateArt return ApiResponse.success(articleVO); } + //管理员文章分页 @GetMapping("/admin/page") public PageResponse getArticlePageAdmin( @RequestParam(defaultValue = "1") Integer current, @@ -102,7 +103,7 @@ public PageResponse getArticlePageAdmin( @RequestParam(required = false) String keyword) { Page
page = new Page<>(current, size); - Page articlePage = articleService.getArticlePagAdmine(page, keyword); + Page articlePage = articleService.getArticlePagAdmin(page, keyword); return PageResponse.of( (int) articlePage.getCurrent(), diff --git a/src/main/java/cn/cxdproject/coder/controller/DramaController.java b/src/main/java/cn/cxdproject/coder/controller/DramaController.java index eed0712..682d331 100644 --- a/src/main/java/cn/cxdproject/coder/controller/DramaController.java +++ b/src/main/java/cn/cxdproject/coder/controller/DramaController.java @@ -81,9 +81,6 @@ public CursorPageResponseVO page( return new CursorPageResponseVO<>(list, nextCursor); } - // ==================== 普通用户接口 ==================== - - // ==================== 管理员接口 ==================== /** @@ -97,6 +94,7 @@ public ApiResponse updateDramaByAdmin( return ApiResponse.success(dramaVO); } + //管理员分页 @GetMapping("/admin/page") public PageResponse getDramaPageAdmin( @RequestParam(defaultValue = "1") Integer current, diff --git a/src/main/java/cn/cxdproject/coder/controller/HotelController.java b/src/main/java/cn/cxdproject/coder/controller/HotelController.java index 0363659..dc6af4a 100644 --- a/src/main/java/cn/cxdproject/coder/controller/HotelController.java +++ b/src/main/java/cn/cxdproject/coder/controller/HotelController.java @@ -73,21 +73,7 @@ public CursorPageResponseVO page( return new CursorPageResponseVO<>(list, nextCursor); } - // ==================== 管理员接口 ==================== - - /** - * 管理员创建旅店信息 - */ - @PostMapping("/admin/create") - public ApiResponse createHotelByAdmin(@Valid @RequestBody CreateHotelDTO createDTO) { - User currentUser = AuthContext.getCurrentUser(); - if (currentUser == null) { - return ApiResponse.error(401, "未登录"); - } - HotelVO hotelVO = hotelService.createHotelByAdmin(currentUser.getId(),createDTO); - return ApiResponse.success(hotelVO); - } - + //用户分页查询(游标分页) @GetMapping("/admin/page") public PageResponse getHotelPageAdmin( @RequestParam(defaultValue = "1") Integer current, @@ -105,6 +91,23 @@ public PageResponse getHotelPageAdmin( ); } + + // ==================== 管理员接口 ==================== + + /** + * 管理员创建旅店信息 + */ + @PostMapping("/admin/create") + public ApiResponse createHotelByAdmin(@Valid @RequestBody CreateHotelDTO createDTO) { + User currentUser = AuthContext.getCurrentUser(); + if (currentUser == null) { + return ApiResponse.error(401, "未登录"); + } + HotelVO hotelVO = hotelService.createHotelByAdmin(currentUser.getId(),createDTO); + return ApiResponse.success(hotelVO); + } + + /** * 管理员更新旅店 */ diff --git a/src/main/java/cn/cxdproject/coder/controller/LocationController.java b/src/main/java/cn/cxdproject/coder/controller/LocationController.java index 817f90d..68fdc98 100644 --- a/src/main/java/cn/cxdproject/coder/controller/LocationController.java +++ b/src/main/java/cn/cxdproject/coder/controller/LocationController.java @@ -81,7 +81,6 @@ public CursorPageResponseVO page( return new CursorPageResponseVO<>(list, nextCursor); } - // ==================== 普通用户接口 ==================== // ==================== 管理员接口 ==================== @@ -99,6 +98,7 @@ public ApiResponse createLocation(@Valid @RequestBody CreateLocation return ApiResponse.success(locationVO); } + //管理员分页 @GetMapping("/admin/page") public PageResponse getLocationPageAdmin( @RequestParam(defaultValue = "1") Integer current, diff --git a/src/main/java/cn/cxdproject/coder/controller/OperationLogController.java b/src/main/java/cn/cxdproject/coder/controller/OperationLogController.java index a09ca9a..d174308 100644 --- a/src/main/java/cn/cxdproject/coder/controller/OperationLogController.java +++ b/src/main/java/cn/cxdproject/coder/controller/OperationLogController.java @@ -1,6 +1,12 @@ package cn.cxdproject.coder.controller; +import cn.cxdproject.coder.common.PageResponse; +import cn.cxdproject.coder.common.anno.PublicAccess; +import cn.cxdproject.coder.model.dto.UpdateArticleDTO; +import cn.cxdproject.coder.model.entity.Article; import cn.cxdproject.coder.model.entity.OperationLog; +import cn.cxdproject.coder.model.vo.ArticleVO; +import cn.cxdproject.coder.model.vo.OperationLogVO; import cn.cxdproject.coder.service.OperationLogService; import cn.cxdproject.coder.common.ApiResponse; import cn.cxdproject.coder.common.PageRequest; @@ -8,6 +14,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; import java.util.List; /** @@ -24,74 +32,41 @@ public OperationLogController(OperationLogService operationLogService) { this.operationLogService = operationLogService; } - /** - * 新增 OperationLog 记录 - * @param entity 实体对象 - * @return 是否新增成功 - */ - @PostMapping - public ApiResponse add(@RequestBody OperationLog entity) { - return ApiResponse.success(operationLogService.save(entity)); - } + //分页获取操作信息 + @GetMapping("/admin/page") + public PageResponse getOperationPageAdmin( + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String keyword) { - /** - * 更新 OperationLog 记录 - * @param entity 实体对象(必须包含主键 ID) - * @return 是否更新成功 - */ - @PutMapping - public ApiResponse update(@RequestBody OperationLog entity) { - return ApiResponse.success(operationLogService.updateById(entity)); - } + Page page = new Page<>(current, size); + Page operationPage = operationLogService.getOperationPage(page, keyword); - /** - * 删除指定 ID 的 OperationLog 记录 - * @param id 主键 ID - * @return 是否删除成功 - */ - @DeleteMapping("/{id}") - public ApiResponse delete(@PathVariable("id") Integer id) { - return ApiResponse.success(operationLogService.removeById(id)); + return PageResponse.of( + (int) operationPage.getCurrent(), + (int) operationPage.getSize(), + operationPage.getTotal(), + operationPage.getRecords() + ); } - /** - * 根据 ID 获取 OperationLog 详情 - * @param id 主键 ID - * @return 匹配的实体对象 - */ - @GetMapping("/{id}") - public ApiResponse getById(@PathVariable("id") Integer id) { - return ApiResponse.success(operationLogService.getById(id)); + //根据id获取操作信息 + @GetMapping("/admin/{id}") + @PublicAccess + public ApiResponse getOperationById(@PathVariable @NotNull(message = "记录ID不能为空") Long id) { + OperationLogVO operationLogVO = operationLogService.getOperationById(id); + return ApiResponse.success(operationLogVO); } - /** - * 获取所有 OperationLog 列表(不分页) - * @return 实体列表 - */ - @GetMapping - public ApiResponse> list() { - return ApiResponse.success(operationLogService.list()); - } /** - * 分页查询 OperationLog 列表 - * 支持关键字模糊搜索与排序 - * @param pageRequest 分页与筛选请求参数 - * @return 分页结果 + * 管理员删除文章 */ - @PostMapping("/page") - public ApiResponse> getPage(@RequestBody PageRequest pageRequest) { - Page page = new Page<>(pageRequest.getPage(), pageRequest.getSize()); - QueryWrapper wrapper = new QueryWrapper<>(); - - if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) { - wrapper.like("name", pageRequest.getKeyword()); // 可自定义字段 - } + @DeleteMapping("/admin/delete/{id}") + public ApiResponse deleteOperation(@PathVariable @NotNull(message = "记录ID不能为空") Long id) { + operationLogService.deleteArticle(id); + return ApiResponse.success(); + } - if (pageRequest.getSortBy() != null && !pageRequest.getSortBy().isEmpty()) { - wrapper.orderBy(true, "asc".equalsIgnoreCase(pageRequest.getSortOrder()), pageRequest.getSortBy()); - } - return ApiResponse.success(operationLogService.page(page, wrapper)); - } } diff --git a/src/main/java/cn/cxdproject/coder/controller/PolicyController.java b/src/main/java/cn/cxdproject/coder/controller/PolicyController.java index 0382d4c..4b2c796 100644 --- a/src/main/java/cn/cxdproject/coder/controller/PolicyController.java +++ b/src/main/java/cn/cxdproject/coder/controller/PolicyController.java @@ -77,7 +77,6 @@ public CursorPageResponseVO page( return new CursorPageResponseVO<>(list, nextCursor); } - // ==================== 普通用户接口 ==================== // ==================== 管理员接口 ==================== @@ -95,6 +94,7 @@ public ApiResponse createPolicy(@Valid @RequestBody CreatePolicyDTO cr return ApiResponse.success(policyVO); } + //管理员分页查询 @GetMapping("/admin/page") public PageResponse getPolicyPageAdmin( @RequestParam(defaultValue = "1") Integer current, diff --git a/src/main/java/cn/cxdproject/coder/controller/ReportController.java b/src/main/java/cn/cxdproject/coder/controller/ReportController.java index 3cf1b49..b0041e4 100644 --- a/src/main/java/cn/cxdproject/coder/controller/ReportController.java +++ b/src/main/java/cn/cxdproject/coder/controller/ReportController.java @@ -2,10 +2,10 @@ import cn.cxdproject.coder.common.ApiResponse; import cn.cxdproject.coder.common.PageResponse; -import cn.cxdproject.coder.common.anno.PublicAccess; import cn.cxdproject.coder.common.context.AuthContext; import cn.cxdproject.coder.model.dto.CreateReportDTO; -import cn.cxdproject.coder.model.dto.UpdateReportDTO; +import cn.cxdproject.coder.model.dto.UpdateReportAdminDTO; +import cn.cxdproject.coder.model.dto.UpdateReportUserDTO; import cn.cxdproject.coder.model.entity.Report; import cn.cxdproject.coder.model.entity.User; import cn.cxdproject.coder.model.vo.ReportVO; @@ -69,7 +69,7 @@ public ApiResponse createReport(@Valid @RequestBody CreateReportDTO cr @PutMapping("/update/{id}") public ApiResponse updateReport( @PathVariable @NotNull(message = "ID不能为空") Long id, - @Valid @RequestBody UpdateReportDTO updateDTO) { + @Valid @RequestBody UpdateReportUserDTO updateDTO) { User currentUser = AuthContext.getCurrentUser(); if (currentUser == null) { return ApiResponse.error(401, "未登录"); @@ -128,7 +128,7 @@ public PageResponse getMyReportPage( @PutMapping("/admin/update/{id}") public ApiResponse updateReportByAdmin( @PathVariable @NotNull(message = "ID不能为空") Long id, - @Valid @RequestBody UpdateReportDTO updateDTO) { + @Valid @RequestBody UpdateReportAdminDTO updateDTO) { ReportVO reportVO = reportService.updateReportByAdmin(id, updateDTO); return ApiResponse.success(reportVO); } diff --git a/src/main/java/cn/cxdproject/coder/mapper/ArticleMapper.java b/src/main/java/cn/cxdproject/coder/mapper/ArticleMapper.java index 5ef1323..a01b214 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/ArticleMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/ArticleMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdateArticleDTO; import cn.cxdproject.coder.model.entity.Banner; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import cn.cxdproject.coder.model.entity.Article; @@ -30,4 +31,9 @@ List
getAdminPage( List
selectLatest10(); + int updateArticle( + @Param("articleId") Long articleId, + @Param("dto") UpdateArticleDTO updateArticleDTO + ); + } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/mapper/DramaMapper.java b/src/main/java/cn/cxdproject/coder/mapper/DramaMapper.java index 4c8aac7..b9b82b5 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/DramaMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/DramaMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdateDramaDTO; import cn.cxdproject.coder.model.entity.Article; import cn.cxdproject.coder.model.entity.Banner; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -27,7 +28,11 @@ List getAdminPage( ); - List selectLatest10(); + int updateDrama( + @Param("dramaId") Long dramaId, + @Param("dto") UpdateDramaDTO dto + ); + } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/mapper/HotelMapper.java b/src/main/java/cn/cxdproject/coder/mapper/HotelMapper.java index e441d48..cbf8bdc 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/HotelMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/HotelMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdateHotelDTO; import cn.cxdproject.coder.model.entity.Banner; import cn.cxdproject.coder.model.entity.Hotel; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -21,4 +22,8 @@ List getAdminPage( ); List selectLatest10(); + + int updateHotel(@Param("id") Long id, + @Param("dto") UpdateHotelDTO dto + ); } diff --git a/src/main/java/cn/cxdproject/coder/mapper/LocationMapper.java b/src/main/java/cn/cxdproject/coder/mapper/LocationMapper.java index e05b5f9..847e248 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/LocationMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/LocationMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdateLocationDTO; import cn.cxdproject.coder.model.entity.Article; import cn.cxdproject.coder.model.entity.Banner; import cn.cxdproject.coder.model.entity.Drama; @@ -27,8 +28,10 @@ List getAdminPage( @Param("size") long size ); - - - List selectLatest10(); + + int updateLocation( + @Param("id") Long id, + @Param("dto") UpdateLocationDTO dto + ); } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/mapper/OperationLogMapper.java b/src/main/java/cn/cxdproject/coder/mapper/OperationLogMapper.java index 9afbe9e..59a9331 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/OperationLogMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/OperationLogMapper.java @@ -1,8 +1,12 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.entity.Banner; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import cn.cxdproject.coder.model.entity.OperationLog; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** * OperationLog Mapper 接口 @@ -11,4 +15,10 @@ @Mapper public interface OperationLogMapper extends BaseMapper { + List getPage( + @Param("keyword") String keyword, + @Param("offset") long offset, + @Param("size") long size + ); + } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/mapper/PolicyMapper.java b/src/main/java/cn/cxdproject/coder/mapper/PolicyMapper.java index 8c28e6a..274a579 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/PolicyMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/PolicyMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdatePolicyDTO; import cn.cxdproject.coder.model.entity.Banner; import cn.cxdproject.coder.model.entity.Location; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -25,7 +26,11 @@ List getAdminPage( @Param("size") long size ); - List selectLatest10(); + int updatePolicy( + @Param("id") Long id, + @Param("dto") UpdatePolicyDTO dto + ); + } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/mapper/ReportMapper.java b/src/main/java/cn/cxdproject/coder/mapper/ReportMapper.java index 5a239d0..7995a2b 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/ReportMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/ReportMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdateReportUserDTO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import cn.cxdproject.coder.model.entity.Report; import org.apache.ibatis.annotations.Mapper; @@ -26,5 +27,15 @@ List selectPageByAdmin( @Param("size") long size ); + int updateReportByUser( + @Param("id") Long id, + @Param("dto") UpdateReportUserDTO dto + ); + + int updateReportByAdmin( + @Param("id") Long id, + @Param("status") String status + ); + } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/mapper/ShootMapper.java b/src/main/java/cn/cxdproject/coder/mapper/ShootMapper.java index c0de075..5e31730 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/ShootMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/ShootMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdateShootDTO; import cn.cxdproject.coder.model.entity.Banner; import cn.cxdproject.coder.model.entity.Drama; import cn.cxdproject.coder.model.entity.Location; @@ -27,7 +28,11 @@ List getAdminPage( @Param("size") long size ); - List selectLatest10(); + int updateShoot( + @Param("id") Long id, + @Param("dto") UpdateShootDTO dto + ); + } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/mapper/TourMapper.java b/src/main/java/cn/cxdproject/coder/mapper/TourMapper.java index a4fca5c..d0ad1a4 100644 --- a/src/main/java/cn/cxdproject/coder/mapper/TourMapper.java +++ b/src/main/java/cn/cxdproject/coder/mapper/TourMapper.java @@ -1,5 +1,6 @@ package cn.cxdproject.coder.mapper; +import cn.cxdproject.coder.model.dto.UpdateTourDTO; import cn.cxdproject.coder.model.entity.Banner; import cn.cxdproject.coder.model.entity.Shoot; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -28,4 +29,9 @@ List getAdminPage( List selectLatest10(); + int updateTour( + @Param("id") Long id, + @Param("dto") UpdateTourDTO dto + ); + } \ No newline at end of file diff --git a/src/main/java/cn/cxdproject/coder/model/dto/UpdateReportAdminDTO.java b/src/main/java/cn/cxdproject/coder/model/dto/UpdateReportAdminDTO.java new file mode 100644 index 0000000..e4ec377 --- /dev/null +++ b/src/main/java/cn/cxdproject/coder/model/dto/UpdateReportAdminDTO.java @@ -0,0 +1,8 @@ +package cn.cxdproject.coder.model.dto; + +import lombok.Data; + +@Data +public class UpdateReportAdminDTO { + private String status; +} diff --git a/src/main/java/cn/cxdproject/coder/model/dto/UpdateReportDTO.java b/src/main/java/cn/cxdproject/coder/model/dto/UpdateReportUserDTO.java similarity index 93% rename from src/main/java/cn/cxdproject/coder/model/dto/UpdateReportDTO.java rename to src/main/java/cn/cxdproject/coder/model/dto/UpdateReportUserDTO.java index 4c5b443..629060e 100644 --- a/src/main/java/cn/cxdproject/coder/model/dto/UpdateReportDTO.java +++ b/src/main/java/cn/cxdproject/coder/model/dto/UpdateReportUserDTO.java @@ -11,7 +11,7 @@ * @author heathcetide */ @Data -public class UpdateReportDTO { +public class UpdateReportUserDTO { private String name; private String type; @@ -27,7 +27,6 @@ public class UpdateReportDTO { private String contact; private String phoneNumber; private String crewPosition; - private String status; private String shootPermit; private String thumbShootPermit; private String approvalFile; diff --git a/src/main/java/cn/cxdproject/coder/model/vo/OperationLogVO.java b/src/main/java/cn/cxdproject/coder/model/vo/OperationLogVO.java new file mode 100644 index 0000000..f145663 --- /dev/null +++ b/src/main/java/cn/cxdproject/coder/model/vo/OperationLogVO.java @@ -0,0 +1,60 @@ +package cn.cxdproject.coder.model.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class OperationLogVO { + + private Long id; + + /** + * 操作类型(如:LOGIN、DOC_EDIT) + */ + private String type; + + /** + * 操作描述 + */ + private String description; + + /** + * 执行用户ID + */ + private Long userId; + + /** + * 操作人标识(用户名或IP) + */ + private String operator; + + /** + * 是否成功 + */ + private Boolean success; + + /** + * 输入参数快照 + */ + private String params; + + /** + * 输出结果快照 + */ + private String result; + + /** + * 操作时间 + */ + private LocalDateTime timestamp; + +} diff --git a/src/main/java/cn/cxdproject/coder/service/ArticleService.java b/src/main/java/cn/cxdproject/coder/service/ArticleService.java index c7dbc71..9ab0784 100644 --- a/src/main/java/cn/cxdproject/coder/service/ArticleService.java +++ b/src/main/java/cn/cxdproject/coder/service/ArticleService.java @@ -70,5 +70,5 @@ public interface ArticleService extends IService
{ List getPageFallback(Long lastId, int size, String keyword, Throwable e); - Page getArticlePagAdmine(Page
page, String keyword); + Page getArticlePagAdmin(Page
page, String keyword); } diff --git a/src/main/java/cn/cxdproject/coder/service/OperationLogService.java b/src/main/java/cn/cxdproject/coder/service/OperationLogService.java index b602817..227a2c7 100644 --- a/src/main/java/cn/cxdproject/coder/service/OperationLogService.java +++ b/src/main/java/cn/cxdproject/coder/service/OperationLogService.java @@ -1,5 +1,9 @@ package cn.cxdproject.coder.service; +import cn.cxdproject.coder.model.entity.Location; +import cn.cxdproject.coder.model.vo.LocationVO; +import cn.cxdproject.coder.model.vo.OperationLogVO; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import cn.cxdproject.coder.model.entity.OperationLog; @@ -9,4 +13,11 @@ */ public interface OperationLogService extends IService { + OperationLogVO getOperationById(Long id); + + void deleteArticle(Long id); + + Page getOperationPage(Page page, String keyword); + + OperationLogVO toOperationVO(OperationLog operationLog); } diff --git a/src/main/java/cn/cxdproject/coder/service/ReportService.java b/src/main/java/cn/cxdproject/coder/service/ReportService.java index 8b93098..ac81cc6 100644 --- a/src/main/java/cn/cxdproject/coder/service/ReportService.java +++ b/src/main/java/cn/cxdproject/coder/service/ReportService.java @@ -1,7 +1,8 @@ package cn.cxdproject.coder.service; import cn.cxdproject.coder.model.dto.CreateReportDTO; -import cn.cxdproject.coder.model.dto.UpdateReportDTO; +import cn.cxdproject.coder.model.dto.UpdateReportAdminDTO; +import cn.cxdproject.coder.model.dto.UpdateReportUserDTO; import cn.cxdproject.coder.model.entity.Report; import cn.cxdproject.coder.model.vo.ReportVO; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -14,11 +15,11 @@ public interface ReportService extends IService { ReportVO createReport(Long userId, CreateReportDTO createDTO); - ReportVO updateReport(Long userId, Long reportId, UpdateReportDTO updateDTO); + ReportVO updateReport(Long userId, Long reportId, UpdateReportUserDTO updateDTO); void deleteReport(Long userId, Long reportId); ReportVO getReportById(Long reportId,Long userId); Page getMyReportPage(Long userId, Page page); - ReportVO updateReportByAdmin(Long reportId, UpdateReportDTO updateDTO); + ReportVO updateReportByAdmin(Long reportId, UpdateReportAdminDTO updateDTO); Page getReportPageByAdmin(Page page, String keyword); ReportVO getReportByIdByAdmin(Long reportId); ReportVO toReportVO(Report report); diff --git a/src/main/java/cn/cxdproject/coder/service/impl/ArticleServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/ArticleServiceImpl.java index 46a35bf..89ed4a1 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/ArticleServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/ArticleServiceImpl.java @@ -1,7 +1,9 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.*; import cn.cxdproject.coder.common.context.AuthContext; +import cn.cxdproject.coder.common.enums.LogType; import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.model.dto.CreateArticleDTO; @@ -59,6 +61,10 @@ public ArticleServiceImpl( @Override @CircuitBreaker(name = "articleGetById", fallbackMethod = "getByIdFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.ARTICLE_GET, + value = "Get article by ID: #{#articleId}" + ) public ArticleVO getArticleById(Long articleId) { Object store = cache.getIfPresent(CaffeineConstants.ARTICLE + articleId); if (store != null) { @@ -68,7 +74,7 @@ public ArticleVO getArticleById(Long articleId) { if (article == null || Boolean.TRUE.equals(article.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - cache.asMap().put(CaffeineConstants.ARTICLE + articleId, article); + cache.put(CaffeineConstants.ARTICLE + articleId, article); return toArticleVO(article); } } @@ -77,6 +83,10 @@ public ArticleVO getArticleById(Long articleId) { @Override @CircuitBreaker(name = "articleGetPage", fallbackMethod = "getPageFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.ARTICLE_USER_GET_PAGE, + value = "User get page " + ) public List getArticlePage(Long lastId, int size, String keyword) { List ids = articleMapper.selectIds(lastId, size, keyword); if (ids.isEmpty()) { @@ -96,7 +106,10 @@ public List getArticlePage(Long lastId, int size, String keyword) { } @Override - @Bulkhead(name = "add", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.ARTICLE_CREATE, + value = "Create article " + ) public ArticleVO createArticleByAdmin(CreateArticleDTO createDTO) { // 获取当前管理员用户 User currentUser = AuthContext.getCurrentUser(); @@ -124,26 +137,40 @@ public ArticleVO createArticleByAdmin(CreateArticleDTO createDTO) { } @Override - @Bulkhead(name = "update", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.ARTICLE_UPDATE, + value = "update article ID: #{#articleId}" + ) public ArticleVO updateArticleByAdmin(Long articleId, UpdateArticleDTO updateDTO) { - Article article = this.getById(articleId); - if (article == null || Boolean.TRUE.equals(article.getDeleted())) { + // 1. 先校验文章是否存在且未删除(必须做,用于抛出 NotFoundException) + Article existing = this.getById(articleId); + if (existing == null || Boolean.TRUE.equals(existing.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getTitle() != null) article.setTitle(updateDTO.getTitle()); - if (updateDTO.getIssueUnit() != null) article.setIssueUnit(updateDTO.getIssueUnit()); - if (updateDTO.getContent() != null) article.setContent(updateDTO.getContent()); - if (updateDTO.getThumbImage() != null) article.setThumbImage(updateDTO.getThumbImage()); + // 2. 执行动态更新(只更新非空字段) + int updated = articleMapper.updateArticle(articleId,updateDTO); + // 3. 如果更新影响行数为 0,说明条件不满足(比如已被删除),也可视为未找到 + if (updated == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } - cache.asMap().put(CaffeineConstants.ARTICLE + articleId, article); - this.updateById(article); - return toArticleVO(article); + // 4. 重新加载最新数据 + Article updatedArticle = this.getById(articleId); + + // 5. 更新缓存 + cache.put(CaffeineConstants.ARTICLE + articleId, updatedArticle); + + // 6. 返回 VO + return toArticleVO(updatedArticle); } @Override - @Bulkhead(name = "delete", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.ARTICLE_DELETE, + value = "Delete article ID: #{#articleId}" + ) public void deleteArticleByAdmin(Long articleId) { boolean updated = articleMapper.update(null, Wrappers.
lambdaUpdate() @@ -183,22 +210,26 @@ public ArticleVO toArticleVO(Article article) { @Override public ArticleVO getByIdFallback(Long articleId,Throwable e) { - Object store; - store = cache.getIfPresent(CaffeineConstants.ARTICLE + articleId); - if (store != null) { - return toArticleVO((Article) store); + + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; } - store = redisUtils.get(RedisKeyConstants.ARTICLE+articleId); + + Object store = cache.getIfPresent(CaffeineConstants.ARTICLE + articleId); if (store != null) { - Article article = JsonUtils.fromJson((String) store, Article.class); - return toArticleVO(article); + return toArticleVO((Article) store); } + return null; } @Override public List getPageFallback(Long lastId, int size, String keyword, Throwable e) { + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; + } + try { // 从 Redis 获取缓存的全量文章(假设是 ArticleVO[] 的 JSON) String json = (String) redisUtils.get(TaskConstants.ARTICLE); @@ -221,7 +252,12 @@ public List getPageFallback(Long lastId, int size, String keyword, Th } @Override - public Page getArticlePagAdmine(Page
page, String keyword) { + @Loggable( + type = LogType.ARTICLE_ADMIN_GET_PAGE, + value = "Admin get article page" + ) + public Page getArticlePagAdmin(Page
page, String keyword) { + long current = page.getCurrent(); long size = page.getSize(); long offset = (current - 1) * size; diff --git a/src/main/java/cn/cxdproject/coder/service/impl/BannerServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/BannerServiceImpl.java index 2686934..6e84b3d 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/BannerServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/BannerServiceImpl.java @@ -85,7 +85,6 @@ public BannerVO createImage(CreateBannerDTO createDTO) { .status(true) .build(); - // 保存文章 this.save(banner); return toBannerVO(banner); } diff --git a/src/main/java/cn/cxdproject/coder/service/impl/DramaServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/DramaServiceImpl.java index 8abbcb0..831a701 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/DramaServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/DramaServiceImpl.java @@ -1,6 +1,8 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.*; +import cn.cxdproject.coder.common.enums.LogType; import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.model.dto.CreateDramaDTO; @@ -51,7 +53,10 @@ public DramaServiceImpl(DramaMapper dramaMapper, @Override - @Bulkhead(name = "add", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.DRAMA_CREATE, + value = "create drama" + ) public DramaVO createDramaByAdmin(Long userId, CreateDramaDTO createDTO) { if (createDTO.getImage() == null) { createDTO.setImage(Constants.DEFAULT_COVER); @@ -81,6 +86,10 @@ public DramaVO createDramaByAdmin(Long userId, CreateDramaDTO createDTO) { @Override @CircuitBreaker(name = "dramaGetById", fallbackMethod = "getByIdFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.DRAMA_GET, + value = "Get drama by ID: #{#dramaId}" + ) public DramaVO getDramaById(Long dramaId) { Object store = cache.asMap().get(CaffeineConstants.DRAMA + dramaId); if (store != null) { @@ -90,7 +99,7 @@ public DramaVO getDramaById(Long dramaId) { if (drama == null || Boolean.TRUE.equals(drama.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - cache.asMap().put(CaffeineConstants.DRAMA + dramaId, drama); + cache.put(CaffeineConstants.DRAMA + dramaId, drama); return toDramaVO(drama); } } @@ -98,6 +107,10 @@ public DramaVO getDramaById(Long dramaId) { @Override @CircuitBreaker(name = "dramaGetPage", fallbackMethod = "getPageFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.DRAMA_USER_GET_PAGE, + value = "User get drama page" + ) public List getDramaPage(Long lastId, int size, String keyword) { List ids = dramaMapper.selectIds(lastId, size, keyword); if (ids.isEmpty()) { @@ -118,32 +131,40 @@ public List getDramaPage(Long lastId, int size, String keyword) { } @Override + @Loggable( + type = LogType.DRAMA_UPDATE, + value = "Update drama ID: #{#dramaId}" + ) public DramaVO updateDramaByAdmin(Long dramaId, UpdateDramaDTO updateDTO) { - Drama drama = this.getById(dramaId); - if (drama == null || Boolean.TRUE.equals(drama.getDeleted())) { + // 先校验文章是否存在且未被删除 + Drama existing = this.getById(dramaId); + if (existing == null || Boolean.TRUE.equals(existing.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getName() != null) drama.setName(updateDTO.getName()); - if (updateDTO.getFilingNum() != null) drama.setFilingNum(updateDTO.getFilingNum()); - if (updateDTO.getProdCompany() != null) drama.setProdCompany(updateDTO.getProdCompany()); - if (updateDTO.getCrewDescription() != null) drama.setCrewDescription(updateDTO.getCrewDescription()); - if (updateDTO.getDramaDescription() != null) drama.setDramaDescription(updateDTO.getDramaDescription()); - if (updateDTO.getCast() != null) drama.setCast(updateDTO.getCast()); - if (updateDTO.getShootLocation() != null) drama.setShootLocation(updateDTO.getShootLocation()); - if (updateDTO.getLocationId() != null) drama.setLocationId(updateDTO.getLocationId()); - if (updateDTO.getService() != null) drama.setService(updateDTO.getService()); - if (updateDTO.getServiceId() != null) drama.setServiceId(updateDTO.getServiceId()); - if(updateDTO.getImage() != null) drama.setImage(updateDTO.getImage()); - if(updateDTO.getThumbImage() != null) drama.setThumbImage(updateDTO.getThumbImage()); - - drama.setUpdatedAt(LocalDateTime.now()); - cache.asMap().put(CaffeineConstants.DRAMA + dramaId, dramaId); - this.updateById(drama); - return toDramaVO(drama); + // 执行动态更新(只更新非空字段) + int updated = dramaMapper.updateDrama(dramaId, updateDTO); + + // 如果更新影响行数为 0,说明条件不满足(比如已被删除),也可视为未找到 + if (updated == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + + // 重新加载最新数据(保证数据一致性) + Drama updatedDrama = this.getById(dramaId); + + // 更新缓存 + cache.put(CaffeineConstants.DRAMA + dramaId, updatedDrama); + + // 返回 VO + return toDramaVO(updatedDrama); } @Override + @Loggable( + type = LogType.DRAMA_DELETE, + value = "delete drama ID: #{#dramaId}" + ) public void deleteDramaByAdmin(Long dramaId) { boolean updated = dramaMapper.update(null, Wrappers.lambdaUpdate() @@ -188,22 +209,26 @@ public DramaVO toDramaVO(Drama drama) { @Override public DramaVO getByIdFallback(Long id,Throwable e) { - Object store; - store = cache.getIfPresent(CaffeineConstants.DRAMA + id); - if (store != null) { - return toDramaVO((Drama) store); + + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; } - store = redisUtils.get(RedisKeyConstants.DRAMA+id); + + Object store = cache.getIfPresent(CaffeineConstants.DRAMA + id); if (store != null) { - Drama drama = JsonUtils.fromJson((String) store, Drama.class); - return toDramaVO(drama); + return toDramaVO((Drama) store); } + return null; } @Override public List getPageFallback(Long lastId, int size, String keyword, Throwable e) { + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; + } + try { // 从 Redis 获取缓存的全量文章(假设是 ArticleVO[] 的 JSON) String json = (String) redisUtils.get(TaskConstants.DRAMA); @@ -226,6 +251,10 @@ public List getPageFallback(Long lastId, int size, String keyword, Thro } @Override + @Loggable( + type = LogType.DRAMA_ADMIN_GET_PAGE, + value = "Admin get drama page" + ) public Page getDramaPageAdmin(Page page, String keyword) { long current = page.getCurrent(); long size = page.getSize(); diff --git a/src/main/java/cn/cxdproject/coder/service/impl/FileServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/FileServiceImpl.java index 401cf5d..b7d8959 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/FileServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/FileServiceImpl.java @@ -1,5 +1,7 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; +import cn.cxdproject.coder.common.enums.LogType; import cn.cxdproject.coder.common.storage.LocalStorageService; import cn.cxdproject.coder.common.storage.MinioStorageService; import cn.cxdproject.coder.model.vo.FileVO; @@ -20,6 +22,10 @@ public FileServiceImpl(MinioStorageService minioStorageService, LocalStorageServ @Override + @Loggable( + type = LogType.FILE_UPLOAD, + value = "upload file" + ) public FileVO imageUpload(MultipartFile file) { FileVO fileVO = minioStorageService.upload(file); return fileVO; diff --git a/src/main/java/cn/cxdproject/coder/service/impl/HotelServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/HotelServiceImpl.java index 6139e15..fa57b37 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/HotelServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/HotelServiceImpl.java @@ -1,6 +1,9 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.*; +import cn.cxdproject.coder.common.enums.LogType; +import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.mapper.HotelMapper; import cn.cxdproject.coder.mapper.LocationMapper; @@ -54,7 +57,10 @@ public HotelServiceImpl( @Override - @Bulkhead(name = "add", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.HOTEL_CREATE, + value = "Create hotel" + ) public HotelVO createHotelByAdmin(Long userId, CreateHotelDTO createDTO) { if (createDTO.getImage() == null) { createDTO.setImage(Constants.DEFAULT_COVER); @@ -80,6 +86,10 @@ public HotelVO createHotelByAdmin(Long userId, CreateHotelDTO createDTO) { @Override @CircuitBreaker(name = "hotelGetById", fallbackMethod = "getByIdFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.HOTEL_GET, + value = "Get hotel by ID: #{#hotelId}" + ) public HotelVO getHotelById(Long hotelId) { Object store = cache.asMap().get(CaffeineConstants.HOTEL + hotelId); if (store != null) { @@ -89,7 +99,7 @@ public HotelVO getHotelById(Long hotelId) { if (hotel == null || Boolean.TRUE.equals(hotel.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - cache.asMap().put(CaffeineConstants.HOTEL + hotelId, hotel); + cache.put(CaffeineConstants.HOTEL + hotelId, hotel); return toHotelVO(hotel); } } @@ -97,6 +107,10 @@ public HotelVO getHotelById(Long hotelId) { @Override @CircuitBreaker(name = "hotelGetPage", fallbackMethod = "getPageFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.ARTICLE_USER_GET_PAGE, + value = "User get hotel" + ) public List getHotelPage(Long lastId, int size, String keyword) { List ids = hotelMapper.selectIds(lastId, size, keyword); if (ids.isEmpty()) { @@ -117,31 +131,41 @@ public List getHotelPage(Long lastId, int size, String keyword) { @Override - @Bulkhead(name = "update", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.DRAMA_UPDATE, + value = "Update hotel ID: #{#dramaId}" + ) public HotelVO updateHotelByAdmin(Long hotelId, UpdateHotelDTO updateDTO) { - Hotel hotel = this.getById(hotelId); - if (hotel == null || Boolean.TRUE.equals(hotel.getDeleted())) { + // 1. 校验是否存在且未删除 + Hotel existing = this.getById(hotelId); + if (existing == null || Boolean.TRUE.equals(existing.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getName() != null) hotel.setName(updateDTO.getName()); - if (updateDTO.getDescription() != null) hotel.setDescription(updateDTO.getDescription()); - if (updateDTO.getAddress() != null) hotel.setAddress(updateDTO.getAddress()); - if (updateDTO.getManagerName() != null) hotel.setManagerName(updateDTO.getManagerName()); - if (updateDTO.getManagerPhone() != null) hotel.setManagerPhone(updateDTO.getManagerPhone()); - if (updateDTO.getImage() != null) hotel.setImage(updateDTO.getImage()); - if (updateDTO.getThumbImage() != null) hotel.setThumbImage(updateDTO.getThumbImage()); - if (updateDTO.getLongitude() != null) hotel.setLongitude(updateDTO.getLongitude()); - if (updateDTO.getLatitude() != null) hotel.setLatitude(updateDTO.getLatitude()); - - cache.asMap().put(CaffeineConstants.HOTEL + hotelId, hotel); - this.updateById(hotel); - return toHotelVO(hotel); + // 2. 执行动态更新 + int updatedRows = hotelMapper.updateHotel(hotelId, updateDTO); + + // 3. 若无记录被更新 + if (updatedRows == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + + // 4. 重新加载最新数据(保证返回的是 DB 最新状态) + Hotel updatedHotel = this.getById(hotelId); + + // 5. 更新缓存(注意:存的是对象,不是 ID!) + cache.put(CaffeineConstants.HOTEL + hotelId, updatedHotel); + + // 6. 返回 VO + return toHotelVO(updatedHotel); } @Override - @Bulkhead(name = "delete", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.HOTEL_DELETE, + value = "Delete hotel by ID: #{#hotelId}" + ) public void deleteHotelByAdmin(Long hotelId) { boolean updated = hotelMapper.update(null, Wrappers.lambdaUpdate() @@ -183,15 +207,14 @@ public HotelVO toHotelVO(Hotel hotel) { @Override public HotelVO getByIdFallback(Long id,Throwable e) { - Object store; - store = cache.getIfPresent(CaffeineConstants.HOTEL + id); - if (store != null) { - return toHotelVO((Hotel) store); + + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; } - store = redisUtils.get(RedisKeyConstants.HOTEL+id); + + Object store = cache.getIfPresent(CaffeineConstants.HOTEL + id); if (store != null) { - Hotel hotel = JsonUtils.fromJson((String) store, Hotel.class); - return toHotelVO(hotel); + return toHotelVO((Hotel) store); } return null; } @@ -199,6 +222,10 @@ public HotelVO getByIdFallback(Long id,Throwable e) { @Override public List getPageFallback(Long lastId, int size, String keyword, Throwable e) { + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; + } + try { // 从 Redis 获取缓存的全量文章(假设是 ArticleVO[] 的 JSON) String json = (String) redisUtils.get(TaskConstants.HOTEL); @@ -222,6 +249,10 @@ public List getPageFallback(Long lastId, int size, String keyword, Thro } @Override + @Loggable( + type = LogType.DRAMA_ADMIN_GET_PAGE, + value = "Admin get hotel Page" + ) public Page getHotelPageAdmin(Page page, String keyword) { long current = page.getCurrent(); long size = page.getSize(); diff --git a/src/main/java/cn/cxdproject/coder/service/impl/LocationServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/LocationServiceImpl.java index 804d6be..1639be2 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/LocationServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/LocationServiceImpl.java @@ -1,6 +1,8 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.*; +import cn.cxdproject.coder.common.enums.LogType; import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.model.dto.CreateLocationDTO; @@ -53,7 +55,10 @@ public LocationServiceImpl( } @Override - @Bulkhead(name = "add", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.LOCATION_CREATE, + value = "Create location" + ) public LocationVO createLocationByAdmin(Long userId, CreateLocationDTO createDTO) { if (createDTO.getImage() == null) { createDTO.setImage(Constants.DEFAULT_COVER); @@ -85,6 +90,10 @@ public LocationVO createLocationByAdmin(Long userId, CreateLocationDTO createDTO @Override @CircuitBreaker(name = "locationGetById", fallbackMethod = "getByIdFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.LOCATION_GET, + value = "Get location by ID: #{#locationId}" + ) public LocationVO getLocationById(Long locationId) { Object store = cache.asMap().get(CaffeineConstants.LOCATION + locationId); if (store != null) { @@ -94,7 +103,7 @@ public LocationVO getLocationById(Long locationId) { if (location == null || Boolean.TRUE.equals(location.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - cache.asMap().put(CaffeineConstants.LOCATION + locationId, location); + cache.put(CaffeineConstants.LOCATION + locationId, location); return toLocationVO(location); } } @@ -102,6 +111,10 @@ public LocationVO getLocationById(Long locationId) { @Override @CircuitBreaker(name = "locationGetPage", fallbackMethod = "getPageFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.LOCATION_USER_GET_PAGE, + value = "User get location" + ) public List getLocationPage(Long lastId, int size, String keyword) { List ids = locationMapper.selectIds(lastId, size, keyword); if (ids.isEmpty()) { @@ -122,35 +135,40 @@ public List getLocationPage(Long lastId, int size, String keyword) { @Override - @Bulkhead(name = "update", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.LOCATION_UPDATE, + value = "Update location ID: #{#locationId}" + ) public LocationVO updateLocationByAdmin(Long locationId, UpdateLocationDTO updateDTO) { - Location location = this.getById(locationId); - if (location == null || Boolean.TRUE.equals(location.getDeleted())) { + // 1. 校验是否存在且未删除 + Location existing = this.getById(locationId); + if (existing == null || Boolean.TRUE.equals(existing.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getName() != null) location.setName(updateDTO.getName()); - if (updateDTO.getType() != null) location.setType(updateDTO.getType()); - if (updateDTO.getStatus() != null) location.setStatus(updateDTO.getStatus()); - if (updateDTO.getLocationDescription() != null) location.setLocationDescription(updateDTO.getLocationDescription()); - if (updateDTO.getLocationPrincipalName() != null) location.setLocationPrincipalName(updateDTO.getLocationPrincipalName()); - if (updateDTO.getLocationPrincipalPhone() != null) location.setLocationPrincipalPhone(updateDTO.getLocationPrincipalPhone()); - if (updateDTO.getGovPrincipalName() != null) location.setGovPrincipalName(updateDTO.getGovPrincipalName()); - if (updateDTO.getGovPrincipalPhone() != null) location.setGovPrincipalPhone(updateDTO.getGovPrincipalPhone()); - if (updateDTO.getAddress() != null) location.setAddress(updateDTO.getAddress()); - if (updateDTO.getPrice() != null) location.setPrice(updateDTO.getPrice()); - if(updateDTO.getImage() != null) location.setImage(updateDTO.getImage()); - if(updateDTO.getThumbImage() != null) location.setThumbImage(updateDTO.getThumbImage()); - if(updateDTO.getLongitude() != null) location.setLongitude(updateDTO.getLongitude()); - if(updateDTO.getLatitude() != null) location.setLatitude(updateDTO.getLatitude()); - - cache.asMap().put(CaffeineConstants.LOCATION + locationId, location); - this.updateById(location); - return toLocationVO(location); + // 2. 执行动态更新 + int updatedRows = locationMapper.updateLocation(locationId, updateDTO); + + // 3. 若无记录被更新(可能已被删除) + if (updatedRows == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + + // 4. 重新加载最新数据(保证缓存和返回的是最新状态) + Location updatedLocation = this.getById(locationId); + + // 5. 更新缓存 + cache.put(CaffeineConstants.LOCATION + locationId, updatedLocation); + + // 6. 返回 VO + return toLocationVO(updatedLocation); } @Override - @Bulkhead(name = "delete", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.LOCATION_DELETE, + value = "Delete location by ID: #{#locationId}" + ) public void deleteLocationByAdmin(Long locationId) { boolean updated = locationMapper.update(null, Wrappers.lambdaUpdate() @@ -198,15 +216,14 @@ public LocationVO toLocationVO(Location location) { @Override public LocationVO getByIdFallback(Long id,Throwable e) { - Object store; - store = cache.getIfPresent(CaffeineConstants.LOCATION + id); - if (store != null) { - return toLocationVO((Location) store); + + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; } - store = redisUtils.get(RedisKeyConstants.LOCATION+id); + + Object store = cache.getIfPresent(CaffeineConstants.LOCATION + id); if (store != null) { - Location location = JsonUtils.fromJson((String) store, Location.class); - return toLocationVO(location); + return toLocationVO((Location) store); } return null; } @@ -214,6 +231,10 @@ public LocationVO getByIdFallback(Long id,Throwable e) { @Override public List getPageFallback(Long lastId, int size, String keyword, Throwable e) { + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; + } + try { // 从 Redis 获取缓存的全量文章(假设是 ArticleVO[] 的 JSON) String json = (String) redisUtils.get(TaskConstants.LOCATION); @@ -236,6 +257,10 @@ public List getPageFallback(Long lastId, int size, String keyword, T } @Override + @Loggable( + type = LogType.LOCATION_ADMIN_GET_PAGE, + value = "Admin get location Page" + ) public Page getLocationPageAdmin(Page page, String keyword) { long current = page.getCurrent(); long size = page.getSize(); diff --git a/src/main/java/cn/cxdproject/coder/service/impl/OperationLogServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/OperationLogServiceImpl.java index e43c95b..f254220 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/OperationLogServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/OperationLogServiceImpl.java @@ -1,11 +1,23 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.constants.ResponseConstants; +import cn.cxdproject.coder.exception.NotFoundException; +import cn.cxdproject.coder.model.entity.Banner; +import cn.cxdproject.coder.model.vo.BannerVO; +import cn.cxdproject.coder.model.vo.OperationLogVO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import cn.cxdproject.coder.model.entity.OperationLog; import cn.cxdproject.coder.mapper.OperationLogMapper; import cn.cxdproject.coder.service.OperationLogService; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.stream.Collectors; + +import static cn.cxdproject.coder.common.enums.ResponseCodeEnum.NOT_FOUND; + /** * OperationLog 服务实现类 * @author Hibiscus-code-generate @@ -13,4 +25,73 @@ @Service public class OperationLogServiceImpl extends ServiceImpl implements OperationLogService { + private final OperationLogMapper operationLogMapper; + + public OperationLogServiceImpl(OperationLogMapper operationLogMapper) { + this.operationLogMapper = operationLogMapper; + } + + @Override + public OperationLogVO getOperationById(Long id) { + OperationLog operationLog = this.getById(id); + if (operationLog == null || Boolean.TRUE.equals(operationLog.getDeleted())) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + return toOperationVO(operationLog); + } + + @Override + public void deleteArticle(Long id) { + boolean updated = operationLogMapper.update(null, + Wrappers.lambdaUpdate() + .set(OperationLog::getDeleted, true) + .eq(OperationLog::getId, id) + .eq(OperationLog::getDeleted, false) + ) > 0; + + if (!updated) { + OperationLog operationLog = this.getById(id); + if (operationLog == null || Boolean.TRUE.equals(operationLog.getDeleted())) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + } + + } + + @Override + public Page getOperationPage(Page page, String keyword) { + long current = page.getCurrent(); + long size = page.getSize(); + long offset = (current - 1) * size; + + + List operationLogs = operationLogMapper.getPage(keyword, offset, size); + + List voList = operationLogs.stream() + .map(this::toOperationVO) + .collect(Collectors.toList()); + + return new Page() + .setCurrent(current) + .setSize(size) + .setRecords(voList); + } + + @Override + public OperationLogVO toOperationVO(OperationLog operationLog) { + if (log == null) { + return null; + } + return OperationLogVO.builder() + .id(operationLog.getId()) + .type(operationLog.getType()) + .description(operationLog.getDescription()) + .userId(operationLog.getUserId()) + .operator(operationLog.getOperator()) + .success(operationLog.getSuccess()) + .params(operationLog.getParams()) + .result(operationLog.getResult()) + .timestamp(operationLog.getTimestamp()) + .build(); + } } diff --git a/src/main/java/cn/cxdproject/coder/service/impl/PolicyServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/PolicyServiceImpl.java index 7ebca52..a626ebd 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/PolicyServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/PolicyServiceImpl.java @@ -1,6 +1,9 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.*; +import cn.cxdproject.coder.common.enums.LogType; +import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.mapper.LocationMapper; import cn.cxdproject.coder.model.dto.CreatePolicyDTO; @@ -48,6 +51,10 @@ public PolicyServiceImpl(PolicyMapper policyMapper, Cache cache, } @Override + @Loggable( + type = LogType.POLICY_CREATE, + value = "Create policy" + ) public PolicyVO createPolicyByAdmin(CreatePolicyDTO createDTO) { if (createDTO.getImage() == null) { createDTO.setImage(Constants.DEFAULT_COVER); @@ -71,6 +78,10 @@ public PolicyVO createPolicyByAdmin(CreatePolicyDTO createDTO) { @Override @CircuitBreaker(name = "policyGetById", fallbackMethod = "getByIdFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.POLICY_GET, + value = "Get policy by ID: #{#policyId}" + ) public PolicyVO getPolicyById(Long policyId) { Object store = cache.asMap().get(CaffeineConstants.POLICY + policyId); if (store != null) { @@ -88,6 +99,10 @@ public PolicyVO getPolicyById(Long policyId) { @Override @CircuitBreaker(name = "policyGetPage", fallbackMethod = "getPageFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.POLICY_USER_GET_PAGE, + value = "User get policy" + ) public List getPolicyPage(Long lastId, int size, String keyword) { List ids = policyMapper.selectIds(lastId, size, keyword); if (ids.isEmpty()) { @@ -107,28 +122,40 @@ public List getPolicyPage(Long lastId, int size, String keyword) { } @Override - @Bulkhead(name = "update", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.POLICY_UPDATE, + value = "Update policy ID: #{#policyId}" + ) public PolicyVO updatePolicyByAdmin(Long policyId, UpdatePolicyDTO updateDTO) { - Policy policy = this.getById(policyId); - if (policy == null || Boolean.TRUE.equals(policy.getDeleted())) { + // 1. 校验是否存在且未被删除 + Policy existing = this.getById(policyId); + if (existing == null || Boolean.TRUE.equals(existing.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getTitle() != null) policy.setTitle(updateDTO.getTitle()); - if (updateDTO.getType() != null) policy.setType(updateDTO.getType()); - if (updateDTO.getIssueUnit() != null) policy.setIssueUnit(updateDTO.getIssueUnit()); - if (updateDTO.getIssueTime() != null) policy.setIssueTime(updateDTO.getIssueTime()); - if (updateDTO.getContent() != null) policy.setContent(updateDTO.getContent()); - if (updateDTO.getImage() != null) policy.setImage(updateDTO.getImage()); - if (updateDTO.getThumbImage() != null) policy.setThumbImage(updateDTO.getThumbImage()); + // 2. 执行动态更新 + int updatedRows = policyMapper.updatePolicy(policyId, updateDTO); - cache.asMap().put(CaffeineConstants.POLICY + policyId, policy); - this.updateById(policy); - return toPolicyVO(policy); + // 3. 若无记录被更新(可能已被删除或并发修改) + if (updatedRows == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + + // 4. 重新加载最新数据(保证一致性) + Policy updatedPolicy = this.getById(policyId); + + // 5. 更新缓存(注意:缓存的是对象,不是 ID) + cache.put(CaffeineConstants.POLICY + policyId, updatedPolicy); + + // 6. 返回 VO + return toPolicyVO(updatedPolicy); } @Override - @Bulkhead(name = "delete", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.POLICY_DELETE, + value = "Delete policy by ID: #{#policyId}" + ) public void deletePolicyByAdmin(Long policyId) { boolean updated = policyMapper.update(null, Wrappers.lambdaUpdate() @@ -167,15 +194,14 @@ public PolicyVO toPolicyVO(Policy policy) { @Override public PolicyVO getByIdFallback(Long id, Throwable e) { - Object store; - store = cache.getIfPresent(CaffeineConstants.POLICY + id); - if (store != null) { - return toPolicyVO((Policy) store); + + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; } - store = redisUtils.get(RedisKeyConstants.POLICY + id); + + Object store = cache.getIfPresent(CaffeineConstants.POLICY + id); if (store != null) { - Policy policy = JsonUtils.fromJson((String) store, Policy.class); - return toPolicyVO(policy); + return toPolicyVO((Policy) store); } return null; } @@ -183,6 +209,10 @@ public PolicyVO getByIdFallback(Long id, Throwable e) { @Override public List getPageFallback(Long lastId, int size, String keyword, Throwable e) { + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; + } + try { // 从 Redis 获取缓存的全量文章(假设是 ArticleVO[] 的 JSON) String json = (String) redisUtils.get(TaskConstants.POLICY); @@ -205,6 +235,10 @@ public List getPageFallback(Long lastId, int size, String keyword, Thr } @Override + @Loggable( + type = LogType.POLICY_ADMIN_GET_PAGE, + value = "Admin get policy Page" + ) public Page getPolicyPageAdmin(Page page, String keyword) { long current = page.getCurrent(); long size = page.getSize(); diff --git a/src/main/java/cn/cxdproject/coder/service/impl/ReportServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/ReportServiceImpl.java index 5bcd9a6..5b458b1 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/ReportServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/ReportServiceImpl.java @@ -1,17 +1,19 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.CaffeineConstants; import cn.cxdproject.coder.common.constants.ResponseConstants; import cn.cxdproject.coder.common.context.AuthContext; +import cn.cxdproject.coder.common.enums.LogType; import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.model.dto.CreateReportDTO; -import cn.cxdproject.coder.model.dto.UpdateReportDTO; +import cn.cxdproject.coder.model.dto.UpdateReportAdminDTO; +import cn.cxdproject.coder.model.dto.UpdateReportUserDTO; import cn.cxdproject.coder.model.entity.Report; import cn.cxdproject.coder.model.entity.User; import cn.cxdproject.coder.model.vo.ReportVO; import cn.cxdproject.coder.mapper.ReportMapper; -import cn.cxdproject.coder.model.vo.ShootVO; import cn.cxdproject.coder.service.ReportService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -28,6 +30,7 @@ import static cn.cxdproject.coder.common.enums.ResponseCodeEnum.*; + /** * Report 服务实现类 * @author Hibiscus-code-generate @@ -46,6 +49,10 @@ public ReportServiceImpl( } @Override + @Loggable( + type = LogType.REPORT_USER_CREATE, + value = "User submit report application" + ) public ReportVO createReport(Long userId, CreateReportDTO createDTO) { Report report = Report.builder() @@ -78,43 +85,34 @@ public ReportVO createReport(Long userId, CreateReportDTO createDTO) { } @Override - public ReportVO updateReport(Long userId, Long reportId, UpdateReportDTO updateDTO) { + @Loggable( + type = LogType.REPORT_USER_UPDATE, + value = "User update report application ID: #{#reportId}" + ) + public ReportVO updateReport(Long userId, Long reportId, UpdateReportUserDTO updateDTO) { Report report = this.getById(reportId); if (report == null || Boolean.TRUE.equals(report.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (!report.getUserId().equals(userId)) { throw new BusinessException(FORBIDDEN.code(), ResponseConstants.NO_PERMISSION); } - if (updateDTO.getName() != null) report.setName(updateDTO.getName()); - if (updateDTO.getType() != null) report.setType(updateDTO.getType()); - if (updateDTO.getGenre() != null) report.setGenre(updateDTO.getGenre()); - if (updateDTO.getEpisodes() != null) report.setEpisodes(updateDTO.getEpisodes()); - if (updateDTO.getInvestAmount() != null) report.setInvestAmount(updateDTO.getInvestAmount()); - if (updateDTO.getMainCreators() != null) report.setMainCreators(updateDTO.getMainCreators()); - if (updateDTO.getLeadProducer() != null) report.setLeadProducer(updateDTO.getLeadProducer()); - if (updateDTO.getProducerUnit() != null) report.setProducerUnit(updateDTO.getProducerUnit()); - if (updateDTO.getStartDate() != null) report.setStartDate(updateDTO.getStartDate()); - if (updateDTO.getEndDate() != null) report.setEndDate(updateDTO.getEndDate()); - if (updateDTO.getCrewScale() != null) report.setCrewScale(updateDTO.getCrewScale()); - if (updateDTO.getContact() != null) report.setContact(updateDTO.getContact()); - if (updateDTO.getPhoneNumber() != null) report.setPhoneNumber(updateDTO.getPhoneNumber()); - if (updateDTO.getCrewPosition() != null) report.setCrewPosition(updateDTO.getCrewPosition()); - if (updateDTO.getShootPermit() != null) report.setShootPermit(updateDTO.getShootPermit()); - if (updateDTO.getThumbShootPermit() != null) report.setThumbShootPermit(updateDTO.getThumbShootPermit()); - if (updateDTO.getApprovalFile() != null) report.setApprovalFile(updateDTO.getApprovalFile()); - if (updateDTO.getThumbApprovalFile() != null) report.setThumbApprovalFile(updateDTO.getThumbApprovalFile()); - if (updateDTO.getShootApply() != null) report.setShootApply(updateDTO.getShootApply()); - if (updateDTO.getThumbShootApply() != null) report.setThumbShootApply(updateDTO.getThumbShootApply()); - if (updateDTO.getStatus() != null) throw new BusinessException(FORBIDDEN.code(), "无权修改申请状态");; - - this.updateById(report); - return toReportVO(report); + int rows = reportMapper.updateReportByUser(reportId, updateDTO); + if (rows == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + + Report updated = this.getById(reportId); + cache.asMap().put(CaffeineConstants.REPORT + reportId, updated); + return toReportVO(updated); } @Override + @Loggable( + type = LogType.REPORT_USER_DELETE, + value = "User cancel report application ID: #{#reportId}" + ) public void deleteReport(Long userId, Long reportId) { boolean updated = reportMapper.update(null, Wrappers.lambdaUpdate() @@ -136,6 +134,10 @@ public void deleteReport(Long userId, Long reportId) { } @Override + @Loggable( + type = LogType.REPORT_USER_GET, + value = "User get own report application by ID: #{#reportId}" + ) public ReportVO getReportById(Long reportId,Long userId) { Object store = cache.asMap().get(CaffeineConstants.REPORT + reportId); Report report; @@ -157,6 +159,10 @@ public ReportVO getReportById(Long reportId,Long userId) { @Override + @Loggable( + type = LogType.REPORT_USER_GET_PAGE, + value = "User get all own report applications" + ) public Page getMyReportPage(Long userId, Page page) { long current = page.getCurrent(); long size = page.getSize(); @@ -178,23 +184,36 @@ public Page getMyReportPage(Long userId, Page page) { @Override - public ReportVO updateReportByAdmin(Long reportId, UpdateReportDTO updateDTO) { - User currentUser = AuthContext.getCurrentUser(); + @Loggable( + type = LogType.REPORT_ADMIN_UPDATE, + value = "Admin process report application ID: #{#reportId}" + ) + public ReportVO updateReportByAdmin(Long reportId, UpdateReportAdminDTO updateDTO) { + if (updateDTO.getStatus() == null || updateDTO.getStatus().trim().isEmpty()) { + throw new BusinessException(MISSING_PARAMETER.code(),"审核状态不能为空"); + } + Report report = this.getById(reportId); if (report == null || Boolean.TRUE.equals(report.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getStatus() != null) report.setStatus(updateDTO.getStatus()); + int rows = reportMapper.updateReportByAdmin(reportId, updateDTO.getStatus()); + if (rows == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } - report.setUpdatedAt(LocalDateTime.now()); - cache.asMap().put(CaffeineConstants.REPORT + reportId, report); - this.updateById(report); - return toReportVO(report); + Report updated = this.getById(reportId); + cache.asMap().put(CaffeineConstants.REPORT + reportId, updated); + return toReportVO(updated); } @Override + @Loggable( + type = LogType.REPORT_ADMIN_GET_PAGE, + value = "Admin get paginated report applications" + ) public Page getReportPageByAdmin(Page page, String keyword) { long current = page.getCurrent(); long size = page.getSize(); @@ -221,6 +240,10 @@ public Page getReportPageByAdmin(Page page, String keyword) { } @Override + @Loggable( + type = LogType.REPORT_ADMIN_GET, + value = "Admin get report application by ID: #{#reportId}" + ) public ReportVO getReportByIdByAdmin(Long reportId) { Object store = cache.asMap().get(CaffeineConstants.REPORT + reportId); if (store != null) { diff --git a/src/main/java/cn/cxdproject/coder/service/impl/ShootServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/ShootServiceImpl.java index fb5d203..f7ea56f 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/ShootServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/ShootServiceImpl.java @@ -1,6 +1,8 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.*; +import cn.cxdproject.coder.common.enums.LogType; import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.model.dto.CreateShootDTO; @@ -53,7 +55,10 @@ public ShootServiceImpl( } @Override - @Bulkhead(name = "add", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.SHOOT_CREATE, + value = "Create shoot" + ) public ShootVO createShootByAdmin(Long userId, CreateShootDTO createDTO) { if (createDTO.getImage() == null) { createDTO.setImage(Constants.DEFAULT_COVER); @@ -80,6 +85,10 @@ public ShootVO createShootByAdmin(Long userId, CreateShootDTO createDTO) { @Override @CircuitBreaker(name = "shootGetById", fallbackMethod = "getByIdFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.SHOOT_GET, + value = "Get shoot by ID: #{#shootId}" + ) public ShootVO getShootById(Long shootId) { Object store = cache.asMap().get(CaffeineConstants.SHOOT + shootId); if (store != null) { @@ -89,7 +98,7 @@ public ShootVO getShootById(Long shootId) { if (shoot == null || Boolean.TRUE.equals(shoot.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - cache.asMap().put(CaffeineConstants.SHOOT + shootId, shoot); + cache.put(CaffeineConstants.SHOOT + shootId, shoot); return toShootVO(shoot); } } @@ -97,6 +106,10 @@ public ShootVO getShootById(Long shootId) { @Override @CircuitBreaker(name = "shootGetPage", fallbackMethod = "getPageFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.SHOOT_USER_GET_PAGE, + value = "User get shoot" + ) public List getShootPage(Long lastId, int size, String keyword) { List ids = shootMapper.selectIds(lastId, size, keyword); if (ids.isEmpty()) { @@ -117,30 +130,40 @@ public List getShootPage(Long lastId, int size, String keyword) { @Override @Bulkhead(name = "update", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.SHOOT_UPDATE, + value = "Update shoot ID: #{#shootId}" + ) public ShootVO updateShootByAdmin(Long shootId, UpdateShootDTO updateDTO) { - Shoot shoot = this.getById(shootId); - if (shoot == null || Boolean.TRUE.equals(shoot.getDeleted())) { + // 1. 校验是否存在且未被删除 + Shoot existing = this.getById(shootId); + if (existing == null || Boolean.TRUE.equals(existing.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getName() != null) shoot.setName(updateDTO.getName()); - if (updateDTO.getDescription() != null) shoot.setDescription(updateDTO.getDescription()); - if (updateDTO.getPrice() != null) shoot.setPrice(updateDTO.getPrice()); - if (updateDTO.getStatus() != null) shoot.setStatus(updateDTO.getStatus()); - if (updateDTO.getAddress() != null) shoot.setAddress(updateDTO.getAddress()); - if (updateDTO.getPhone() != null) shoot.setPhone(updateDTO.getPhone()); - if (updateDTO.getContactName() != null) shoot.setContactName(updateDTO.getContactName()); - if (updateDTO.getImage() != null) shoot.setImage(updateDTO.getImage()); - if (updateDTO.getThumbImage() != null) shoot.setThumbImage(updateDTO.getThumbImage()); - - shoot.setUpdatedAt(LocalDateTime.now()); - cache.asMap().put(CaffeineConstants.SHOOT + shootId, shoot); - this.updateById(shoot); - return toShootVO(shoot); + // 3. 执行动态更新 + int updatedRows = shootMapper.updateShoot(shootId, updateDTO); + + // 4. 若无记录被更新(可能已被删除) + if (updatedRows == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + + // 5. 重新加载最新数据(保证缓存和返回的是最新状态) + Shoot updatedShoot = this.getById(shootId); + + // 6. 更新缓存 + cache.put(CaffeineConstants.SHOOT + shootId, updatedShoot); + + // 7. 返回 VO + return toShootVO(updatedShoot); } @Override - @Bulkhead(name = "delete", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.SHOOT_DELETE, + value = "Delete shoot by ID: #{#shootId}" + ) public void deleteShootByAdmin(Long shootId) { boolean updated = shootMapper.update(null, Wrappers.lambdaUpdate() @@ -182,15 +205,14 @@ public ShootVO toShootVO(Shoot shoot) { @Override public ShootVO getByIdFallback(Long id,Throwable e) { - Object store; - store = cache.getIfPresent(CaffeineConstants.SHOOT + id); - if (store != null) { - return toShootVO((Shoot) store); + + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; } - store = redisUtils.get(RedisKeyConstants.SHOOT+id); + + Object store = cache.getIfPresent(CaffeineConstants.SHOOT + id); if (store != null) { - Shoot shoot = JsonUtils.fromJson((String) store, Shoot.class); - return toShootVO(shoot); + return toShootVO((Shoot) store); } return null; } @@ -198,6 +220,10 @@ public ShootVO getByIdFallback(Long id,Throwable e) { @Override public List getPageFallback(Long lastId, int size, String keyword, Throwable e) { + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; + } + try { // 从 Redis 获取缓存的全量文章(假设是 ArticleVO[] 的 JSON) String json = (String) redisUtils.get(TaskConstants.SHOOT); @@ -220,6 +246,10 @@ public List getPageFallback(Long lastId, int size, String keyword, Thro } @Override + @Loggable( + type = LogType.SHOOT_ADMIN_GET_PAGE, + value = "Admin get shoot Page" + ) public Page getShootPageAdmin(Page page, String keyword) { long current = page.getCurrent(); long size = page.getSize(); diff --git a/src/main/java/cn/cxdproject/coder/service/impl/TourServiceImpl.java b/src/main/java/cn/cxdproject/coder/service/impl/TourServiceImpl.java index fceeb52..68f513b 100644 --- a/src/main/java/cn/cxdproject/coder/service/impl/TourServiceImpl.java +++ b/src/main/java/cn/cxdproject/coder/service/impl/TourServiceImpl.java @@ -1,6 +1,9 @@ package cn.cxdproject.coder.service.impl; +import cn.cxdproject.coder.common.anno.Loggable; import cn.cxdproject.coder.common.constants.*; +import cn.cxdproject.coder.common.enums.LogType; +import cn.cxdproject.coder.exception.BusinessException; import cn.cxdproject.coder.exception.NotFoundException; import cn.cxdproject.coder.model.dto.CreateTourDTO; import cn.cxdproject.coder.model.dto.UpdateTourDTO; @@ -46,6 +49,10 @@ public TourServiceImpl(TourMapper tourMapper, Cache cache, Redis } @Override + @Loggable( + type = LogType.TOUR_CREATE, + value = "Create tour" + ) public TourVO createTourByAdmin(CreateTourDTO createDTO) { if (createDTO.getImage() == null) { createDTO.setImage(Constants.DEFAULT_COVER); @@ -76,6 +83,10 @@ public TourVO createTourByAdmin(CreateTourDTO createDTO) { @Override @CircuitBreaker(name = "tourGetById", fallbackMethod = "getByIdFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.TOUR_GET, + value = "Get tour by ID: #{#tourId}" + ) public TourVO getTourById(Long tourId) { Object store = cache.asMap().get(CaffeineConstants.TOUR + tourId); if (store != null) { @@ -85,7 +96,7 @@ public TourVO getTourById(Long tourId) { if (tour == null || Boolean.TRUE.equals(tour.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - cache.asMap().put(CaffeineConstants.TOUR + tourId, tour); + cache.put(CaffeineConstants.TOUR + tourId, tour); return toTourVO(tour); } } @@ -93,6 +104,10 @@ public TourVO getTourById(Long tourId) { @Override @CircuitBreaker(name = "tourGetPage", fallbackMethod = "getPageFallback") @Bulkhead(name = "get", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.TOUR_USER_GET_PAGE, + value = "User get tour" + ) public List getTourPage(Long lastId, int size, String keyword) { List ids = tourMapper.selectIds(lastId, size, keyword); if (ids.isEmpty()) { @@ -113,37 +128,40 @@ public List getTourPage(Long lastId, int size, String keyword) { @Override - @Bulkhead(name = "update", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.TOUR_UPDATE, + value = "Update tour ID: #{#tourId}" + ) public TourVO updateTourByAdmin(Long tourId, UpdateTourDTO updateDTO) { - Tour tour = this.getById(tourId); - if (tour == null || Boolean.TRUE.equals(tour.getDeleted())) { + // 1. 校验是否存在且未被删除 + Tour existing = this.getById(tourId); + if (existing == null || Boolean.TRUE.equals(existing.getDeleted())) { throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); } - if (updateDTO.getName() != null) tour.setName(updateDTO.getName()); - if (updateDTO.getDescription() != null) tour.setDescription(updateDTO.getDescription()); - if (updateDTO.getTheme() != null) tour.setTheme(updateDTO.getTheme()); - if (updateDTO.getFeatures() != null) tour.setFeatures(updateDTO.getFeatures()); - if (updateDTO.getTransport() != null) tour.setTransport(updateDTO.getTransport()); - if (updateDTO.getHotel() != null) tour.setHotel(updateDTO.getHotel()); - if (updateDTO.getFood() != null) tour.setFood(updateDTO.getFood()); - if (updateDTO.getImage() != null) tour.setImage(updateDTO.getImage()); - if (updateDTO.getThumbImage() != null) tour.setThumbImage(updateDTO.getThumbImage()); - if (updateDTO.getLongitude() != null) tour.setLongitude(updateDTO.getLongitude()); - if (updateDTO.getLatitude() != null) tour.setLatitude(updateDTO.getLatitude()); - if (updateDTO.getLocationId() != null) tour.setLocationId(updateDTO.getLocationId()); - if (updateDTO.getDramaId() != null) tour.setDramaId(updateDTO.getDramaId()); - - - - tour.setUpdatedAt(LocalDateTime.now()); - cache.asMap().put(CaffeineConstants.TOUR + tourId, tour); - this.updateById(tour); - return toTourVO(tour); + // 3. 执行动态更新 + int updatedRows = tourMapper.updateTour(tourId, updateDTO); + + // 4. 若无记录被更新(可能已被删除或并发修改) + if (updatedRows == 0) { + throw new NotFoundException(NOT_FOUND.code(), ResponseConstants.NOT_FIND); + } + + // 5. 重新加载最新数据(保证一致性) + Tour updatedTour = this.getById(tourId); + + // 6. 更新缓存(注意:缓存的是对象,不是旧引用) + cache.put(CaffeineConstants.TOUR + tourId, updatedTour); + + // 7. 返回 VO + return toTourVO(updatedTour); } @Override - @Bulkhead(name = "delete", type = Bulkhead.Type.SEMAPHORE) + @Loggable( + type = LogType.TOUR_DELETE, + value = "Delete tour by ID: #{#tourId}" + ) public void deleteTourByAdmin(Long tourId) { boolean updated = tourMapper.update(null, Wrappers.lambdaUpdate() @@ -190,15 +208,14 @@ public TourVO toTourVO(Tour tour) { @Override public TourVO getByIdFallback(Long id,Throwable e) { - Object store; - store = cache.getIfPresent(CaffeineConstants.TOUR + id); - if (store != null) { - return toTourVO((Tour) store); + + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; } - store = redisUtils.get(RedisKeyConstants.TOUR+id); + + Object store = cache.getIfPresent(CaffeineConstants.TOUR + id); if (store != null) { - Tour tour = JsonUtils.fromJson((String) store, Tour.class); - return toTourVO(tour); + return toTourVO((Tour) store); } return null; } @@ -206,6 +223,10 @@ public TourVO getByIdFallback(Long id,Throwable e) { @Override public List getPageFallback(Long lastId, int size, String keyword, Throwable e) { + if (e instanceof NotFoundException || e instanceof BusinessException) { + throw (RuntimeException) e; + } + try { String json = (String) redisUtils.get(TaskConstants.TOUR); @@ -228,6 +249,10 @@ public List getPageFallback(Long lastId, int size, String keyword, Throw } @Override + @Loggable( + type = LogType.TOUR_ADMIN_GET_PAGE, + value = "Admin get tour Page" + ) public Page getTourPageAdmin(Page page, String keyword) { long current = page.getCurrent(); long size = page.getSize(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dc3659c..3b9712f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -125,21 +125,6 @@ resilience4j: max-wait-duration: 100ms instances: - add: - base-config: default - max-concurrent-calls: 10 - max-wait-duration: 100ms # 写操作可以稍微等一下 - - delete: - base-config: default - max-concurrent-calls: 20 - max-wait-duration: 50ms - - update: - base-config: default - max-concurrent-calls: 20 - max-wait-duration: 50ms - get: base-config: default max-concurrent-calls: 50 diff --git a/src/main/resources/mapper/ArticleMapper.xml b/src/main/resources/mapper/ArticleMapper.xml index 3a5951a..b6bcdca 100644 --- a/src/main/resources/mapper/ArticleMapper.xml +++ b/src/main/resources/mapper/ArticleMapper.xml @@ -77,4 +77,25 @@ LIMIT 10 + + + UPDATE fi_articles + + + title = #{dto.title}, + + + issue_unit = #{dto.issueUnit}, + + + content = #{dto.content}, + + + thumb_image = #{dto.thumbImage}, + + + WHERE id = #{articleId} + AND deleted = false + + diff --git a/src/main/resources/mapper/DramaMapper.xml b/src/main/resources/mapper/DramaMapper.xml index 1d59006..8c08015 100644 --- a/src/main/resources/mapper/DramaMapper.xml +++ b/src/main/resources/mapper/DramaMapper.xml @@ -89,4 +89,48 @@ LIMIT 10 + + UPDATE fi_dramas + + + name = #{dto.name}, + + + filing_num = #{dto.filingNum}, + + + prod_company = #{dto.prodCompany}, + + + crew_description = #{dto.crewDescription}, + + + drama_description = #{dto.dramaDescription}, + + + cast = #{dto.cast}, + + + shoot_location = #{dto.shootLocation}, + + + service = #{dto.service}, + + + image = #{dto.image}, + + + thumb_image = #{dto.thumbImage}, + + + location_id = #{dto.locationId}, + + + service_id = #{dto.serviceId}, + + + WHERE id = #{dramaId} + AND deleted = false + + diff --git a/src/main/resources/mapper/HotelMapper.xml b/src/main/resources/mapper/HotelMapper.xml index c33dcc3..c21affa 100644 --- a/src/main/resources/mapper/HotelMapper.xml +++ b/src/main/resources/mapper/HotelMapper.xml @@ -81,4 +81,39 @@ LIMIT 10 + + UPDATE fi_hotel + + + name = #{dto.name}, + + + description = #{dto.description}, + + + address = #{dto.address}, + + + manager_name = #{dto.managerName}, + + + manager_phone = #{dto.managerPhone}, + + + image = #{dto.image}, + + + thumb_image = #{dto.thumbImage}, + + + longitude = #{dto.longitude}, + + + latitude = #{dto.latitude}, + + + WHERE id = #{id} + AND deleted = false + + \ No newline at end of file diff --git a/src/main/resources/mapper/LocationMapper.xml b/src/main/resources/mapper/LocationMapper.xml index dfda343..17bd0b3 100644 --- a/src/main/resources/mapper/LocationMapper.xml +++ b/src/main/resources/mapper/LocationMapper.xml @@ -59,6 +59,7 @@ AND ( name LIKE CONCAT('%', #{keyword}, '%') OR location_description LIKE CONCAT('%', #{keyword}, '%') + OR type LIKE CONCAT('%', #{keyword}, '%') ) @@ -79,7 +80,11 @@ FROM fi_locations WHERE deleted = 0 - AND title LIKE CONCAT('%', #{keyword}, '%') + AND ( + name LIKE CONCAT('%', #{keyword}, '%') + OR location_description LIKE CONCAT('%', #{keyword}, '%') + OR type LIKE CONCAT('%', #{keyword}, '%') + ) ORDER BY created_at DESC LIMIT #{offset}, #{size} @@ -94,4 +99,55 @@ LIMIT 10 + + + UPDATE fi_locations + + + name = #{dto.name}, + + + type = #{dto.type}, + + + status = #{dto.status}, + + + location_description = #{dto.locationDescription}, + + + location_principal_name = #{dto.locationPrincipalName}, + + + location_principal_phone = #{dto.locationPrincipalPhone}, + + + gov_principal_name = #{dto.govPrincipalName}, + + + gov_principal_phone = #{dto.govPrincipalPhone}, + + + address = #{dto.address}, + + + price = #{dto.price}, + + + image = #{dto.image}, + + + thumb_image = #{dto.thumbImage}, + + + longitude = #{dto.longitude}, + + + latitude = #{dto.latitude}, + + + WHERE id = #{id} + AND deleted = false + + diff --git a/src/main/resources/mapper/OperationLogMapper.xml b/src/main/resources/mapper/OperationLogMapper.xml index a25d5df..8bebc05 100644 --- a/src/main/resources/mapper/OperationLogMapper.xml +++ b/src/main/resources/mapper/OperationLogMapper.xml @@ -34,4 +34,15 @@ `updated_at` + + diff --git a/src/main/resources/mapper/PolicyMapper.xml b/src/main/resources/mapper/PolicyMapper.xml index b45c915..5f3956d 100644 --- a/src/main/resources/mapper/PolicyMapper.xml +++ b/src/main/resources/mapper/PolicyMapper.xml @@ -76,5 +76,34 @@ LIMIT 10 + + UPDATE fi_policy + + + title = #{dto.title}, + + + type = #{dto.type}, + + + issue_unit = #{dto.issueUnit}, + + + issue_time = #{dto.issueTime}, + + + content = #{dto.content}, + + + image = #{dto.image}, + + + thumb_image = #{dto.thumbImage}, + + + WHERE id = #{id} + AND deleted = false + + diff --git a/src/main/resources/mapper/ReportMapper.xml b/src/main/resources/mapper/ReportMapper.xml index 55bc8de..7f657b5 100644 --- a/src/main/resources/mapper/ReportMapper.xml +++ b/src/main/resources/mapper/ReportMapper.xml @@ -84,7 +84,10 @@ FROM fi_reports WHERE deleted = 0 - AND name LIKE CONCAT('%', #{keyword}, '%') + AND ( + name LIKE CONCAT('%', #{keyword}, '%') + OR type LIKE CONCAT('%', #{keyword}, '%') + ) ORDER BY created_at DESC LIMIT #{offset}, #{size} @@ -92,5 +95,81 @@ ORDER BY created_at DESC + + UPDATE fi_reports + + + name = #{dto.name}, + + + type = #{dto.type}, + + + genre = #{dto.genre}, + + + episodes = #{dto.episodes}, + + + invest_amount = #{dto.investAmount}, + + + main_creators = #{dto.mainCreators}, + + + lead_producer = #{dto.leadProducer}, + + + producer_unit = #{dto.producerUnit}, + + + start_date = #{dto.startDate}, + + + end_date = #{dto.endDate}, + + + crew_scale = #{dto.crewScale}, + + + contact = #{dto.contact}, + + + phone_number = #{dto.phoneNumber}, + + + crew_position = #{dto.crewPosition}, + + + shoot_permit = #{dto.shootPermit}, + + + thumb_shoot_permit = #{dto.thumbShootPermit}, + + + approval_file = #{dto.approvalFile}, + + + thumb_approval_file = #{dto.thumbApprovalFile}, + + + shoot_apply = #{dto.shootApply}, + + + thumb_shoot_apply = #{dto.thumbShootApply}, + + + WHERE id = #{id} + AND deleted = false + + + + UPDATE fi_reports + SET + status = #{status} + WHERE id = #{id} + AND deleted = false + + diff --git a/src/main/resources/mapper/ShootMapper.xml b/src/main/resources/mapper/ShootMapper.xml index 2749bf2..78466e2 100644 --- a/src/main/resources/mapper/ShootMapper.xml +++ b/src/main/resources/mapper/ShootMapper.xml @@ -83,4 +83,38 @@ LIMIT 10 + + UPDATE fi_shoots + + + name = #{dto.name}, + + + description = #{dto.description}, + + + price = #{dto.price}, + + + status = #{dto.status}, + + + address = #{dto.address}, + + + phone = #{dto.phone}, + + + contact_name = #{dto.contactName}, + + + image = #{dto.image}, + + + thumb_image = #{dto.thumbImage}, + + + WHERE id = #{id} + AND deleted = false + diff --git a/src/main/resources/mapper/TourMapper.xml b/src/main/resources/mapper/TourMapper.xml index 8a3d7c9..77b0df3 100644 --- a/src/main/resources/mapper/TourMapper.xml +++ b/src/main/resources/mapper/TourMapper.xml @@ -54,6 +54,7 @@ AND ( name LIKE CONCAT('%', #{keyword}, '%') OR description LIKE CONCAT('%', #{keyword}, '%') + OR theme LIKE CONCAT('%', #{keyword}, '%') ) @@ -67,14 +68,18 @@ + + UPDATE fi_tour + + + name = #{dto.name}, + + + description = #{dto.description}, + + + theme = #{dto.theme}, + + + features = #{dto.features}, + + + transport = #{dto.transport}, + + + hotel = #{dto.hotel}, + + + food = #{dto.food}, + + + image = #{dto.image}, + + + thumb_image = #{dto.thumbImage}, + + + longitude = #{dto.longitude}, + + + latitude = #{dto.latitude}, + + + location_id = #{dto.locationId}, + + + drama_id = #{dto.dramaId}, + + + WHERE id = #{id} + AND deleted = false + +