From b9ddbc3a886cb5c1eccb74d3bebdb1fa02f352cd Mon Sep 17 00:00:00 2001 From: Kendy Date: Thu, 1 May 2025 13:41:59 +0700 Subject: [PATCH 01/16] setup server & client --- client/.gitignore | 24 + client/README.md | 12 + client/eslint.config.js | 33 + client/index.html | 19 + client/package-lock.json | 3143 ++++++++ client/package.json | 30 + client/public/vite.svg | 1 + client/src/App.css | 42 + client/src/App.jsx | 37 + client/src/assets/react.svg | 1 + client/src/components/AllDevices.jsx | 31 + client/src/components/AnimatedBackground.jsx | 10 + client/src/components/ErrorMessage.jsx | 10 + client/src/components/FeaturedDevices.jsx | 34 + client/src/components/HomeStyles.jsx | 265 + client/src/components/LandingStyles.jsx | 73 + client/src/components/LoadingSpinner.jsx | 9 + client/src/components/Navbar.jsx | 59 + client/src/components/WelcomeContent.jsx | 29 + client/src/components/card.jsx | 82 + client/src/index.css | 68 + client/src/main.jsx | 9 + client/src/pages/detail.page.jsx | 476 ++ client/src/pages/favorite.page.jsx | 87 + client/src/pages/home.page.jsx | 74 + client/src/pages/landing.page.jsx | 13 + client/src/pages/login.page.jsx | 98 + client/src/pages/register.app.jsx | 151 + client/vite.config.js | 7 + server/.envexample | 1 + server/.gitignore | 2 + server/__test__/sum.test.js | 6 + server/__test__/test.sum.js | 0 server/__test__/testing.js | 0 server/__test__/testing.test.js | 402 + server/app.js | 52 + server/config/config.json | 23 + server/controllers/Controller.js | 149 + server/controllers/PubController.js | 82 + server/controllers/UserController.js | 123 + server/data/list-hp-xiaomi.json | 939 +++ server/data/list-user.json | 27 + server/helpers/bcrypt.js | 14 + server/helpers/jwt.js | 15 + server/helpers/sum.js | 5 + server/middlewares/authentication.js | 33 + .../migrations/20250429133242-create-user.js | 35 + .../20250429134349-create-xiaomi-device.js | 73 + .../20250429135649-create-favorite.js | 31 + server/models/favorite.js | 49 + server/models/index.js | 43 + server/models/user.js | 71 + server/models/xiaomidevice.js | 43 + server/package-lock.json | 6559 +++++++++++++++++ server/package.json | 29 + server/routes/testing.js | 9 + .../seeders/20250429000000-xiaomi-devices.js | 47 + server/seeders/20250429000001-users.js | 36 + 58 files changed, 13825 insertions(+) create mode 100644 client/.gitignore create mode 100644 client/README.md create mode 100644 client/eslint.config.js create mode 100644 client/index.html create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/public/vite.svg create mode 100644 client/src/App.css create mode 100644 client/src/App.jsx create mode 100644 client/src/assets/react.svg create mode 100644 client/src/components/AllDevices.jsx create mode 100644 client/src/components/AnimatedBackground.jsx create mode 100644 client/src/components/ErrorMessage.jsx create mode 100644 client/src/components/FeaturedDevices.jsx create mode 100644 client/src/components/HomeStyles.jsx create mode 100644 client/src/components/LandingStyles.jsx create mode 100644 client/src/components/LoadingSpinner.jsx create mode 100644 client/src/components/Navbar.jsx create mode 100644 client/src/components/WelcomeContent.jsx create mode 100644 client/src/components/card.jsx create mode 100644 client/src/index.css create mode 100644 client/src/main.jsx create mode 100644 client/src/pages/detail.page.jsx create mode 100644 client/src/pages/favorite.page.jsx create mode 100644 client/src/pages/home.page.jsx create mode 100644 client/src/pages/landing.page.jsx create mode 100644 client/src/pages/login.page.jsx create mode 100644 client/src/pages/register.app.jsx create mode 100644 client/vite.config.js create mode 100644 server/.envexample create mode 100644 server/.gitignore create mode 100644 server/__test__/sum.test.js create mode 100644 server/__test__/test.sum.js create mode 100644 server/__test__/testing.js create mode 100644 server/__test__/testing.test.js create mode 100644 server/app.js create mode 100644 server/config/config.json create mode 100644 server/controllers/Controller.js create mode 100644 server/controllers/PubController.js create mode 100644 server/controllers/UserController.js create mode 100644 server/data/list-hp-xiaomi.json create mode 100644 server/data/list-user.json create mode 100644 server/helpers/bcrypt.js create mode 100644 server/helpers/jwt.js create mode 100644 server/helpers/sum.js create mode 100644 server/middlewares/authentication.js create mode 100644 server/migrations/20250429133242-create-user.js create mode 100644 server/migrations/20250429134349-create-xiaomi-device.js create mode 100644 server/migrations/20250429135649-create-favorite.js create mode 100644 server/models/favorite.js create mode 100644 server/models/index.js create mode 100644 server/models/user.js create mode 100644 server/models/xiaomidevice.js create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 server/routes/testing.js create mode 100644 server/seeders/20250429000000-xiaomi-devices.js create mode 100644 server/seeders/20250429000001-users.js diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/client/README.md b/client/README.md new file mode 100644 index 00000000..7059a962 --- /dev/null +++ b/client/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/client/eslint.config.js b/client/eslint.config.js new file mode 100644 index 00000000..ec2b712d --- /dev/null +++ b/client/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/client/index.html b/client/index.html new file mode 100644 index 00000000..d2b4320f --- /dev/null +++ b/client/index.html @@ -0,0 +1,19 @@ + + + + + + + Vite + React + + + +
+ + + diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 00000000..f9f5c6b7 --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,3143 @@ +{ + "name": "client", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client", + "version": "0.0.0", + "dependencies": { + "axios": "^1.9.0", + "prop-types": "^15.8.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.5.3" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.25.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", + "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", + "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", + "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", + "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", + "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", + "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", + "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", + "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", + "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", + "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", + "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", + "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", + "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", + "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", + "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", + "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", + "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", + "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", + "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", + "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", + "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", + "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001715", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", + "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/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/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "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", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.145", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.145.tgz", + "integrity": "sha512-pZ5EcTWRq/055MvSBgoFEyKf2i4apwfoqJbK/ak2jnFq8oHjZ+vzc3AhRcz37Xn+ZJfL58R666FLJx0YOK9yTw==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.25.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", + "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.25.1", + "@eslint/plugin-kit": "^0.2.8", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "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.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz", + "integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", + "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.1", + "@rollup/rollup-android-arm64": "4.40.1", + "@rollup/rollup-darwin-arm64": "4.40.1", + "@rollup/rollup-darwin-x64": "4.40.1", + "@rollup/rollup-freebsd-arm64": "4.40.1", + "@rollup/rollup-freebsd-x64": "4.40.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", + "@rollup/rollup-linux-arm-musleabihf": "4.40.1", + "@rollup/rollup-linux-arm64-gnu": "4.40.1", + "@rollup/rollup-linux-arm64-musl": "4.40.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-musl": "4.40.1", + "@rollup/rollup-linux-s390x-gnu": "4.40.1", + "@rollup/rollup-linux-x64-gnu": "4.40.1", + "@rollup/rollup-linux-x64-musl": "4.40.1", + "@rollup/rollup-win32-arm64-msvc": "4.40.1", + "@rollup/rollup-win32-ia32-msvc": "4.40.1", + "@rollup/rollup-win32-x64-msvc": "4.40.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", + "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.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 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 00000000..0600bcf0 --- /dev/null +++ b/client/package.json @@ -0,0 +1,30 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.9.0", + "prop-types": "^15.8.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.5.3" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.1" + } +} diff --git a/client/public/vite.svg b/client/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/client/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/client/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/client/src/App.jsx b/client/src/App.jsx new file mode 100644 index 00000000..64b59eda --- /dev/null +++ b/client/src/App.jsx @@ -0,0 +1,37 @@ +import { BrowserRouter, Routes, Route } from "react-router"; +import HomePage from "./pages/home.page"; +import LandingPage from "./pages/landing.page"; +import RegisterPage from "./pages/register.app"; +import LoginPage from "./pages/login.page"; +import DetailPage from "./pages/detail.page"; +import FavoritePage from "./pages/favorite.page"; + +function AuthenticatedLayout() { + const access_token = localStorage.getItem("access_token"); + if (!access_token) { + return ; + } + return ( +
+ + +
+ ); +} + +function App() { + return ( + + + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} + +export default App; diff --git a/client/src/assets/react.svg b/client/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/client/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/components/AllDevices.jsx b/client/src/components/AllDevices.jsx new file mode 100644 index 00000000..ddbd6ef8 --- /dev/null +++ b/client/src/components/AllDevices.jsx @@ -0,0 +1,31 @@ +import Card from "./card"; +import LoadingSpinner from "./LoadingSpinner"; + +export default function AllDevices({ devices, loading, onCardClick }) { + return ( +
+

Daftar Lengkap HP

+ {loading ? ( + + ) : ( +
+ {devices.map((device) => ( +
+ onCardClick(device.id)} + /> +
+ ))} +
+ )} +
+ ); +} diff --git a/client/src/components/AnimatedBackground.jsx b/client/src/components/AnimatedBackground.jsx new file mode 100644 index 00000000..ff478281 --- /dev/null +++ b/client/src/components/AnimatedBackground.jsx @@ -0,0 +1,10 @@ +export default function AnimatedBackground() { + return ( +
+
+
+
+
+
+ ); +} diff --git a/client/src/components/ErrorMessage.jsx b/client/src/components/ErrorMessage.jsx new file mode 100644 index 00000000..9fd99096 --- /dev/null +++ b/client/src/components/ErrorMessage.jsx @@ -0,0 +1,10 @@ +export default function ErrorMessage({ message }) { + return ( +
+
+ + {message} +
+
+ ); +} diff --git a/client/src/components/FeaturedDevices.jsx b/client/src/components/FeaturedDevices.jsx new file mode 100644 index 00000000..a9592e24 --- /dev/null +++ b/client/src/components/FeaturedDevices.jsx @@ -0,0 +1,34 @@ +import Card from "./card"; +import LoadingSpinner from "./LoadingSpinner"; + +export default function FeaturedDevices({ devices, loading, onCardClick }) { + return ( +
+

+ Berikut beberapa pilihan HP yang sangat cocok sesuai kebutuhan kamu... +

+ {loading ? ( + + ) : ( +
+ {devices.map((device) => ( +
+ onCardClick(device.id)} + /> +
+ ))} +
+ )} +
+ ); +} diff --git a/client/src/components/HomeStyles.jsx b/client/src/components/HomeStyles.jsx new file mode 100644 index 00000000..5c8330b5 --- /dev/null +++ b/client/src/components/HomeStyles.jsx @@ -0,0 +1,265 @@ +export default function HomeStyles() { + return ( + <> + + + Xiaomi Phone Catalog + + + + + + +
+
+

All files

+
+ +
+ 66.12% + Statements + 164/248 +
+ + +
+ 41.02% + Branches + 32/78 +
+ + +
+ 75% + Functions + 24/32 +
+ + +
+ 67.35% + Lines + 163/242 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
server +
+
93.93%31/3375%3/450%1/293.93%31/33
server/controllers +
+
44.36%63/14231.66%19/6057.14%8/1445.58%62/136
server/helpers +
+
100%13/13100%0/0100%4/4100%13/13
server/middlewares +
+
93.75%15/1675%3/4100%1/193.75%15/16
server/models +
+
97.43%38/3970%7/10100%10/1097.43%38/39
server/routes +
+
80%4/5100%0/00%0/180%4/5
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/prettify.css b/server/coverage/lcov-report/prettify.css new file mode 100644 index 00000000..b317a7cd --- /dev/null +++ b/server/coverage/lcov-report/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/server/coverage/lcov-report/prettify.js b/server/coverage/lcov-report/prettify.js new file mode 100644 index 00000000..b3225238 --- /dev/null +++ b/server/coverage/lcov-report/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/server/coverage/lcov-report/server/app.js.html b/server/coverage/lcov-report/server/app.js.html new file mode 100644 index 00000000..c4126ff9 --- /dev/null +++ b/server/coverage/lcov-report/server/app.js.html @@ -0,0 +1,244 @@ + + + + + + Code coverage report for server/app.js + + + + + + + + + +
+
+

All files / server app.js

+
+ +
+ 93.93% + Statements + 31/33 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 50% + Functions + 1/2 +
+ + +
+ 93.93% + Lines + 31/33 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +541x +1x +  +  +1x +1x +1x +1x +1x +1x +1x +1x +1x +  +  +1x +1x +1x +  +  +1x +  +  +1x +1x +  +  +1x +1x +1x +  +  +1x +  +  +1x +1x +1x +1x +1x +1x +  +  +1x +  +  +  +  +1x +1x +  +  +1x + 
Eif (process.env.NODE_ENV !== "production") {
+  require("dotenv").config();
+}
+ 
+const express = require("express");
+const app = express();
+const port = process.env.PORT || 3000;
+const cors = require("cors");
+const authentication = require("./middlewares/authentication");
+const UserController = require("./controllers/UserController");
+const Controller = require("./controllers/Controller");
+const PubController = require("./controllers/PubController");
+const testingRoutes = require("./routes/testing");
+ 
+// Middleware
+app.use(cors());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+ 
+// Testing routes
+app.use("/testing", testingRoutes);
+ 
+// Route untuk public (tanpa login)
+app.get("/public/devices", PubController.getAllDevices);
+app.get("/public/devices/:id", PubController.getDeviceById);
+ 
+// Route untuk user (login & register)
+app.post("/register", UserController.register);
+app.post("/login", UserController.login);
+app.post("/login/google", UserController.googleLogin);
+ 
+// Middleware authentication untuk semua rute dibawah ini
+app.use(authentication);
+ 
+// Routes that require authentication
+app.get("/devices", Controller.getAllDevices);
+app.get("/devices/:id", Controller.getDeviceById);
+app.put("/users/update", UserController.updateUser);
+app.post("/favorites/:XiaomiDeviceId", Controller.addToFavorites);
+app.get("/favorites", Controller.getFavorites);
+app.delete("/favorites/:XiaomiDeviceId", Controller.removeFromFavorites);
+ 
+// Error handler
+app.use((err, req, res, next) => {
+  console.error(err);
+  res.status(500).json({ message: "Internal server error" });
+});
+ 
+app.listen(port, () => {
+  console.log(`Server running on port ${port}`);
+});
+ 
+module.exports = app;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/controllers/Controller.js.html b/server/coverage/lcov-report/server/controllers/Controller.js.html new file mode 100644 index 00000000..c03d9c8c --- /dev/null +++ b/server/coverage/lcov-report/server/controllers/Controller.js.html @@ -0,0 +1,532 @@ + + + + + + Code coverage report for server/controllers/Controller.js + + + + + + + + + +
+
+

All files / server/controllers Controller.js

+
+ +
+ 78.43% + Statements + 40/51 +
+ + +
+ 62.5% + Branches + 10/16 +
+ + +
+ 85.71% + Functions + 6/7 +
+ + +
+ 78% + Lines + 39/50 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +1501x +  +  +  +  +1x +1x +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +1x +1x +  +1x +  +1x +  +  +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +2x +2x +2x +  +  +2x +  +2x +  +  +  +  +2x +  +  +  +2x +1x +  +  +  +  +  +1x +  +1x +  +  +  +  +  +  +  +  +  +2x +2x +  +1x +  +  +  +  +1x +  +1x +  +1x +1x +  +  +  +  +  +2x +2x +2x +  +  +2x +  +  +  +2x +1x +  +  +  +  +  +1x +  +1x +  +  +  +  +  +  +1x + 
const { XiaomiDevice, User, Favorite } = require("../models");
+ 
+class Controller {
+  // Method untuk mendapatkan semua device Xiaomi
+  static async getAllDevices(req, res) {
+    try {
+      const devices = await XiaomiDevice.findAll();
+ 
+      // Mengecek apakah user sudah login atau belum
+      const isLoggedIn = req.user ? true : false;
+ 
+      // Jika user belum login, hanya kirim informasi terbatas
+      Iif (!isLoggedIn) {
+        const limitedDevices = devices.map((device) => {
+          return {
+            id: device.id,
+            key: device.key,
+            device_name: device.device_name,
+            device_image: device.device_image,
+            price: device.price,
+          };
+        });
+ 
+        return res.status(200).json(limitedDevices);
+      }
+ 
+      // Jika user sudah login, kirim semua informasi device
+      res.status(200).json(devices);
+    } catch (error) {
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+ 
+  // Method untuk mendapatkan detail satu device berdasarkan ID
+  static async getDeviceById(req, res) {
+    try {
+      const { id } = req.params;
+ 
+      const device = await XiaomiDevice.findByPk(id);
+ 
+      Iif (!device) {
+        return res.status(404).json({ message: "Device tidak ditemukan" });
+      }
+ 
+      // Mengecek apakah user sudah login atau belum
+      const isLoggedIn = req.user ? true : false;
+ 
+      // Jika user belum login, hanya kirim informasi terbatas
+      Iif (!isLoggedIn) {
+        const limitedDevice = {
+          id: device.id,
+          key: device.key,
+          device_name: device.device_name,
+          device_image: device.device_image,
+          price: device.price,
+        };
+ 
+        return res.status(200).json(limitedDevice);
+      }
+ 
+      // Jika user sudah login, kirim semua informasi device
+      res.status(200).json(device);
+    } catch (error) {
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+ 
+  // Method untuk menambahkan device ke daftar favorit
+  static async addToFavorites(req, res) {
+    try {
+      const UserId = req.user.id; // Diambil dari middleware authentication
+      const { XiaomiDeviceId } = req.params;
+ 
+      // Memastikan device ada
+      const device = await XiaomiDevice.findByPk(XiaomiDeviceId);
+ 
+      Iif (!device) {
+        return res.status(404).json({ message: "Device tidak ditemukan" });
+      }
+ 
+      // Mengecek apakah device sudah ada di daftar favorit user
+      const existingFavorite = await Favorite.findOne({
+        where: { UserId, XiaomiDeviceId },
+      });
+ 
+      if (existingFavorite) {
+        return res
+          .status(400)
+          .json({ message: "Device sudah ada di daftar favorit Anda" });
+      }
+ 
+      // Menambahkan device ke daftar favorit
+      await Favorite.create({ UserId, XiaomiDeviceId });
+ 
+      res
+        .status(201)
+        .json({ message: "Device berhasil ditambahkan ke favorit" });
+    } catch (error) {
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+ 
+  // Method untuk mendapatkan daftar favorit user
+  static async getFavorites(req, res) {
+    try {
+      const UserId = req.user.id; // Add this line to get UserId from authenticated request
+ 
+      const favorites = await Favorite.findAll({
+        where: { UserId },
+        include: [XiaomiDevice],
+      });
+ 
+      const favoriteDevices = favorites.map((fav) => fav.XiaomiDevice);
+ 
+      res.status(200).json(favoriteDevices);
+    } catch (error) {
+      console.error(error); // Add logging for debugging
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+ 
+  // Method untuk menghapus device dari daftar favorit
+  static async removeFromFavorites(req, res) {
+    try {
+      const UserId = req.user.id; // Diambil dari middleware authentication
+      const { XiaomiDeviceId } = req.params;
+ 
+      // Memastikan device ada di daftar favorit user
+      const favorite = await Favorite.findOne({
+        where: { UserId, XiaomiDeviceId },
+      });
+ 
+      if (!favorite) {
+        return res
+          .status(404)
+          .json({ message: "Device tidak ada di daftar favorit Anda" });
+      }
+ 
+      // Menghapus device dari daftar favorit
+      await favorite.destroy();
+ 
+      res.status(200).json({ message: "Device berhasil dihapus dari favorit" });
+    } catch (error) {
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+}
+ 
+module.exports = Controller;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/controllers/PubController.js.html b/server/coverage/lcov-report/server/controllers/PubController.js.html new file mode 100644 index 00000000..e56ac4cb --- /dev/null +++ b/server/coverage/lcov-report/server/controllers/PubController.js.html @@ -0,0 +1,331 @@ + + + + + + Code coverage report for server/controllers/PubController.js + + + + + + + + + +
+
+

All files / server/controllers PubController.js

+
+ +
+ 6.25% + Statements + 2/32 +
+ + +
+ 0% + Branches + 0/18 +
+ + +
+ 0% + Functions + 0/3 +
+ + +
+ 6.89% + Lines + 2/29 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x + 
const {
+  XiaomiDevice,
+  Sequelize: { Op },
+} = require("../models");
+ 
+class PubController {
+  static async getAllDevices(req, res) {
+    try {
+      const { search, sort, minPrice, maxPrice } = req.query;
+ 
+      // Build the query options
+      let queryOptions = {};
+ 
+      // Search functionality
+      if (search) {
+        queryOptions.where = {
+          [Op.or]: [
+            { device_name: { [Op.iLike]: `%${search}%` } },
+            { key: { [Op.iLike]: `%${search}%` } },
+          ],
+        };
+      }
+ 
+      // Price filter
+      if (minPrice || maxPrice) {
+        queryOptions.where = {
+          ...queryOptions.where,
+          price: {},
+        };
+        if (minPrice) queryOptions.where.price[Op.gte] = minPrice;
+        if (maxPrice) queryOptions.where.price[Op.lte] = maxPrice;
+      }
+ 
+      // Sort functionality
+      if (sort) {
+        const [field, order] = sort.split(":");
+        const validFields = ["price", "device_name"];
+        const validOrders = ["asc", "desc"];
+ 
+        if (validFields.includes(field) && validOrders.includes(order)) {
+          queryOptions.order = [[field, order.toUpperCase()]];
+        }
+      }
+ 
+      const devices = await XiaomiDevice.findAll(queryOptions);
+ 
+      // Map to limited device info
+      const limitedDevices = devices.map((device) => ({
+        id: device.id,
+        key: device.key,
+        device_name: device.device_name,
+        device_image: device.device_image,
+        price: device.price,
+      }));
+ 
+      res.status(200).json(limitedDevices);
+    } catch (error) {
+      console.error("Error in getAllDevices:", error);
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+ 
+  // Method untuk mendapatkan detail satu device berdasarkan ID dengan informasi lengkap
+  static async getDeviceById(req, res) {
+    try {
+      const { id } = req.params;
+ 
+      const device = await XiaomiDevice.findByPk(id);
+ 
+      if (!device) {
+        return res.status(404).json({ message: "Device tidak ditemukan" });
+      }
+ 
+      // Kirim semua informasi device
+      res.status(200).json(device);
+    } catch (error) {
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+}
+ 
+module.exports = PubController;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/controllers/UserController.js.html b/server/coverage/lcov-report/server/controllers/UserController.js.html new file mode 100644 index 00000000..2e73afff --- /dev/null +++ b/server/coverage/lcov-report/server/controllers/UserController.js.html @@ -0,0 +1,580 @@ + + + + + + Code coverage report for server/controllers/UserController.js + + + + + + + + + +
+
+

All files / server/controllers UserController.js

+
+ +
+ 35.59% + Statements + 21/59 +
+ + +
+ 34.61% + Branches + 9/26 +
+ + +
+ 50% + Functions + 2/4 +
+ + +
+ 36.84% + Lines + 21/57 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +1661x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +2x +  +2x +  +  +  +  +  +1x +  +  +  +  +  +1x +  +  +  +1x +  +  +  +  +  +  +  +3x +3x +  +3x +  +  +  +  +  +3x +  +  +  +3x +1x +  +  +2x +  +2x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x + 
const { User } = require("../models");
+const { comparePassword } = require("../helpers/bcrypt");
+const { createToken } = require("../helpers/jwt");
+const { OAuth2Client } = require("google-auth-library");
+const client = new OAuth2Client();
+ 
+class UserController {
+  static async googleLogin(req, res) {
+    try {
+      const { googleToken } = req.body;
+      const ticket = await client.verifyIdToken({
+        idToken: googleToken,
+        audience: process.env.GOOGLE_CLIENT_ID,
+      });
+      const payload = ticket.getPayload();
+ 
+      let user = await User.findOne({
+        where: { email: payload.email },
+      });
+ 
+      if (!user) {
+        user = await User.create({
+          username: payload.name,
+          email: payload.email,
+          password: payload.sub,
+        });
+      }
+ 
+      const tokenPayload = {
+        id: user.id,
+        email: user.email,
+        username: user.username,
+      };
+ 
+      const token = createToken(tokenPayload);
+      res.status(200).json({
+        access_token: token,
+        id: user.id,
+        username: user.username,
+        email: user.email,
+      });
+    } catch (error) {
+      console.log(error);
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+ 
+  static async register(req, res) {
+    try {
+      const { username, email, password } = req.body;
+ 
+      const newUser = await User.create({
+        username,
+        email,
+        password,
+      });
+ 
+      res.status(201).json({
+        id: newUser.id,
+        username: newUser.username,
+        email: newUser.email,
+      });
+    } catch (error) {
+      if (
+        error.name === "SequelizeValidationError" ||
+        error.name === "SequelizeUniqueConstraintError"
+      ) {
+        res.status(400).json({ message: error.errors[0].message });
+      } else E{
+        res.status(500).json({ message: "Internal server error" });
+      }
+    }
+  }
+ 
+  static async login(req, res) {
+    try {
+      const { email, password } = req.body;
+ 
+      Iif (!email || !password) {
+        return res
+          .status(400)
+          .json({ message: "Email and password are required" });
+      }
+ 
+      const user = await User.findOne({
+        where: { email },
+      });
+ 
+      if (!user) {
+        return res.status(404).json({ message: "User not found" });
+      }
+ 
+      const isPasswordValid = comparePassword(password, user.password);
+ 
+      Eif (!isPasswordValid) {
+        return res.status(401).json({ message: "Invalid password" });
+      }
+ 
+      const payload = {
+        id: user.id,
+        email: user.email,
+        username: user.username,
+      };
+ 
+      const token = createToken(payload);
+ 
+      res.status(200).json({
+        access_token: token,
+        id: user.id,
+        username: user.username,
+        email: user.email,
+      });
+    } catch (error) {
+      console.error(error);
+      res.status(500).json({ message: "Internal server error" });
+    }
+  }
+ 
+  static async updateUser(req, res) {
+    try {
+      const userId = req.user.id;
+      const { username, email } = req.body;
+ 
+      const user = await User.findByPk(userId);
+ 
+      if (!user) {
+        return res.status(404).json({ message: "User tidak ditemukan" });
+      }
+ 
+      const updateFields = {};
+ 
+      if (username) updateFields.username = username;
+      if (email) updateFields.email = email;
+ 
+      if (Object.keys(updateFields).length === 0) {
+        return res
+          .status(400)
+          .json({ message: "Tidak ada data yang diupdate" });
+      }
+ 
+      await user.update(updateFields);
+ 
+      res.status(200).json({
+        message: "User berhasil diupdate",
+        user: {
+          id: user.id,
+          username: user.username,
+          email: user.email,
+        },
+      });
+    } catch (error) {
+      if (
+        error.name === "SequelizeValidationError" ||
+        error.name === "SequelizeUniqueConstraintError"
+      ) {
+        res.status(400).json({ message: error.errors[0].message });
+      } else {
+        console.error(error);
+        res.status(500).json({ message: "Internal server error" });
+      }
+    }
+  }
+}
+ 
+module.exports = UserController;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/controllers/index.html b/server/coverage/lcov-report/server/controllers/index.html new file mode 100644 index 00000000..1bf0aa4b --- /dev/null +++ b/server/coverage/lcov-report/server/controllers/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for server/controllers + + + + + + + + + +
+
+

All files server/controllers

+
+ +
+ 44.36% + Statements + 63/142 +
+ + +
+ 31.66% + Branches + 19/60 +
+ + +
+ 57.14% + Functions + 8/14 +
+ + +
+ 45.58% + Lines + 62/136 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Controller.js +
+
78.43%40/5162.5%10/1685.71%6/778%39/50
PubController.js +
+
6.25%2/320%0/180%0/36.89%2/29
UserController.js +
+
35.59%21/5934.61%9/2650%2/436.84%21/57
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/helpers/bcrypt.js.html b/server/coverage/lcov-report/server/helpers/bcrypt.js.html new file mode 100644 index 00000000..15a5f294 --- /dev/null +++ b/server/coverage/lcov-report/server/helpers/bcrypt.js.html @@ -0,0 +1,127 @@ + + + + + + Code coverage report for server/helpers/bcrypt.js + + + + + + + + + +
+
+

All files / server/helpers bcrypt.js

+
+ +
+ 100% + Statements + 6/6 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +151x +  +1x +4x +  +  +1x +2x +  +  +1x +  +  +  + 
const bcrypt = require("bcryptjs");
+ 
+const hashPassword = (password) => {
+  return bcrypt.hashSync(password, 10);
+};
+ 
+const comparePassword = (password, hashedPassword) => {
+  return bcrypt.compareSync(password, hashedPassword);
+};
+ 
+module.exports = {
+  hashPassword,
+  comparePassword,
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/helpers/index.html b/server/coverage/lcov-report/server/helpers/index.html new file mode 100644 index 00000000..9dd88145 --- /dev/null +++ b/server/coverage/lcov-report/server/helpers/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for server/helpers + + + + + + + + + +
+
+

All files server/helpers

+
+ +
+ 100% + Statements + 13/13 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 13/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
bcrypt.js +
+
100%6/6100%0/0100%2/2100%6/6
jwt.js +
+
100%7/7100%0/0100%2/2100%7/7
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/helpers/jwt.js.html b/server/coverage/lcov-report/server/helpers/jwt.js.html new file mode 100644 index 00000000..f9921e24 --- /dev/null +++ b/server/coverage/lcov-report/server/helpers/jwt.js.html @@ -0,0 +1,130 @@ + + + + + + Code coverage report for server/helpers/jwt.js + + + + + + + + + +
+
+

All files / server/helpers jwt.js

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +161x +1x +  +1x +1x +  +  +1x +8x +  +  +1x +  +  +  + 
const jwt = require("jsonwebtoken");
+const SECRET_KEY = process.env.JWT_SECRET;
+ 
+const createToken = (payload) => {
+  return jwt.sign(payload, SECRET_KEY);
+};
+ 
+const verifyToken = (token) => {
+  return jwt.verify(token, SECRET_KEY);
+};
+ 
+module.exports = {
+  createToken,
+  verifyToken,
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/index.html b/server/coverage/lcov-report/server/index.html new file mode 100644 index 00000000..c02bb2ca --- /dev/null +++ b/server/coverage/lcov-report/server/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for server + + + + + + + + + +
+
+

All files server

+
+ +
+ 93.93% + Statements + 31/33 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 50% + Functions + 1/2 +
+ + +
+ 93.93% + Lines + 31/33 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
app.js +
+
93.93%31/3375%3/450%1/293.93%31/33
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/middlewares/authentication.js.html b/server/coverage/lcov-report/server/middlewares/authentication.js.html new file mode 100644 index 00000000..170721d9 --- /dev/null +++ b/server/coverage/lcov-report/server/middlewares/authentication.js.html @@ -0,0 +1,184 @@ + + + + + + Code coverage report for server/middlewares/authentication.js + + + + + + + + + +
+
+

All files / server/middlewares authentication.js

+
+ +
+ 93.75% + Statements + 15/16 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 93.75% + Lines + 15/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +341x +1x +  +1x +9x +9x +  +9x +1x +  +  +8x +  +7x +  +7x +  +  +  +7x +  +  +  +  +  +7x +  +1x +1x +  +  +  +1x + 
const { verifyToken } = require("../helpers/jwt");
+const { User } = require("../models");
+ 
+const authentication = async (req, res, next) => {
+  try {
+    const { access_token } = req.headers;
+ 
+    if (!access_token) {
+      return next(); // Allow public routes to proceed
+    }
+ 
+    const payload = verifyToken(access_token);
+ 
+    const user = await User.findByPk(payload.id);
+ 
+    Iif (!user) {
+      return res.status(404).json({ message: "User not found" }); // Changed to 404
+    }
+ 
+    req.user = {
+      id: user.id,
+      email: user.email,
+      username: user.username,
+    };
+ 
+    next();
+  } catch (error) {
+    console.error(error);
+    return res.status(401).json({ message: "Invalid token" });
+  }
+};
+ 
+module.exports = authentication;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/middlewares/index.html b/server/coverage/lcov-report/server/middlewares/index.html new file mode 100644 index 00000000..d15f3153 --- /dev/null +++ b/server/coverage/lcov-report/server/middlewares/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for server/middlewares + + + + + + + + + +
+
+

All files server/middlewares

+
+ +
+ 93.75% + Statements + 15/16 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 93.75% + Lines + 15/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
authentication.js +
+
93.75%15/1675%3/4100%1/193.75%15/16
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/models/favorite.js.html b/server/coverage/lcov-report/server/models/favorite.js.html new file mode 100644 index 00000000..c2990794 --- /dev/null +++ b/server/coverage/lcov-report/server/models/favorite.js.html @@ -0,0 +1,232 @@ + + + + + + Code coverage report for server/models/favorite.js + + + + + + + + + +
+
+

All files / server/models favorite.js

+
+ +
+ 100% + Statements + 6/6 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50  +1x +1x +  +  +  +  +  +  +  +  +1x +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  + 
"use strict";
+const { Model } = require("sequelize");
+module.exports = (sequelize, DataTypes) => {
+  class Favorite extends Model {
+    /**
+     * Helper method for defining associations.
+     * This method is not a part of Sequelize lifecycle.
+     * The `models/index` file will call this method automatically.
+     */
+    static associate(models) {
+      // define association here
+      Favorite.belongsTo(models.User, { foreignKey: "UserId" });
+      Favorite.belongsTo(models.XiaomiDevice, { foreignKey: "XiaomiDeviceId" });
+    }
+  }
+  Favorite.init(
+    {
+      UserId: {
+        type: DataTypes.INTEGER,
+        allowNull: false,
+        validate: {
+          notNull: {
+            msg: "User ID tidak boleh null",
+          },
+          notEmpty: {
+            msg: "User ID tidak boleh kosong",
+          },
+        },
+      },
+      XiaomiDeviceId: {
+        type: DataTypes.INTEGER,
+        allowNull: false,
+        validate: {
+          notNull: {
+            msg: "Xiaomi Device ID tidak boleh null",
+          },
+          notEmpty: {
+            msg: "Xiaomi Device ID tidak boleh kosong",
+          },
+        },
+      },
+    },
+    {
+      sequelize,
+      modelName: "Favorite",
+    }
+  );
+  return Favorite;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/models/index.html b/server/coverage/lcov-report/server/models/index.html new file mode 100644 index 00000000..d4113a89 --- /dev/null +++ b/server/coverage/lcov-report/server/models/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for server/models + + + + + + + + + +
+
+

All files server/models

+
+ +
+ 97.43% + Statements + 38/39 +
+ + +
+ 70% + Branches + 7/10 +
+ + +
+ 100% + Functions + 10/10 +
+ + +
+ 97.43% + Lines + 38/39 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
favorite.js +
+
100%6/6100%0/0100%2/2100%6/6
index.js +
+
95.23%20/2170%7/10100%3/395.23%20/21
user.js +
+
100%7/7100%0/0100%3/3100%7/7
xiaomidevice.js +
+
100%5/5100%0/0100%2/2100%5/5
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/models/index.js.html b/server/coverage/lcov-report/server/models/index.js.html new file mode 100644 index 00000000..0f79bb7c --- /dev/null +++ b/server/coverage/lcov-report/server/models/index.js.html @@ -0,0 +1,214 @@ + + + + + + Code coverage report for server/models/index.js + + + + + + + + + +
+
+

All files / server/models index.js

+
+ +
+ 95.23% + Statements + 20/21 +
+ + +
+ 70% + Branches + 7/10 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 95.23% + Lines + 20/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44  +  +1x +1x +1x +1x +1x +1x +1x +1x +  +  +1x +  +  +1x +  +  +1x +  +  +4x +  +  +  +  +  +  +  +3x +3x +  +  +1x +3x +3x +  +  +  +1x +1x +  +1x + 
'use strict';
+ 
+const fs = require('fs');
+const path = require('path');
+const Sequelize = require('sequelize');
+const process = require('process');
+const basename = path.basename(__filename);
+const env = process.env.NODE_ENV || 'development';
+const config = require(__dirname + '/../config/config.json')[env];
+const db = {};
+ 
+let sequelize;
+Iif (config.use_env_variable) {
+  sequelize = new Sequelize(process.env[config.use_env_variable], config);
+} else {
+  sequelize = new Sequelize(config.database, config.username, config.password, config);
+}
+ 
+fs
+  .readdirSync(__dirname)
+  .filter(file => {
+    return (
+      file.indexOf('.') !== 0 &&
+      file !== basename &&
+      file.slice(-3) === '.js' &&
+      file.indexOf('.test.js') === -1
+    );
+  })
+  .forEach(file => {
+    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
+    db[model.name] = model;
+  });
+ 
+Object.keys(db).forEach(modelName => {
+  Eif (db[modelName].associate) {
+    db[modelName].associate(db);
+  }
+});
+ 
+db.sequelize = sequelize;
+db.Sequelize = Sequelize;
+ 
+module.exports = db;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/models/user.js.html b/server/coverage/lcov-report/server/models/user.js.html new file mode 100644 index 00000000..008831ef --- /dev/null +++ b/server/coverage/lcov-report/server/models/user.js.html @@ -0,0 +1,298 @@ + + + + + + Code coverage report for server/models/user.js + + + + + + + + + +
+
+

All files / server/models user.js

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72  +1x +1x +  +1x +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +1x +  + 
"use strict";
+const { Model } = require("sequelize");
+const { hashPassword } = require("../helpers/bcrypt");
+ 
+module.exports = (sequelize, DataTypes) => {
+  class User extends Model {
+    /**
+     * Helper method for defining associations.
+     * This method is not a part of Sequelize lifecycle.
+     * The `models/index` file will call this method automatically.
+     */
+    static associate(models) {
+      // define association here
+      User.belongsToMany(models.XiaomiDevice, {
+        through: models.Favorite,
+        foreignKey: "UserId",
+      });
+    }
+  }
+  User.init(
+    {
+      username: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          notNull: {
+            msg: "Username tidak boleh null",
+          },
+          notEmpty: {
+            msg: "Username tidak boleh kosong",
+          },
+        },
+      },
+      email: {
+        type: DataTypes.STRING,
+        unique: true,
+        allowNull: false,
+        validate: {
+          notNull: {
+            msg: "Email tidak boleh null",
+          },
+          notEmpty: {
+            msg: "Email tidak boleh kosong",
+          },
+        },
+      },
+      password: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          notNull: {
+            msg: "Password tidak boleh null",
+          },
+          notEmpty: {
+            msg: "Password tidak boleh kosong",
+          },
+        },
+      },
+    },
+    {
+      hooks: {
+        beforeCreate: (user) => {
+          user.password = hashPassword(user.password);
+        },
+      },
+      sequelize,
+      modelName: "User",
+    }
+  );
+  return User;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/models/xiaomidevice.js.html b/server/coverage/lcov-report/server/models/xiaomidevice.js.html new file mode 100644 index 00000000..1851c516 --- /dev/null +++ b/server/coverage/lcov-report/server/models/xiaomidevice.js.html @@ -0,0 +1,214 @@ + + + + + + Code coverage report for server/models/xiaomidevice.js + + + + + + + + + +
+
+

All files / server/models xiaomidevice.js

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44  +1x +1x +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  + 
"use strict";
+const { Model } = require("sequelize");
+module.exports = (sequelize, DataTypes) => {
+  class XiaomiDevice extends Model {
+    /**
+     * Helper method for defining associations.
+     * This method is not a part of Sequelize lifecycle.
+     * The `models/index` file will call this method automatically.
+     */
+    static associate(models) {
+      // define association here
+      XiaomiDevice.belongsToMany(models.User, {
+        through: models.Favorite,
+        foreignKey: "XiaomiDeviceId",
+      });
+    }
+  }
+  XiaomiDevice.init(
+    {
+      key: DataTypes.STRING,
+      device_name: DataTypes.STRING,
+      device_image: DataTypes.STRING,
+      display_size: DataTypes.STRING,
+      display_res: DataTypes.STRING,
+      camera: DataTypes.STRING,
+      video: DataTypes.STRING,
+      ram: DataTypes.STRING,
+      chipset: DataTypes.STRING,
+      battery: DataTypes.STRING,
+      batteryType: DataTypes.STRING,
+      body: DataTypes.STRING,
+      os_type: DataTypes.STRING,
+      storage: DataTypes.STRING,
+      comment: DataTypes.STRING,
+      price: DataTypes.INTEGER,
+    },
+    {
+      sequelize,
+      modelName: "XiaomiDevice",
+    }
+  );
+  return XiaomiDevice;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/routes/index.html b/server/coverage/lcov-report/server/routes/index.html new file mode 100644 index 00000000..1ba5260b --- /dev/null +++ b/server/coverage/lcov-report/server/routes/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for server/routes + + + + + + + + + +
+
+

All files server/routes

+
+ +
+ 80% + Statements + 4/5 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 80% + Lines + 4/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
testing.js +
+
80%4/5100%0/00%0/180%4/5
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/server/routes/testing.js.html b/server/coverage/lcov-report/server/routes/testing.js.html new file mode 100644 index 00000000..591a06dc --- /dev/null +++ b/server/coverage/lcov-report/server/routes/testing.js.html @@ -0,0 +1,112 @@ + + + + + + Code coverage report for server/routes/testing.js + + + + + + + + + +
+
+

All files / server/routes testing.js

+
+ +
+ 80% + Statements + 4/5 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 80% + Lines + 4/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +101x +1x +  +  +1x +  +  +  +1x + 
const express = require("express");
+const router = express.Router();
+ 
+// Rute testing dasar
+router.get("/health", (req, res) => {
+  res.status(200).json({ message: "API is running" });
+});
+ 
+module.exports = router;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/server/coverage/lcov-report/sort-arrow-sprite.png b/server/coverage/lcov-report/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/server/coverage/lcov-report/sorter.js b/server/coverage/lcov-report/sorter.js new file mode 100644 index 00000000..2bb296a8 --- /dev/null +++ b/server/coverage/lcov-report/sorter.js @@ -0,0 +1,196 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + if ( + row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()) + ) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/server/coverage/lcov.info b/server/coverage/lcov.info new file mode 100644 index 00000000..982ea0f1 --- /dev/null +++ b/server/coverage/lcov.info @@ -0,0 +1,492 @@ +TN: +SF:app.js +FN:44,(anonymous_0) +FN:49,(anonymous_1) +FNF:2 +FNH:1 +FNDA:0,(anonymous_0) +FNDA:1,(anonymous_1) +DA:1,1 +DA:2,1 +DA:5,1 +DA:6,1 +DA:7,1 +DA:8,1 +DA:9,1 +DA:10,1 +DA:11,1 +DA:12,1 +DA:13,1 +DA:16,1 +DA:17,1 +DA:18,1 +DA:21,1 +DA:24,1 +DA:25,1 +DA:28,1 +DA:29,1 +DA:30,1 +DA:33,1 +DA:36,1 +DA:37,1 +DA:38,1 +DA:39,1 +DA:40,1 +DA:41,1 +DA:44,1 +DA:45,0 +DA:46,0 +DA:49,1 +DA:50,1 +DA:53,1 +LF:33 +LH:31 +BRDA:1,0,0,1 +BRDA:1,0,1,0 +BRDA:7,1,0,1 +BRDA:7,1,1,1 +BRF:4 +BRH:3 +end_of_record +TN: +SF:controllers\Controller.js +FN:5,(anonymous_0) +FN:14,(anonymous_1) +FN:35,(anonymous_2) +FN:69,(anonymous_3) +FN:104,(anonymous_4) +FN:113,(anonymous_5) +FN:123,(anonymous_6) +FNF:7 +FNH:6 +FNDA:1,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:1,(anonymous_2) +FNDA:2,(anonymous_3) +FNDA:2,(anonymous_4) +FNDA:1,(anonymous_5) +FNDA:2,(anonymous_6) +DA:1,1 +DA:6,1 +DA:7,1 +DA:10,1 +DA:13,1 +DA:14,0 +DA:15,0 +DA:24,0 +DA:28,1 +DA:30,0 +DA:36,1 +DA:37,1 +DA:39,1 +DA:41,1 +DA:42,0 +DA:46,1 +DA:49,1 +DA:50,0 +DA:58,0 +DA:62,1 +DA:64,0 +DA:70,2 +DA:71,2 +DA:72,2 +DA:75,2 +DA:77,2 +DA:78,0 +DA:82,2 +DA:86,2 +DA:87,1 +DA:93,1 +DA:95,1 +DA:99,0 +DA:105,2 +DA:106,2 +DA:108,1 +DA:113,1 +DA:115,1 +DA:117,1 +DA:118,1 +DA:124,2 +DA:125,2 +DA:126,2 +DA:129,2 +DA:133,2 +DA:134,1 +DA:140,1 +DA:142,1 +DA:144,0 +DA:149,1 +LF:50 +LH:39 +BRDA:10,0,0,1 +BRDA:10,0,1,0 +BRDA:13,1,0,0 +BRDA:13,1,1,1 +BRDA:41,2,0,0 +BRDA:41,2,1,1 +BRDA:46,3,0,1 +BRDA:46,3,1,0 +BRDA:49,4,0,0 +BRDA:49,4,1,1 +BRDA:77,5,0,0 +BRDA:77,5,1,2 +BRDA:86,6,0,1 +BRDA:86,6,1,1 +BRDA:133,7,0,1 +BRDA:133,7,1,1 +BRF:16 +BRH:10 +end_of_record +TN: +SF:controllers\PubController.js +FN:7,(anonymous_0) +FN:48,(anonymous_1) +FN:64,(anonymous_2) +FNF:3 +FNH:0 +FNDA:0,(anonymous_0) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +DA:4,1 +DA:8,0 +DA:9,0 +DA:12,0 +DA:15,0 +DA:16,0 +DA:25,0 +DA:26,0 +DA:30,0 +DA:31,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:40,0 +DA:41,0 +DA:45,0 +DA:48,0 +DA:56,0 +DA:58,0 +DA:59,0 +DA:65,0 +DA:66,0 +DA:68,0 +DA:70,0 +DA:71,0 +DA:75,0 +DA:77,0 +DA:82,1 +LF:29 +LH:2 +BRDA:15,0,0,0 +BRDA:15,0,1,0 +BRDA:25,1,0,0 +BRDA:25,1,1,0 +BRDA:25,2,0,0 +BRDA:25,2,1,0 +BRDA:30,3,0,0 +BRDA:30,3,1,0 +BRDA:31,4,0,0 +BRDA:31,4,1,0 +BRDA:35,5,0,0 +BRDA:35,5,1,0 +BRDA:40,6,0,0 +BRDA:40,6,1,0 +BRDA:40,7,0,0 +BRDA:40,7,1,0 +BRDA:70,8,0,0 +BRDA:70,8,1,0 +BRF:18 +BRH:0 +end_of_record +TN: +SF:controllers\UserController.js +FN:8,(anonymous_0) +FN:48,(anonymous_1) +FN:75,(anonymous_2) +FN:119,(anonymous_3) +FNF:4 +FNH:2 +FNDA:0,(anonymous_0) +FNDA:2,(anonymous_1) +FNDA:3,(anonymous_2) +FNDA:0,(anonymous_3) +DA:1,1 +DA:2,1 +DA:3,1 +DA:4,1 +DA:5,1 +DA:9,0 +DA:10,0 +DA:11,0 +DA:15,0 +DA:17,0 +DA:21,0 +DA:22,0 +DA:29,0 +DA:35,0 +DA:36,0 +DA:43,0 +DA:44,0 +DA:49,2 +DA:50,2 +DA:52,2 +DA:58,1 +DA:64,1 +DA:68,1 +DA:70,0 +DA:76,3 +DA:77,3 +DA:79,3 +DA:80,0 +DA:85,3 +DA:89,3 +DA:90,1 +DA:93,2 +DA:95,2 +DA:96,2 +DA:99,0 +DA:105,0 +DA:107,0 +DA:114,0 +DA:115,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:126,0 +DA:127,0 +DA:130,0 +DA:132,0 +DA:133,0 +DA:135,0 +DA:136,0 +DA:141,0 +DA:143,0 +DA:152,0 +DA:156,0 +DA:158,0 +DA:159,0 +DA:165,1 +LF:57 +LH:21 +BRDA:21,0,0,0 +BRDA:21,0,1,0 +BRDA:64,1,0,1 +BRDA:64,1,1,0 +BRDA:65,2,0,1 +BRDA:65,2,1,1 +BRDA:79,3,0,0 +BRDA:79,3,1,3 +BRDA:79,4,0,3 +BRDA:79,4,1,3 +BRDA:89,5,0,1 +BRDA:89,5,1,2 +BRDA:95,6,0,2 +BRDA:95,6,1,0 +BRDA:126,7,0,0 +BRDA:126,7,1,0 +BRDA:132,8,0,0 +BRDA:132,8,1,0 +BRDA:133,9,0,0 +BRDA:133,9,1,0 +BRDA:135,10,0,0 +BRDA:135,10,1,0 +BRDA:152,11,0,0 +BRDA:152,11,1,0 +BRDA:153,12,0,0 +BRDA:153,12,1,0 +BRF:26 +BRH:9 +end_of_record +TN: +SF:helpers\bcrypt.js +FN:3,(anonymous_0) +FN:7,(anonymous_1) +FNF:2 +FNH:2 +FNDA:4,(anonymous_0) +FNDA:2,(anonymous_1) +DA:1,1 +DA:3,1 +DA:4,4 +DA:7,1 +DA:8,2 +DA:11,1 +LF:6 +LH:6 +BRF:0 +BRH:0 +end_of_record +TN: +SF:helpers\jwt.js +FN:4,(anonymous_0) +FN:8,(anonymous_1) +FNF:2 +FNH:2 +FNDA:1,(anonymous_0) +FNDA:8,(anonymous_1) +DA:1,1 +DA:2,1 +DA:4,1 +DA:5,1 +DA:8,1 +DA:9,8 +DA:12,1 +LF:7 +LH:7 +BRF:0 +BRH:0 +end_of_record +TN: +SF:middlewares\authentication.js +FN:4,(anonymous_0) +FNF:1 +FNH:1 +FNDA:9,(anonymous_0) +DA:1,1 +DA:2,1 +DA:4,1 +DA:5,9 +DA:6,9 +DA:8,9 +DA:9,1 +DA:12,8 +DA:14,7 +DA:16,7 +DA:17,0 +DA:20,7 +DA:26,7 +DA:28,1 +DA:29,1 +DA:33,1 +LF:16 +LH:15 +BRDA:8,0,0,1 +BRDA:8,0,1,8 +BRDA:16,1,0,0 +BRDA:16,1,1,7 +BRF:4 +BRH:3 +end_of_record +TN: +SF:models\favorite.js +FN:3,(anonymous_0) +FN:10,(anonymous_1) +FNF:2 +FNH:2 +FNDA:1,(anonymous_0) +FNDA:1,(anonymous_1) +DA:2,1 +DA:3,1 +DA:12,1 +DA:13,1 +DA:16,1 +DA:48,1 +LF:6 +LH:6 +BRF:0 +BRH:0 +end_of_record +TN: +SF:models\index.js +FN:21,(anonymous_0) +FN:29,(anonymous_1) +FN:34,(anonymous_2) +FNF:3 +FNH:3 +FNDA:4,(anonymous_0) +FNDA:3,(anonymous_1) +FNDA:3,(anonymous_2) +DA:3,1 +DA:4,1 +DA:5,1 +DA:6,1 +DA:7,1 +DA:8,1 +DA:9,1 +DA:10,1 +DA:13,1 +DA:14,0 +DA:16,1 +DA:19,1 +DA:22,4 +DA:30,3 +DA:31,3 +DA:34,1 +DA:35,3 +DA:36,3 +DA:40,1 +DA:41,1 +DA:43,1 +LF:21 +LH:20 +BRDA:8,0,0,1 +BRDA:8,0,1,0 +BRDA:13,1,0,0 +BRDA:13,1,1,1 +BRDA:23,2,0,4 +BRDA:23,2,1,4 +BRDA:23,2,2,3 +BRDA:23,2,3,3 +BRDA:35,3,0,3 +BRDA:35,3,1,0 +BRF:10 +BRH:7 +end_of_record +TN: +SF:models\user.js +FN:5,(anonymous_0) +FN:12,(anonymous_1) +FN:62,(anonymous_2) +FNF:3 +FNH:3 +FNDA:1,(anonymous_0) +FNDA:1,(anonymous_1) +FNDA:3,(anonymous_2) +DA:2,1 +DA:3,1 +DA:5,1 +DA:14,1 +DA:20,1 +DA:63,3 +DA:70,1 +LF:7 +LH:7 +BRF:0 +BRH:0 +end_of_record +TN: +SF:models\xiaomidevice.js +FN:3,(anonymous_0) +FN:10,(anonymous_1) +FNF:2 +FNH:2 +FNDA:1,(anonymous_0) +FNDA:1,(anonymous_1) +DA:2,1 +DA:3,1 +DA:12,1 +DA:18,1 +DA:42,1 +LF:5 +LH:5 +BRF:0 +BRH:0 +end_of_record +TN: +SF:routes\testing.js +FN:5,(anonymous_0) +FNF:1 +FNH:0 +FNDA:0,(anonymous_0) +DA:1,1 +DA:2,1 +DA:5,1 +DA:6,0 +DA:9,1 +LF:5 +LH:4 +BRF:0 +BRH:0 +end_of_record diff --git a/server/package.json b/server/package.json index 24285928..fe8be3b5 100644 --- a/server/package.json +++ b/server/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "main": "index.js", "scripts": { - "test": "npx jest --verbose --detectOpenHandles --forceExit", + "test": "npx jest --coverage --verbose --detectOpenHandles --forceExit", "dev": "npx nodemon app" }, "keywords": [], From 141f942263e17b0209ede0083e9e13a7b97ed72a Mon Sep 17 00:00:00 2001 From: Kendy Date: Mon, 5 May 2025 22:19:30 +0700 Subject: [PATCH 07/16] chore : done setup react-redux --- client/package-lock.json | 113 ++++++++++++++++++++++++++- client/package.json | 2 + client/src/main.jsx | 6 +- client/src/pages/detail.page.jsx | 78 +++---------------- client/src/pages/favorite.page.jsx | 51 ++++--------- client/src/store/favoriteSlice.js | 118 +++++++++++++++++++++++++++++ client/src/store/index.js | 10 +++ 7 files changed, 272 insertions(+), 106 deletions(-) create mode 100644 client/src/store/favoriteSlice.js create mode 100644 client/src/store/index.js diff --git a/client/package-lock.json b/client/package-lock.json index f9f5c6b7..ccd6e245 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,10 +8,12 @@ "name": "client", "version": "0.0.0", "dependencies": { + "@reduxjs/toolkit": "^2.7.0", "axios": "^1.9.0", "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-redux": "^9.2.0", "react-router": "^7.5.3" }, "devDependencies": { @@ -1017,6 +1019,32 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.7.0.tgz", + "integrity": "sha512-XVwolG6eTqwV0N8z/oDlN93ITCIGIop6leXlGJI/4EKy+0POYkR+ABHRSdGXY+0MQvJBP8yAzh+EYFxTuvmBiQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.40.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", @@ -1297,6 +1325,18 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1360,7 +1400,7 @@ "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1376,6 +1416,12 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", @@ -1662,7 +1708,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -2297,6 +2343,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -2777,6 +2833,29 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -2810,6 +2889,27 @@ } } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3018,6 +3118,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", diff --git a/client/package.json b/client/package.json index 0600bcf0..ce012441 100644 --- a/client/package.json +++ b/client/package.json @@ -10,10 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "@reduxjs/toolkit": "^2.7.0", "axios": "^1.9.0", "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-redux": "^9.2.0", "react-router": "^7.5.3" }, "devDependencies": { diff --git a/client/src/main.jsx b/client/src/main.jsx index 0daebe8f..80d573fd 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,9 +1,13 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import { Provider } from "react-redux"; import App from "./App.jsx"; +import store from "./store"; createRoot(document.getElementById("root")).render( - + + + ); diff --git a/client/src/pages/detail.page.jsx b/client/src/pages/detail.page.jsx index 4d3e97b2..d99302ae 100644 --- a/client/src/pages/detail.page.jsx +++ b/client/src/pages/detail.page.jsx @@ -1,64 +1,43 @@ import { useState, useEffect } from "react"; import { useParams, useNavigate } from "react-router"; +import { useSelector, useDispatch } from "react-redux"; +import { toggleFavorite } from "../store/favoriteSlice"; import axios from "axios"; import Navbar from "../components/Navbar"; export default function DetailPage() { const { id } = useParams(); const navigate = useNavigate(); + const dispatch = useDispatch(); + const { favorites, loading: favoriteLoading } = useSelector( + (state) => state.favorites + ); const [device, setDevice] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [isFavorite, setIsFavorite] = useState(false); const [isAuthenticated, setIsAuthenticated] = useState(false); + const isFavorite = favorites.some((fav) => fav.id === Number(id)); useEffect(() => { const fetchDeviceDetail = async () => { try { setLoading(true); - - // Cek apakah user terotentikasi const token = localStorage.getItem("access_token"); if (token) { setIsAuthenticated(true); - - // Ambil data device const response = await axios.get( `http://localhost:3000/public/devices/${id}`, { - headers: { - access_token: token, - }, + headers: { access_token: token }, } ); setDevice(response.data); - - // Cek apakah device sudah di favorit - try { - const favoriteResponse = await axios.get( - "http://localhost:3000/favorites", - { - headers: { - access_token: token, - }, - } - ); - - const isInFavorites = favoriteResponse.data.some( - (favorite) => favorite.id === Number(id) - ); - setIsFavorite(isInFavorites); - } catch (error) { - console.error("Error checking favorites:", error); - } } else { - // Jika tidak terotentikasi, ambil data publik const response = await axios.get( `http://localhost:3000/public/devices/${id}` ); setDevice(response.data); } - setLoading(false); } catch (err) { setError("Gagal mengambil data detail HP"); @@ -74,43 +53,14 @@ export default function DetailPage() { navigate(-1); }; - const handleToggleFavorite = async () => { + const handleToggleFavorite = () => { if (!isAuthenticated) { navigate("/login"); return; } - - try { - const token = localStorage.getItem("access_token"); - - if (isFavorite) { - // Hapus dari favorit - await axios.delete(`http://localhost:3000/favorites/${id}`, { - headers: { - access_token: token, - }, - }); - setIsFavorite(false); - } else { - // Tambahkan ke favorit - await axios.post( - `http://localhost:3000/favorites/${id}`, - {}, - { - headers: { - access_token: token, - }, - } - ); - setIsFavorite(true); - } - } catch (error) { - console.error("Error toggling favorite:", error); - setError("Gagal memperbarui status favorit"); - } + dispatch(toggleFavorite(id)); }; - // Format price as Indonesian Rupiah const formatPrice = (price) => { return new Intl.NumberFormat("id-ID", { style: "currency", @@ -266,12 +216,9 @@ export default function DetailPage() { }} /> - {/* Navbar */} - {/* Content */}
- {/* Menampilkan error jika ada */} {error && (
@@ -279,8 +226,7 @@ export default function DetailPage() {
)} - {/* Loading spinner */} - {loading ? ( + {loading || favoriteLoading ? (
Loading... @@ -289,7 +235,6 @@ export default function DetailPage() { ) : ( device && (
- {/* Image and basic info */}
- {/* Specifications */}

{device.device_name}

{formatPrice(device.price)}

diff --git a/client/src/pages/favorite.page.jsx b/client/src/pages/favorite.page.jsx index 8e841f96..ca430b41 100644 --- a/client/src/pages/favorite.page.jsx +++ b/client/src/pages/favorite.page.jsx @@ -1,6 +1,7 @@ -import { useState, useEffect } from "react"; -import axios from "axios"; +import { useEffect } from "react"; import { useNavigate } from "react-router"; +import { useSelector, useDispatch } from "react-redux"; +import { fetchFavorites, clearError } from "../store/favoriteSlice"; import Navbar from "../components/Navbar"; import Card from "../components/card"; import LoadingSpinner from "../components/LoadingSpinner"; @@ -8,46 +9,28 @@ import ErrorMessage from "../components/ErrorMessage"; import HomeStyles from "../components/HomeStyles"; export default function FavoritePage() { - const [favorites, setFavorites] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const { favorites, loading, error } = useSelector((state) => state.favorites); + const dispatch = useDispatch(); const navigate = useNavigate(); useEffect(() => { - // Cek apakah user sudah login const token = localStorage.getItem("access_token"); if (!token) { navigate("/login"); return; } + dispatch(fetchFavorites()); + }, [dispatch, navigate]); - // Ambil daftar HP favorit user - const fetchFavorites = async () => { - try { - setLoading(true); - const response = await axios.get("http://localhost:3000/favorites", { - headers: { - access_token: token, - }, - }); - - // Pastikan response.data adalah array - const favoriteDevices = Array.isArray(response.data) - ? response.data - : []; - setFavorites(favoriteDevices); - setLoading(false); - } catch (err) { - setError(err.response?.data?.message || "Gagal mengambil data favorit"); - setLoading(false); - console.error("Error fetching favorites:", err); - } - }; - - fetchFavorites(); - }, [navigate]); + useEffect(() => { + if (error) { + const timer = setTimeout(() => { + dispatch(clearError()); + }, 5000); + return () => clearTimeout(timer); + } + }, [error, dispatch]); - // Handler untuk klik tombol View Details const handleCardClick = (phoneId) => { if (phoneId) { navigate(`/devices/${phoneId}`); @@ -58,13 +41,9 @@ export default function FavoritePage() { <> -

Daftar HP Favorit

- - {/* Menampilkan error jika ada */} {error && } - {loading ? ( ) : favorites.length === 0 ? ( diff --git a/client/src/store/favoriteSlice.js b/client/src/store/favoriteSlice.js new file mode 100644 index 00000000..6dc00e90 --- /dev/null +++ b/client/src/store/favoriteSlice.js @@ -0,0 +1,118 @@ +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; +import axios from "axios"; + +export const fetchFavorites = createAsyncThunk( + "favorites/fetchFavorites", + async (_, { rejectWithValue }) => { + try { + const token = localStorage.getItem("access_token"); + const response = await axios.get("http://localhost:3000/favorites", { + headers: { access_token: token }, + }); + return Array.isArray(response.data) ? response.data : []; + } catch (error) { + return rejectWithValue( + error.response?.data?.message || "Gagal mengambil data favorit" + ); + } + } +); + +export const toggleFavorite = createAsyncThunk( + "favorites/toggleFavorite", + async (id, { rejectWithValue }) => { + try { + const token = localStorage.getItem("access_token"); + const favoriteResponse = await axios.get( + "http://localhost:3000/favorites", + { + headers: { access_token: token }, + } + ); + const isFavorite = favoriteResponse.data.some( + (fav) => fav.id === Number(id) + ); + + if (isFavorite) { + // Kirim permintaan DELETE ke API untuk menghapus favorit + await axios.delete(`http://localhost:3000/favorites/${id}`, { + headers: { access_token: token }, + }); + // Kembalikan ID yang dihapus untuk memperbarui state + return { id: Number(id), action: "remove" }; + } else { + // Tambahkan ke favorit + await axios.post( + `http://localhost:3000/favorites/${id}`, + {}, + { + headers: { access_token: token }, + } + ); + const response = await axios.get( + `http://localhost:3000/public/devices/${id}`, + { + headers: { access_token: token }, + } + ); + return { id: Number(id), action: "add", device: response.data }; + } + } catch (error) { + return rejectWithValue( + error.response?.data?.message || "Gagal memperbarui status favorit" + ); + } + } +); + +const favoriteSlice = createSlice({ + name: "favorites", + initialState: { + favorites: [], + loading: false, + error: null, + }, + reducers: { + clearError: (state) => { + state.error = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(fetchFavorites.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(fetchFavorites.fulfilled, (state, action) => { + state.loading = false; + state.favorites = action.payload; + }) + .addCase(fetchFavorites.rejected, (state, action) => { + state.loading = false; + state.error = action.payload; + }) + .addCase(toggleFavorite.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(toggleFavorite.fulfilled, (state, action) => { + state.loading = false; + if (action.payload.action === "remove") { + // Hapus item dari state berdasarkan ID + state.favorites = state.favorites.filter( + (fav) => fav.id !== action.payload.id + ); + } else { + // Tambahkan item baru ke state + state.favorites.push(action.payload.device); + } + }) + .addCase(toggleFavorite.rejected, (state, action) => { + state.loading = false; + state.error = action.payload; + }); + }, +}); + +export const { clearError } = favoriteSlice.actions; +export default favoriteSlice.reducer; diff --git a/client/src/store/index.js b/client/src/store/index.js new file mode 100644 index 00000000..c285281c --- /dev/null +++ b/client/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from "@reduxjs/toolkit"; +import favoriteReducer from "./favoriteSlice"; + +const store = configureStore({ + reducer: { + favorites: favoriteReducer, + }, +}); + +export default store; From ff7cbaffa854e2cced447abc9c0c77a96464e72b Mon Sep 17 00:00:00 2001 From: Kendy Date: Mon, 5 May 2025 22:33:22 +0700 Subject: [PATCH 08/16] fix: delete api_doc.md and move the content to README.md file --- README.md | 460 +++++++++++++++++++++++++++++++++++++++++++++- server/api_doc.md | 457 --------------------------------------------- 2 files changed, 459 insertions(+), 458 deletions(-) delete mode 100644 server/api_doc.md diff --git a/README.md b/README.md index 139c0f31..57946d22 100644 --- a/README.md +++ b/README.md @@ -1 +1,459 @@ -# IP-RMT60 \ No newline at end of file +# IP-RMT60 + +Xiaomi Devices API Documentation +Models +User + +- username : string, required +- email : string, required, unique +- password : string, required + +XiaomiDevice + +- key : string +- device_name : string +- device_image : string +- display_size : string +- display_res : string +- camera : string +- video : string +- ram : string +- chipset : string +- battery : string +- batteryType : string +- body : string +- os_type : string +- storage : string +- comment : string +- price : integer + +Favorite + +- UserId : integer, required +- XiaomiDeviceId : integer, required + +Relation - Many to Many +Perhatikan relasi antara User, Favorite, dan XiaomiDevice gunakan definisi relasi yang sesuai pada sequelize relation doc. +Endpoints +List of available endpoints: + +POST /register +POST /login +POST /login/google +GET /public/devices +GET /public/devices/:id + +Routes below need authentication: + +GET /devices +GET /devices/:id +PUT /users/update +POST /favorites/:XiaomiDeviceId +GET /favorites +DELETE /favorites/:XiaomiDeviceId + +1. POST /register + Request: + +body: + +{ +"username": "string", +"email": "string", +"password": "string" +} + +Response (201 - Created) +{ +"id": "integer", +"username": "string", +"email": "string" +} + +Response (400 - Bad Request) +{ +"message": "Username tidak boleh null" +} +OR +{ +"message": "Username tidak boleh kosong" +} +OR +{ +"message": "Email tidak boleh null" +} +OR +{ +"message": "Email tidak boleh kosong" +} +OR +{ +"message": "Email must be unique" +} +OR +{ +"message": "Password tidak boleh null" +} +OR +{ +"message": "Password tidak boleh kosong" +} + +2. POST /login + Request: + +body: + +{ +"email": "string", +"password": "string" +} + +Response (200 - OK) +{ +"access_token": "string" +} + +Response (400 - Bad Request) +{ +"message": "Email tidak boleh null" +} +OR +{ +"message": "Password tidak boleh null" +} + +Response (401 - Unauthorized) +{ +"message": "Invalid email/password" +} + +3. POST /login/google + Request: + +body: + +{ +"token": "string" +} + +Response (200 - OK) +{ +"access_token": "string" +} + +Response (400 - Bad Request) +{ +"message": "Token tidak valid" +} + +4. GET /public/devices + Description: + +Mendapatkan semua perangkat Xiaomi dari database tanpa autentikasi + +Response (200 - OK) +[ +{ +"id": 1, +"device_name": "Xiaomi 14", +"device_image": "https://example.com/xiaomi14.jpg", +"display_size": "6.36 inches", +"display_res": "1200 x 2670 pixels", +"camera": "50 MP", +"video": "8K@24fps", +"ram": "8 GB", +"chipset": "Snapdragon 8 Gen 3", +"battery": "4610 mAh", +"batteryType": "Li-Po", +"body": "152.8 x 71.5 x 8.2 mm", +"os_type": "Android 14", +"storage": "256 GB", +"comment": "Flagship device", +"price": 9999999 +}, +... +] + +5. GET /public/devices/:id + Description: + +Mendapatkan detail perangkat Xiaomi berdasarkan ID tanpa autentikasi + +Request: + +params: + +{ +"id": "integer" +} + +Response (200 - OK) +{ +"id": 1, +"device_name": "Xiaomi 14", +"device_image": "https://example.com/xiaomi14.jpg", +"display_size": "6.36 inches", +"display_res": "1200 x 2670 pixels", +"camera": "50 MP", +"video": "8K@24fps", +"ram": "8 GB", +"chipset": "Snapdragon 8 Gen 3", +"battery": "4610 mAh", +"batteryType": "Li-Po", +"body": "152.8 x 71.5 x 8.2 mm", +"os_type": "Android 14", +"storage": "256 GB", +"comment": "Flagship device", +"price": 9999999 +} + +Response (404 - Not Found) +{ +"message": "Device not found" +} + +6. GET /devices + Description: + +Mendapatkan semua perangkat Xiaomi dari database + +Request: + +headers: + +{ +"access_token": "string" +} + +Response (200 - OK) +[ +{ +"id": 1, +"device_name": "Xiaomi 14", +"device_image": "https://example.com/xiaomi14.jpg", +"display_size": "6.36 inches", +"display_res": "1200 x 2670 pixels", +"camera": "50 MP", +"video": "8K@24fps", +"ram": "8 GB", +"chipset": "Snapdragon 8 Gen 3", +"battery": "4610 mAh", +"batteryType": "Li-Po", +"body": "152.8 x 71.5 x 8.2 mm", +"os_type": "Android 14", +"storage": "256 GB", +"comment": "Flagship device", +"price": 9999999 +}, +... +] + +7. GET /devices/:id + Description: + +Mendapatkan detail perangkat Xiaomi berdasarkan ID + +Request: + +headers: + +{ +"access_token": "string" +} + +params: + +{ +:id": "integer" +} + +Response (200 - OK) +{ +"id": 1, +"device_name": "Xiaomi 14", +"device_image": "https://example.com/xiaomi14.jpg", +"display_size": "6.36 inches", +"display_res": "1200 x 2670 pixels", +"camera": "50 MP", +"video": "8K@24fps", +"ram": "8 GB", +"chipset": "Snapdragon 8 Gen 3", +"battery": "4610 mAh", +"batteryType": "Li-Po", +"body": "152.8 x 71.5 x 8.2 mm", +"os_type": "Android 14", +"storage": "256 GB", +"comment": "Flagship device", +"price": 9999999 +} + +Response (404 - Not Found) +{ +"message": "Device not found" +} + +8. PUT /users/update + Description: + +Memperbarui informasi pengguna + +Request: + +headers: + +{ +"access_token": "string" +} + +body: + +{ +"username": "string", +"email": "string" +} + +Response (200 - OK) +{ +"id": "integer", +"username": "string", +"email": "string" +} + +Response (400 - Bad Request) +{ +"message": "Username tidak boleh null" +} +OR +{ +"message": "Email tidak boleh null" +} +OR +{ +"message": "Email must be unique" +} + +9. POST /favorites/:XiaomiDeviceId + Description: + +Menambahkan perangkat Xiaomi ke favorit pengguna + +Request: + +headers: + +{ +"access_token": "string" +} + +params: + +{ +"XiaomiDeviceId": "integer" +} + +Response (201 - Created) +{ +"id": "integer", +"UserId": "integer", +"XiaomiDeviceId": "integer" +} + +Response (404 - Not Found) +{ +"message": "Device not found" +} + +10. GET /favorites + Description: + +Mendapatkan daftar perangkat favorit pengguna + +Request: + +headers: + +{ +"access_token": "string" +} + +Response (200 - OK) +[ +{ +"id": 1, +"UserId": 1, +"XiaomiDeviceId": 1, +"XiaomiDevice": { +"id": 1, +"device_name": "Xiaomi 14", +"device_image": "https://example.com/xiaomi14.jpg", +"display_size": "6.36 inches", +"display_res": "1200 x 2670 pixels", +"camera": "50 MP", +"video": "8K@24fps", +"ram": "8 GB", +"chipset": "Snapdragon 8 Gen 3", +"battery": "4610 mAh", +"batteryType": "Li-Po", +"body": "152.8 x 71.5 x 8.2 mm", +"os_type": "Android 14", +"storage": "256 GB", +"comment": "Flagship device", +"price": 9999999 +} +}, +... +] + +11. DELETE /favorites/:XiaomiDeviceId + Description: + +Menghapus perangkat Xiaomi dari favorit pengguna + +Request: + +headers: + +{ +"access_token": "string" +} + +params: + +{ +"XiaomiDeviceId": "integer" +} + +Response (200 - OK) +{ +"message": "Device removed from favorites" +} + +Response (404 - Not Found) +{ +"message": "Favorite not found" +} + +Global Error +Response (401 - Unauthorized) +{ +"message": "Invalid token" +} + +Response (404 - Not Found) +{ +"message": "User not found" +} +OR +{ +"message": "Device not found" +} +OR +{ +"message": "Favorite not found" +} + +Response (500 - Internal Server Error) +{ +"message": "Internal server error" +} diff --git a/server/api_doc.md b/server/api_doc.md deleted file mode 100644 index d49c4d54..00000000 --- a/server/api_doc.md +++ /dev/null @@ -1,457 +0,0 @@ -Xiaomi Devices API Documentation -Models -User - -- username : string, required -- email : string, required, unique -- password : string, required - -XiaomiDevice - -- key : string -- device_name : string -- device_image : string -- display_size : string -- display_res : string -- camera : string -- video : string -- ram : string -- chipset : string -- battery : string -- batteryType : string -- body : string -- os_type : string -- storage : string -- comment : string -- price : integer - -Favorite - -- UserId : integer, required -- XiaomiDeviceId : integer, required - -Relation - Many to Many -Perhatikan relasi antara User, Favorite, dan XiaomiDevice gunakan definisi relasi yang sesuai pada sequelize relation doc. -Endpoints -List of available endpoints: - -POST /register -POST /login -POST /login/google -GET /public/devices -GET /public/devices/:id - -Routes below need authentication: - -GET /devices -GET /devices/:id -PUT /users/update -POST /favorites/:XiaomiDeviceId -GET /favorites -DELETE /favorites/:XiaomiDeviceId - -1. POST /register - Request: - -body: - -{ -"username": "string", -"email": "string", -"password": "string" -} - -Response (201 - Created) -{ -"id": "integer", -"username": "string", -"email": "string" -} - -Response (400 - Bad Request) -{ -"message": "Username tidak boleh null" -} -OR -{ -"message": "Username tidak boleh kosong" -} -OR -{ -"message": "Email tidak boleh null" -} -OR -{ -"message": "Email tidak boleh kosong" -} -OR -{ -"message": "Email must be unique" -} -OR -{ -"message": "Password tidak boleh null" -} -OR -{ -"message": "Password tidak boleh kosong" -} - -2. POST /login - Request: - -body: - -{ -"email": "string", -"password": "string" -} - -Response (200 - OK) -{ -"access_token": "string" -} - -Response (400 - Bad Request) -{ -"message": "Email tidak boleh null" -} -OR -{ -"message": "Password tidak boleh null" -} - -Response (401 - Unauthorized) -{ -"message": "Invalid email/password" -} - -3. POST /login/google - Request: - -body: - -{ -"token": "string" -} - -Response (200 - OK) -{ -"access_token": "string" -} - -Response (400 - Bad Request) -{ -"message": "Token tidak valid" -} - -4. GET /public/devices - Description: - -Mendapatkan semua perangkat Xiaomi dari database tanpa autentikasi - -Response (200 - OK) -[ -{ -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -}, -... -] - -5. GET /public/devices/:id - Description: - -Mendapatkan detail perangkat Xiaomi berdasarkan ID tanpa autentikasi - -Request: - -params: - -{ -"id": "integer" -} - -Response (200 - OK) -{ -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -} - -Response (404 - Not Found) -{ -"message": "Device not found" -} - -6. GET /devices - Description: - -Mendapatkan semua perangkat Xiaomi dari database - -Request: - -headers: - -{ -"access_token": "string" -} - -Response (200 - OK) -[ -{ -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -}, -... -] - -7. GET /devices/:id - Description: - -Mendapatkan detail perangkat Xiaomi berdasarkan ID - -Request: - -headers: - -{ -"access_token": "string" -} - -params: - -{ -:id": "integer" -} - -Response (200 - OK) -{ -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -} - -Response (404 - Not Found) -{ -"message": "Device not found" -} - -8. PUT /users/update - Description: - -Memperbarui informasi pengguna - -Request: - -headers: - -{ -"access_token": "string" -} - -body: - -{ -"username": "string", -"email": "string" -} - -Response (200 - OK) -{ -"id": "integer", -"username": "string", -"email": "string" -} - -Response (400 - Bad Request) -{ -"message": "Username tidak boleh null" -} -OR -{ -"message": "Email tidak boleh null" -} -OR -{ -"message": "Email must be unique" -} - -9. POST /favorites/:XiaomiDeviceId - Description: - -Menambahkan perangkat Xiaomi ke favorit pengguna - -Request: - -headers: - -{ -"access_token": "string" -} - -params: - -{ -"XiaomiDeviceId": "integer" -} - -Response (201 - Created) -{ -"id": "integer", -"UserId": "integer", -"XiaomiDeviceId": "integer" -} - -Response (404 - Not Found) -{ -"message": "Device not found" -} - -10. GET /favorites - Description: - -Mendapatkan daftar perangkat favorit pengguna - -Request: - -headers: - -{ -"access_token": "string" -} - -Response (200 - OK) -[ -{ -"id": 1, -"UserId": 1, -"XiaomiDeviceId": 1, -"XiaomiDevice": { -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -} -}, -... -] - -11. DELETE /favorites/:XiaomiDeviceId - Description: - -Menghapus perangkat Xiaomi dari favorit pengguna - -Request: - -headers: - -{ -"access_token": "string" -} - -params: - -{ -"XiaomiDeviceId": "integer" -} - -Response (200 - OK) -{ -"message": "Device removed from favorites" -} - -Response (404 - Not Found) -{ -"message": "Favorite not found" -} - -Global Error -Response (401 - Unauthorized) -{ -"message": "Invalid token" -} - -Response (404 - Not Found) -{ -"message": "User not found" -} -OR -{ -"message": "Device not found" -} -OR -{ -"message": "Favorite not found" -} - -Response (500 - Internal Server Error) -{ -"message": "Internal server error" -} From 0ee02697435b29d47b636d9f906658ce075a09d4 Mon Sep 17 00:00:00 2001 From: Kendy Date: Mon, 5 May 2025 22:56:44 +0700 Subject: [PATCH 09/16] fix: text in file README.md --- README.md | 791 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 505 insertions(+), 286 deletions(-) diff --git a/README.md b/README.md index 57946d22..9c9ea022 100644 --- a/README.md +++ b/README.md @@ -1,459 +1,678 @@ -# IP-RMT60 +# IP-RMT60: Xiaomi Devices API Documentation -Xiaomi Devices API Documentation -Models -User +This document provides comprehensive details for the server-side API of the Xiaomi Devices platform, built with Express.js, Sequelize, and PostgreSQL. The API facilitates user authentication, device exploration, and management of favorite devices. -- username : string, required -- email : string, required, unique -- password : string, required +--- -XiaomiDevice +## Table of Contents -- key : string -- device_name : string -- device_image : string -- display_size : string -- display_res : string -- camera : string -- video : string -- ram : string -- chipset : string -- battery : string -- batteryType : string -- body : string -- os_type : string -- storage : string -- comment : string -- price : integer +1. [Models](#models) +2. [Relationships](#relationships) +3. [Available Endpoints](#available-endpoints) +4. [Public Endpoints](#public-endpoints) +5. [Authenticated Endpoints](#authenticated-endpoints) +6. [Global Error Handling](#global-error-handling) -Favorite +--- -- UserId : integer, required -- XiaomiDeviceId : integer, required +## Models -Relation - Many to Many -Perhatikan relasi antara User, Favorite, dan XiaomiDevice gunakan definisi relasi yang sesuai pada sequelize relation doc. -Endpoints -List of available endpoints: +### User -POST /register -POST /login -POST /login/google -GET /public/devices -GET /public/devices/:id +Represents a registered user of the platform. -Routes below need authentication: +| Field | Type | Constraints | +| -------- | ------ | ---------------- | +| username | String | Required | +| email | String | Required, Unique | +| password | String | Required | -GET /devices -GET /devices/:id -PUT /users/update -POST /favorites/:XiaomiDeviceId -GET /favorites -DELETE /favorites/:XiaomiDeviceId +### XiaomiDevice -1. POST /register - Request: +Represents a Xiaomi device available on the platform. -body: +| Field | Type | Constraints | +| ------------ | ------- | ----------- | +| key | String | Optional | +| device_name | String | Optional | +| device_image | String | Optional | +| display_size | String | Optional | +| display_res | String | Optional | +| camera | String | Optional | +| video | String | Optional | +| ram | String | Optional | +| chipset | String | Optional | +| battery | String | Optional | +| batteryType | String | Optional | +| body | String | Optional | +| os_type | String | Optional | +| storage | String | Optional | +| comment | String | Optional | +| price | Integer | Optional | +### Favorite + +Represents a user’s favorite Xiaomi device. + +| Field | Type | Constraints | +| -------------- | ------- | ----------- | +| UserId | Integer | Required | +| XiaomiDeviceId | Integer | Required | + +--- + +## Relationships + +- **Many-to-Many**: The `User` and `XiaomiDevice` models are connected through the `Favorite` model using Sequelize's many-to-many relationship. + +--- + +## Available Endpoints + +### Public Endpoints + +- `POST /register` - Create a new user account +- `POST /login` - Authenticate a user and return an access token +- `POST /login/google` - Authenticate a user via Google OAuth +- `GET /public/devices` - Retrieve all Xiaomi devices +- `GET /public/devices/:id` - Retrieve a specific Xiaomi device by ID + +### Authenticated Endpoints + +- `GET /devices` - Retrieve all Xiaomi devices (authenticated) +- `GET /devices/:id` - Retrieve a specific Xiaomi device by ID (authenticated) +- `PUT /users/update` - Update user information +- `POST /favorites/:XiaomiDeviceId` - Add a Xiaomi device to user’s favorites +- `GET /favorites` - Retrieve user’s favorite devices +- `DELETE /favorites/:XiaomiDeviceId` - Remove a Xiaomi device from user’s favorites + +--- + +## Public Endpoints + +### 1. POST /register + +**Description**: Creates a new user account. + +**Body**: + +```json { -"username": "string", -"email": "string", -"password": "string" + "username": "string", + "email": "string", + "password": "string" } +``` + +**Response (201 - Created)**: -Response (201 - Created) +```json { -"id": "integer", -"username": "string", -"email": "string" + "id": "integer", + "username": "string", + "email": "string" } +``` -Response (400 - Bad Request) +**Response (400 - Bad Request)**: + +```json { -"message": "Username tidak boleh null" + "message": "Username tidak boleh null enkah kosong" } -OR +``` + +_Or similar validation errors for username, email, or password._ + +**Response (400 - Bad Request)**: + +```json { -"message": "Username tidak boleh kosong" + "message": "Email must be unique" } -OR +``` + +--- + +### 2. POST /login + +**Description**: Authenticates a user and returns an access token. + +**Body**: + +```json { -"message": "Email tidak boleh null" + "email": "string", + "password": "string" } -OR +``` + +**Response (200 - OK)**: + +```json { -"message": "Email tidak boleh kosong" + "access_token": "string" } -OR +``` + +**Response (400 - Bad Request)**: + +```json { -"message": "Email must be unique" + "message": "Email tidak boleh null" } -OR +``` + +_Or_: + +```json { -"message": "Password tidak boleh null" + "message": "Password tidak boleh null" } -OR +``` + +**Response (401 - Unauthorized)**: + +```json { -"message": "Password tidak boleh kosong" + "message": "Invalid email/password" } +``` -2. POST /login - Request: +--- -body: +### 3. POST /login/google -{ -"email": "string", -"password": "string" -} +**Description**: Authenticates a user via Google OAuth and returns an access token. -Response (200 - OK) -{ -"access_token": "string" -} +**Body**: -Response (400 - Bad Request) +```json { -"message": "Email tidak boleh null" -} -OR -{ -"message": "Password tidak boleh null" + "token": "string" } +``` + +**Response (200 - OK)**: -Response (401 - Unauthorized) +```json { -"message": "Invalid email/password" + "access_token": "string" } +``` -3. POST /login/google - Request: - -body: +**Response (400 - Bad Request)**: +```json { -"token": "string" + "message": "Token tidak valid" } +``` -Response (200 - OK) -{ -"access_token": "string" -} +**Response (500 - Internal Server Error)**: -Response (400 - Bad Request) +```json { -"message": "Token tidak valid" + "message": "Internal server error" } +``` + +--- -4. GET /public/devices - Description: +### 4. GET /public/devices -Mendapatkan semua perangkat Xiaomi dari database tanpa autentikasi +**Description**: Retrieves all Xiaomi devices available on the platform. -Response (200 - OK) +**Response (200 - OK)**: + +```json [ -{ -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -}, -... + { + "id": 1, + "device_name": "Xiaomi 14", + "device_image": "https://example.com/xiaomi14.jpg", + "display_size": "6.36 inches", + "display_res": "1200 x 2670 pixels", + "camera": "50 MP", + "video": "8K@24fps", + "ram": "8 GB", + "chip + +set": "Snapdragon 8 Gen 3", + "battery": "4610 mAh", + "batteryType": "Li-Po", + "body": "152.8 x 71.5 x 8.2 mm", + "os_type": "Android 14", + "storage": "256 GB", + "comment": "Flagship device", + "price": 9999999 + }, + ... ] +``` -5. GET /public/devices/:id - Description: +**Response (500 - Internal Server Error)**: -Mendapatkan detail perangkat Xiaomi berdasarkan ID tanpa autentikasi +```json +{ + "message": "Internal server error" +} +``` + +--- + +### 5. GET /public/devices/:id -Request: +**Description**: Retrieves details of a specific Xiaomi device by its ID. -params: +**Parameters**: +- `id` (required): Device ID (integer) + +**Response (200 - OK)**: + +```json { -"id": "integer" + "id": 1, + "device_name": "Xiaomi 14", + "device_image": "https://example.com/xiaomi14.jpg", + "display_size": "6.36 inches", + "display_res": "1200 x 2670 pixels", + "camera": "50 MP", + "video": "8K@24fps", + "ram": "8 GB", + "chipset": "Snapdragon 8 Gen 3", + "battery": "4610 mAh", + "batteryType": "Li-Po", + "body": "152.8 x 71.5 x 8.2 mm", + "os_type": "Android 14", + "storage": "256 GB", + "comment": "Flagship device", + "price": 9999999 } +``` + +**Response (404 - Not Found)**: -Response (200 - OK) +```json { -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 + "message": "Device not found" } +``` -Response (404 - Not Found) +**Response (500 - Internal Server Error)**: + +```json { -"message": "Device not found" + "message": "Internal server error" } +``` -6. GET /devices - Description: +--- -Mendapatkan semua perangkat Xiaomi dari database +## Authenticated Endpoints -Request: +All endpoints below require an `Authorization` header with a Bearer token obtained from the `/login` or `/login/google` endpoint. -headers: +**Headers**: +```json { -"access_token": "string" + "Authorization": "Bearer " } +``` + +### 6. GET /devices + +**Description**: Retrieves all Xiaomi devices for authenticated users. + +**Response (200 - OK)**: -Response (200 - OK) +```json [ -{ -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -}, -... + { + "id": 1, + "device_name": "Xiaomi 14", + "device_image": "https://example.com/xiaomi14.jpg", + "display_size": "6.36 inches", + "display_res": "1200 x 2670 pixels", + "camera": "50 MP", + "video": "8K@24fps", + "ram": "8 GB", + "chipset": "Snapdragon 8 Gen 3", + "battery": "4610 mAh", + "batteryType": "Li-Po", + "body": "152.8 x 71.5 x 8.2 mm", + "os_type": "Android 14", + "storage": "256 GB", + "comment": "Flagship device", + "price": 9999999 + }, + ... ] +``` + +**Response (401 - Unauthorized)**: + +```json +{ + "message": "Invalid token" +} +``` + +**Response (500 - Internal Server Error)**: + +```json +{ + "message": "Internal server error" +} +``` + +--- -7. GET /devices/:id - Description: +### 7. GET /devices/:id -Mendapatkan detail perangkat Xiaomi berdasarkan ID +**Description**: Retrieves details of a specific Xiaomi device by its ID for authenticated users. -Request: +**Parameters**: -headers: +- `id` (required): Device ID (integer) +**Response (200 - OK)**: + +```json { -"access_token": "string" + "id": 1, + "device_name": "Xiaomi 14", + "device_image": "https://example.com/xiaomi14.jpg", + "display_size": "6.36 inches", + "display_res": "1200 x 2670 pixels", + "camera": "50 MP", + "video": "8K@24fps", + "ram": "8 GB", + "chipset": "Snapdragon 8 Gen 3", + "battery": "4610 mAh", + "batteryType": "Li-Po", + "body": "152.8 x 71.5 x 8.2 mm", + "os_type": "Android 14", + "storage": "256 GB", + "comment": "Flagship device", + "price": 9999999 } +``` -params: +**Response (401 - Unauthorized)**: +```json { -:id": "integer" + "message": "Invalid token" } +``` + +**Response (404 - Not Found)**: -Response (200 - OK) +```json { -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 + "message": "Device not found" } +``` -Response (404 - Not Found) +**Response (500 - Internal Server Error)**: + +```json { -"message": "Device not found" + "message": "Internal server error" } +``` -8. PUT /users/update - Description: +--- -Memperbarui informasi pengguna +### 8. PUT /users/update -Request: +**Description**: Updates the authenticated user’s information. -headers: +**Body**: +```json { -"access_token": "string" + "username": "string", + "email": "string" } +``` -body: +**Response (200 - OK)**: +```json { -"username": "string", -"email": "string" + "id": "integer", + "username": "string", + "email": "string" } +``` + +**Response (400 - Bad Request)**: -Response (200 - OK) +```json { -"id": "integer", -"username": "string", -"email": "string" + "message": "Username tidak boleh null" } +``` -Response (400 - Bad Request) +_Or_: + +```json { -"message": "Username tidak boleh null" + "message": "Email tidak boleh null" } -OR +``` + +_Or_: + +```json { -"message": "Email tidak boleh null" + "message": "Email must be unique" } -OR +``` + +**Response (401 - Unauthorized)**: + +```json +{ + "message": "Invalid token" +} +``` + +**Response (500 - Internal Server Error)**: + +```json { -"message": "Email must be unique" + "message": "Internal server error" } +``` -9. POST /favorites/:XiaomiDeviceId - Description: +--- -Menambahkan perangkat Xiaomi ke favorit pengguna +### 9. POST /favorites/:XiaomiDeviceId -Request: +**Description**: Adds a Xiaomi device to the authenticated user’s favorites. -headers: +**Parameters**: +- `XiaomiDeviceId` (required): Device ID (integer) + +**Response (201 - Created)**: + +```json { -"access_token": "string" + "id": "integer", + "UserId": "integer", + "XiaomiDeviceId": "integer" } +``` -params: +**Response (401 - Unauthorized)**: +```json { -"XiaomiDeviceId": "integer" + "message": "Invalid token" } +``` -Response (201 - Created) +**Response (404 - Not Found)**: + +```json { -"id": "integer", -"UserId": "integer", -"XiaomiDeviceId": "integer" + "message": "Device not found" } +``` + +**Response (500 - Internal Server Error)**: -Response (404 - Not Found) +```json { -"message": "Device not found" + "message": "Internal server error" } +``` -10. GET /favorites - Description: +--- -Mendapatkan daftar perangkat favorit pengguna +### 10. GET /favorites -Request: +**Description**: Retrieves the authenticated user’s favorite devices. + +**Response (200 - OK)**: + +```json +[ + { + "id": 1, + "UserId": 1, + "XiaomiDeviceId": 1, + "XiaomiDevice": { + "id": 1, + "device_name": "Xiaomi 14", + "device_image": "https://example.com/xiaomi14.jpg", + "display_size": "6.36 inches", + "display_res": "1200 x 2670 pixels", + "camera": "50 MP", + "video": "8K@24fps", + "ram": "8 GB", + "chipset": "Snapdragon 8 Gen 3", + "battery": "4610 mAh", + "batteryType": "Li-Po", + "body": "152.8 x 71.5 x 8.2 mm", + "os_type": "Android 14", + "storage": "256 GB", + "comment": "Flagship device", + "price": 9999999 + } + }, + ... +] +``` -headers: +**Response (401 - Unauthorized)**: +```json { -"access_token": "string" + "message": "Invalid token" } +``` -Response (200 - OK) -[ +**Response (500 - Internal Server Error)**: + +```json { -"id": 1, -"UserId": 1, -"XiaomiDeviceId": 1, -"XiaomiDevice": { -"id": 1, -"device_name": "Xiaomi 14", -"device_image": "https://example.com/xiaomi14.jpg", -"display_size": "6.36 inches", -"display_res": "1200 x 2670 pixels", -"camera": "50 MP", -"video": "8K@24fps", -"ram": "8 GB", -"chipset": "Snapdragon 8 Gen 3", -"battery": "4610 mAh", -"batteryType": "Li-Po", -"body": "152.8 x 71.5 x 8.2 mm", -"os_type": "Android 14", -"storage": "256 GB", -"comment": "Flagship device", -"price": 9999999 -} -}, -... -] + "message": "Internal server error" +} +``` + +--- + +### 11. DELETE /favorites/:XiaomiDeviceId -11. DELETE /favorites/:XiaomiDeviceId - Description: +**Description**: Removes a Xiaomi device from the authenticated user’s favorites. -Menghapus perangkat Xiaomi dari favorit pengguna +**Parameters**: -Request: +- `XiaomiDeviceId` (required): Device ID (integer) -headers: +**Response (200 - OK)**: +```json { -"access_token": "string" + "message": "Device removed from favorites" } +``` -params: +**Response (401 - Unauthorized)**: +```json { -"XiaomiDeviceId": "integer" + "message": "Invalid token" } +``` -Response (200 - OK) +**Response (404 - Not Found)**: + +```json { -"message": "Device removed from favorites" + "message": "Favorite not found" } +``` + +**Response (500 - Internal Server Error)**: -Response (404 - Not Found) +```json { -"message": "Favorite not found" + "message": "Internal server error" } +``` + +--- + +## Global Error Handling -Global Error -Response (401 - Unauthorized) +**Response (401 - Unauthorized)**: + +```json { -"message": "Invalid token" + "message": "Invalid token" } +``` + +**Response (404 - Not Found)**: -Response (404 - Not Found) +```json { -"message": "User not found" + "message": "User not found" } -OR +``` + +_Or_: + +```json { -"message": "Device not found" + "message": "Device not found" } -OR +``` + +_Or_: + +```json { -"message": "Favorite not found" + "message": "Favorite not found" } +``` -Response (500 - Internal Server Error) +**Response (500 - Internal Server Error)**: + +```json { -"message": "Internal server error" + "message": "Internal server error" } +``` + +--- + +This API documentation provides clear guidance for developers interacting with the Xiaomi Devices platform. For further details, refer to the source code or contact the development team. From a7c5009755ea04f37564fa400ae324378b9d3d14 Mon Sep 17 00:00:00 2001 From: Kendy Date: Tue, 6 May 2025 16:03:24 +0700 Subject: [PATCH 10/16] chore : embedding gemini AI to landing page and the result show at home page --- client/src/components/FeaturedDevices.jsx | 20 ++++++- client/src/components/WelcomeContent.jsx | 23 ++++++-- client/src/pages/home.page.jsx | 26 +++++---- client/src/pages/landing.page.jsx | 50 +++++++++++++++- server/app.js | 69 +++++++++++++++++++++-- server/package-lock.json | 10 ++++ server/package.json | 1 + 7 files changed, 177 insertions(+), 22 deletions(-) diff --git a/client/src/components/FeaturedDevices.jsx b/client/src/components/FeaturedDevices.jsx index a9592e24..b90cb11b 100644 --- a/client/src/components/FeaturedDevices.jsx +++ b/client/src/components/FeaturedDevices.jsx @@ -1,18 +1,34 @@ import Card from "./card"; import LoadingSpinner from "./LoadingSpinner"; +import { useState, useEffect } from "react"; export default function FeaturedDevices({ devices, loading, onCardClick }) { + const [aiResponse, setAiResponse] = useState(null); + + useEffect(() => { + try { + const storedResponse = localStorage.getItem("aiResponse"); + if (storedResponse) { + setAiResponse(JSON.parse(storedResponse)); + } + } catch (error) { + console.error("Error parsing AI response from localStorage:", error); + } + }, []); + return (

- Berikut beberapa pilihan HP yang sangat cocok sesuai kebutuhan kamu... + {aiResponse + ? `Rekomendasi AI: ${aiResponse}` + : "Berikut beberapa pilihan HP yang sangat cocok sesuai kebutuhan kamu..."}

{loading ? ( ) : (
{devices.map((device) => ( -
+
{ - navigate("/"); + if (prompt.trim()) { + onAiRequest(prompt); + setTimeout(() => { + navigate("/"); + }, 2000); + } }; return ( @@ -28,10 +35,18 @@ export default function WelcomeContent() { className="form-control" rows="3" placeholder="Saya ingin HP gaming dan baterai tahan lama" + value={prompt} + onChange={(e) => setPrompt(e.target.value)} + disabled={loading} >
-
diff --git a/client/src/pages/home.page.jsx b/client/src/pages/home.page.jsx index 3ec74654..352ea8c8 100644 --- a/client/src/pages/home.page.jsx +++ b/client/src/pages/home.page.jsx @@ -9,32 +9,41 @@ import HomeStyles from "../components/HomeStyles"; import WelcomeContent from "../components/WelcomeContent"; export default function HomePage() { - // State untuk menyimpan data HP const [devices, setDevices] = useState([]); const [featuredDevices, setFeaturedDevices] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const navigate = useNavigate(); - // Fungsi untuk mengambil data dari API useEffect(() => { const fetchDevices = async () => { try { setLoading(true); + setError(null); const response = await axios.get( - "http://localhost:3000/public/devices" + "http://localhost:3000/public/devices", + { + timeout: 5000, + } ); - // Simpan semua device ke state setDevices(response.data); - // Filter device unggulan const shuffledDevices = response.data.sort(() => 0.5 - Math.random()); setFeaturedDevices(shuffledDevices.slice(0, 4)); setLoading(false); } catch (err) { - setError("Gagal mengambil data dari server"); + if (err.code === "ERR_NETWORK" || err.code === "ECONNREFUSED") { + setError( + "Tidak dapat terhubung ke server. Pastikan server berjalan di http://localhost:3000." + ); + } else { + setError( + "Gagal mengambil data dari server: " + + (err.message || "Unknown error") + ); + } setLoading(false); console.error("Error fetching devices:", err); } @@ -43,9 +52,7 @@ export default function HomePage() { fetchDevices(); }, []); - // Handler untuk klik tombol View Details const handleCardClick = (phoneId) => { - // Navigasi ke halaman detail dengan parameter ID HP navigate(`/devices/${phoneId}`); }; @@ -54,17 +61,14 @@ export default function HomePage() { - {/* Menampilkan error jika ada */} {error && } - {/* HP Unggulan */} - {/* Daftar Semua HP */} { + setLoading(true); + setError(null); + try { + const response = await axios.post( + "http://localhost:3000/ai", + { prompt }, + { + timeout: 10000, + } + ); + + if (response.data && response.data.response) { + setAiResponse(response.data.response); + localStorage.setItem( + "aiResponse", + JSON.stringify(response.data.response) + ); + } else { + throw new Error("Respons AI tidak valid"); + } + } catch (err) { + if (err.code === "ERR_NETWORK" || err.code === "ECONNREFUSED") { + setError( + "Tidak dapat terhubung ke server. Pastikan server berjalan di http://localhost:3000." + ); + } else { + setError( + "Gagal mendapatkan respons dari AI: " + + (err.message || "Unknown error") + ); + } + console.error("Error fetching AI response:", err); + } finally { + setLoading(false); + } + }; + return ( <> - + ); } diff --git a/server/app.js b/server/app.js index 9cdc5e69..05904bcf 100644 --- a/server/app.js +++ b/server/app.js @@ -11,6 +11,41 @@ const UserController = require("./controllers/UserController"); const Controller = require("./controllers/Controller"); const PubController = require("./controllers/PubController"); const testingRoutes = require("./routes/testing"); +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +// Pastikan API key tersedia +if (!process.env.GOOGLE_API_KEY) { + console.error("GOOGLE_API_KEY tidak ditemukan di environment variables"); + process.exit(1); +} + +const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY); + +async function geminiApi({ prompt }) { + try { + const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + + // Modifikasi prompt untuk membatasi konteks ke HP Xiaomi saja + const enhancedPrompt = ` + Kamu adalah asisten yang hanya membahas tentang HP Xiaomi. + Berikan jawaban hanya seputar HP Xiaomi saja. + Jangan pernah menyebutkan atau merekomendasikan merk smartphone lain atau gadget lain selain Xiaomi. + Jika ditanya tentang merk lain, arahkan pembicaraan kembali ke Xiaomi. + + Fokuskan jawaban pada informasi spesifikasi, fitur, dan kelebihan HP Xiaomi sesuai dengan kebutuhan pengguna. + Berikan jawaban singkat, padat, dan informatif. + + Pertanyaan: ${prompt} + `; + + const result = await model.generateContent(enhancedPrompt); + const response = await result.response; + return response.text(); + } catch (error) { + console.error("Error in geminiApi:", error); + throw error; + } +} // Middleware app.use(cors()); @@ -24,6 +59,21 @@ app.use("/testing", testingRoutes); app.get("/public/devices", PubController.getAllDevices); app.get("/public/devices/:id", PubController.getDeviceById); +// Route untuk AI +app.post("/ai", async (req, res) => { + try { + const { prompt } = req.body; + if (!prompt) { + return res.status(400).json({ message: "Prompt diperlukan" }); + } + const response = await geminiApi({ prompt }); + res.status(200).json({ response }); + } catch (error) { + console.error("Error in AI route:", error); + res.status(500).json({ message: "Internal server error" }); + } +}); + // Route untuk user (login & register) app.post("/register", UserController.register); app.post("/login", UserController.login); @@ -42,12 +92,23 @@ app.delete("/favorites/:XiaomiDeviceId", Controller.removeFromFavorites); // Error handler app.use((err, req, res, next) => { - console.error(err); + console.error("Global error:", err); res.status(500).json({ message: "Internal server error" }); }); -app.listen(port, () => { - console.log(`Server running on port ${port}`); -}); +// Start the server with error handling +app + .listen(port, () => { + console.log(`Server successfully running on port ${port}`); + }) + .on("error", (err) => { + if (err.code === "EADDRINUSE") { + console.error( + `Port ${port} is already in use. Please use a different port or free up port ${port}.` + ); + } else { + console.error("Failed to start server:", err); + } + }); module.exports = app; diff --git a/server/package-lock.json b/server/package-lock.json index 8fd1b710..6ddf855a 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@google/genai": "^0.12.0", + "@google/generative-ai": "^0.24.1", "bcryptjs": "^3.0.2", "cors": "^2.8.5", "dotenv": "^16.5.0", @@ -571,6 +572,15 @@ "node": ">=18.0.0" } }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", diff --git a/server/package.json b/server/package.json index fe8be3b5..356e1f28 100644 --- a/server/package.json +++ b/server/package.json @@ -13,6 +13,7 @@ "description": "", "dependencies": { "@google/genai": "^0.12.0", + "@google/generative-ai": "^0.24.1", "bcryptjs": "^3.0.2", "cors": "^2.8.5", "dotenv": "^16.5.0", From 09647a05a8de1b4bfbd7c1ed30be65066dd4228f Mon Sep 17 00:00:00 2001 From: Kendy Date: Tue, 6 May 2025 17:18:51 +0700 Subject: [PATCH 11/16] fix: testing coverage --- README.md | 124 ++-- server/__test__/testing.test.js | 580 +++++++++++++++- server/coverage/clover.xml | 450 ++++++------ server/coverage/coverage-final.json | 24 +- server/coverage/lcov-report/index.html | 92 +-- .../coverage/lcov-report/server/app.js.html | 267 ++++++-- .../server/controllers/Controller.js.html | 114 ++-- .../server/controllers/PubController.js.html | 136 ++-- .../server/controllers/UserController.js.html | 154 ++--- .../lcov-report/server/controllers/index.html | 86 +-- .../lcov-report/server/helpers/bcrypt.js.html | 10 +- .../lcov-report/server/helpers/index.html | 2 +- .../lcov-report/server/helpers/jwt.js.html | 16 +- server/coverage/lcov-report/server/index.html | 34 +- .../server/middlewares/authentication.js.html | 50 +- .../lcov-report/server/middlewares/index.html | 30 +- .../server/models/favorite.js.html | 14 +- .../lcov-report/server/models/index.html | 2 +- .../lcov-report/server/models/index.js.html | 42 +- .../lcov-report/server/models/user.js.html | 14 +- .../server/models/xiaomidevice.js.html | 12 +- .../lcov-report/server/routes/index.html | 30 +- .../lcov-report/server/routes/testing.js.html | 26 +- server/coverage/lcov.info | 638 +++++++++--------- 24 files changed, 1873 insertions(+), 1074 deletions(-) diff --git a/README.md b/README.md index 9c9ea022..a8a90116 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,25 @@ -# IP-RMT60: Xiaomi Devices API Documentation +# IP-RMT60: Dokumentasi API Perangkat Xiaomi -This document provides comprehensive details for the server-side API of the Xiaomi Devices platform, built with Express.js, Sequelize, and PostgreSQL. The API facilitates user authentication, device exploration, and management of favorite devices. +Dokumen ini menyediakan detail komprehensif untuk API sisi server platform Perangkat Xiaomi, yang dibangun dengan Express.js, Sequelize, dan PostgreSQL. API ini memfasilitasi autentikasi pengguna, eksplorasi perangkat, dan pengelolaan perangkat favorit. --- -## Table of Contents +## Daftar Isi -1. [Models](#models) -2. [Relationships](#relationships) -3. [Available Endpoints](#available-endpoints) -4. [Public Endpoints](#public-endpoints) -5. [Authenticated Endpoints](#authenticated-endpoints) -6. [Global Error Handling](#global-error-handling) +1. [Model](#models) +2. [Hubungan](#relationships) +3. [Endpoint yang Tersedia](#available-endpoints) +4. [Endpoint Publik](#public-endpoints) +5. [Endpoint Terotentikasi](#authenticated-endpoints) +6. [Penanganan Error Global](#global-error-handling) --- -## Models +## Model ### User -Represents a registered user of the platform. +Merepresentasikan pengguna terdaftar platform. | Field | Type | Constraints | | -------- | ------ | ---------------- | @@ -29,7 +29,7 @@ Represents a registered user of the platform. ### XiaomiDevice -Represents a Xiaomi device available on the platform. +Merepresentasikan perangkat Xiaomi yang tersedia di platform. | Field | Type | Constraints | | ------------ | ------- | ----------- | @@ -52,7 +52,7 @@ Represents a Xiaomi device available on the platform. ### Favorite -Represents a user’s favorite Xiaomi device. +Merepresentasikan perangkat Xiaomi favorit pengguna. | Field | Type | Constraints | | -------------- | ------- | ----------- | @@ -61,38 +61,38 @@ Represents a user’s favorite Xiaomi device. --- -## Relationships +## Hubungan -- **Many-to-Many**: The `User` and `XiaomiDevice` models are connected through the `Favorite` model using Sequelize's many-to-many relationship. +- **Many-to-Many**: Model `User` dan `XiaomiDevice` terhubung melalui model `Favorite` menggunakan hubungan many-to-many Sequelize. --- -## Available Endpoints +## Endpoint yang Tersedia -### Public Endpoints +### Endpoint Publik -- `POST /register` - Create a new user account -- `POST /login` - Authenticate a user and return an access token -- `POST /login/google` - Authenticate a user via Google OAuth -- `GET /public/devices` - Retrieve all Xiaomi devices -- `GET /public/devices/:id` - Retrieve a specific Xiaomi device by ID +- `POST /register` - Membuat akun pengguna baru +- `POST /login` - Mengautentikasi pengguna dan mengembalikan token akses +- `POST /login/google` - Mengautentikasi pengguna melalui Google OAuth +- `GET /public/devices` - Mengambil semua perangkat Xiaomi +- `GET /public/devices/:id` - Mengambil perangkat Xiaomi tertentu berdasarkan ID -### Authenticated Endpoints +### Endpoint Terotentikasi -- `GET /devices` - Retrieve all Xiaomi devices (authenticated) -- `GET /devices/:id` - Retrieve a specific Xiaomi device by ID (authenticated) -- `PUT /users/update` - Update user information -- `POST /favorites/:XiaomiDeviceId` - Add a Xiaomi device to user’s favorites -- `GET /favorites` - Retrieve user’s favorite devices -- `DELETE /favorites/:XiaomiDeviceId` - Remove a Xiaomi device from user’s favorites +- `GET /devices` - Mengambil semua perangkat Xiaomi (terotentikasi) +- `GET /devices/:id` - Mengambil perangkat Xiaomi tertentu berdasarkan ID (terotentikasi) +- `PUT /users/update` - Memperbarui informasi pengguna +- `POST /favorites/:XiaomiDeviceId` - Menambahkan perangkat Xiaomi ke favorit pengguna +- `GET /favorites` - Mengambil perangkat favorit pengguna +- `DELETE /favorites/:XiaomiDeviceId` - Menghapus perangkat Xiaomi dari favorit pengguna --- -## Public Endpoints +## Endpoint Publik ### 1. POST /register -**Description**: Creates a new user account. +**Deskripsi**: Membuat akun pengguna baru. **Body**: @@ -122,7 +122,7 @@ Represents a user’s favorite Xiaomi device. } ``` -_Or similar validation errors for username, email, or password._ +_Atau error validasi serupa untuk username, email, atau password._ **Response (400 - Bad Request)**: @@ -136,7 +136,7 @@ _Or similar validation errors for username, email, or password._ ### 2. POST /login -**Description**: Authenticates a user and returns an access token. +**Deskripsi**: Mengautentikasi pengguna dan mengembalikan token akses. **Body**: @@ -163,7 +163,7 @@ _Or similar validation errors for username, email, or password._ } ``` -_Or_: +_Atau_: ```json { @@ -183,7 +183,7 @@ _Or_: ### 3. POST /login/google -**Description**: Authenticates a user via Google OAuth and returns an access token. +**Deskripsi**: Mengautentikasi pengguna melalui Google OAuth dan mengembalikan token akses. **Body**: @@ -221,7 +221,7 @@ _Or_: ### 4. GET /public/devices -**Description**: Retrieves all Xiaomi devices available on the platform. +**Deskripsi**: Mengambil semua perangkat Xiaomi yang tersedia di platform. **Response (200 - OK)**: @@ -236,9 +236,7 @@ _Or_: "camera": "50 MP", "video": "8K@24fps", "ram": "8 GB", - "chip - -set": "Snapdragon 8 Gen 3", + "chipset": "Snapdragon 8 Gen 3", "battery": "4610 mAh", "batteryType": "Li-Po", "body": "152.8 x 71.5 x 8.2 mm", @@ -263,11 +261,11 @@ set": "Snapdragon 8 Gen 3", ### 5. GET /public/devices/:id -**Description**: Retrieves details of a specific Xiaomi device by its ID. +**Deskripsi**: Mengambil detail perangkat Xiaomi tertentu berdasarkan ID-nya. -**Parameters**: +**Parameter**: -- `id` (required): Device ID (integer) +- `id` (wajib): ID Perangkat (integer) **Response (200 - OK)**: @@ -310,9 +308,9 @@ set": "Snapdragon 8 Gen 3", --- -## Authenticated Endpoints +## Endpoint Terotentikasi -All endpoints below require an `Authorization` header with a Bearer token obtained from the `/login` or `/login/google` endpoint. +Semua endpoint di bawah ini memerlukan header `Authorization` dengan token Bearer yang diperoleh dari endpoint `/login` atau `/login/google`. **Headers**: @@ -324,7 +322,7 @@ All endpoints below require an `Authorization` header with a Bearer token obtain ### 6. GET /devices -**Description**: Retrieves all Xiaomi devices for authenticated users. +**Deskripsi**: Mengambil semua perangkat Xiaomi untuk pengguna terotentikasi. **Response (200 - OK)**: @@ -372,11 +370,11 @@ All endpoints below require an `Authorization` header with a Bearer token obtain ### 7. GET /devices/:id -**Description**: Retrieves details of a specific Xiaomi device by its ID for authenticated users. +**Deskripsi**: Mengambil detail perangkat Xiaomi tertentu berdasarkan ID-nya untuk pengguna terotentikasi. -**Parameters**: +**Parameter**: -- `id` (required): Device ID (integer) +- `id` (wajib): ID Perangkat (integer) **Response (200 - OK)**: @@ -429,7 +427,7 @@ All endpoints below require an `Authorization` header with a Bearer token obtain ### 8. PUT /users/update -**Description**: Updates the authenticated user’s information. +**Deskripsi**: Memperbarui informasi pengguna terotentikasi. **Body**: @@ -458,7 +456,7 @@ All endpoints below require an `Authorization` header with a Bearer token obtain } ``` -_Or_: +_Atau_: ```json { @@ -466,7 +464,7 @@ _Or_: } ``` -_Or_: +_Atau_: ```json { @@ -494,11 +492,11 @@ _Or_: ### 9. POST /favorites/:XiaomiDeviceId -**Description**: Adds a Xiaomi device to the authenticated user’s favorites. +**Deskripsi**: Menambahkan perangkat Xiaomi ke favorit pengguna terotentikasi. -**Parameters**: +**Parameter**: -- `XiaomiDeviceId` (required): Device ID (integer) +- `XiaomiDeviceId` (wajib): ID Perangkat (integer) **Response (201 - Created)**: @@ -538,7 +536,7 @@ _Or_: ### 10. GET /favorites -**Description**: Retrieves the authenticated user’s favorite devices. +**Deskripsi**: Mengambil perangkat favorit pengguna terotentikasi. **Response (200 - OK)**: @@ -591,11 +589,11 @@ _Or_: ### 11. DELETE /favorites/:XiaomiDeviceId -**Description**: Removes a Xiaomi device from the authenticated user’s favorites. +**Deskripsi**: Menghapus perangkat Xiaomi dari favorit pengguna terotentikasi. -**Parameters**: +**Parameter**: -- `XiaomiDeviceId` (required): Device ID (integer) +- `XiaomiDeviceId` (wajib): ID Perangkat (integer) **Response (200 - OK)**: @@ -631,7 +629,7 @@ _Or_: --- -## Global Error Handling +## Penanganan Error Global **Response (401 - Unauthorized)**: @@ -649,7 +647,7 @@ _Or_: } ``` -_Or_: +_Atau_: ```json { @@ -657,7 +655,7 @@ _Or_: } ``` -_Or_: +_Atau_: ```json { @@ -672,7 +670,3 @@ _Or_: "message": "Internal server error" } ``` - ---- - -This API documentation provides clear guidance for developers interacting with the Xiaomi Devices platform. For further details, refer to the source code or contact the development team. diff --git a/server/__test__/testing.test.js b/server/__test__/testing.test.js index 6bd6130e..97763702 100644 --- a/server/__test__/testing.test.js +++ b/server/__test__/testing.test.js @@ -1,9 +1,14 @@ const request = require("supertest"); +const express = require("express"); const app = require("../app"); const { sequelize } = require("../models"); const { User, XiaomiDevice, Favorite } = require("../models"); const { hashPassword } = require("../helpers/bcrypt"); const { createToken } = require("../helpers/jwt"); +const { GoogleGenerativeAI } = require("@google/generative-ai"); + +// Mock untuk GoogleGenerativeAI +jest.mock("@google/generative-ai"); let access_token; @@ -52,6 +57,8 @@ beforeAll(async () => { storage: "128GB", comment: "Excellent mid-range phone", price: 3000000, + createdAt: new Date(), + updatedAt: new Date(), }); const user = await User.findOne({ where: { email: "test@example.com" } }); @@ -90,6 +97,17 @@ describe("User Authentication", () => { expect(response.body).toHaveProperty("message"); }); + test("Gagal register karena validasi Sequelize", async () => { + const response = await request(app).post("/register").send({ + username: "", + email: "invalid@example.com", + password: "password123", + }); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty("message"); + }); + test("Login dengan sukses", async () => { const response = await request(app).post("/login").send({ email: "test@example.com", @@ -102,6 +120,18 @@ describe("User Authentication", () => { access_token = response.body.access_token; }); + test("Gagal login karena data tidak lengkap", async () => { + const response = await request(app).post("/login").send({ + email: "test@example.com", + }); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty( + "message", + "Email and password are required" + ); + }); + test("Gagal login karena password salah", async () => { const response = await request(app).post("/login").send({ email: "test@example.com", @@ -121,6 +151,142 @@ describe("User Authentication", () => { expect(response.status).toBe(404); expect(response.body).toHaveProperty("message", "User not found"); }); + + test("Google login error handling", async () => { + const mockVerifyIdToken = jest + .fn() + .mockRejectedValue(new Error("Invalid token")); + const mockOAuth2Client = { + verifyIdToken: mockVerifyIdToken, + }; + + jest.mock("google-auth-library", () => ({ + OAuth2Client: jest.fn().mockImplementation(() => mockOAuth2Client), + })); + + const response = await request(app).post("/login/google").send({ + googleToken: "invalid-token", + }); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + }); + + test("Internal server error handling in register", async () => { + jest.spyOn(User, "create").mockImplementationOnce(() => { + throw new Error("Unexpected error"); + }); + + const response = await request(app).post("/register").send({ + username: "erroruser", + email: "error@example.com", + password: "password123", + }); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); + + test("Internal server error handling in login", async () => { + jest.spyOn(User, "findOne").mockImplementationOnce(() => { + throw new Error("Unexpected error"); + }); + + const response = await request(app).post("/login").send({ + email: "test@example.com", + password: "password123", + }); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); +}); + +describe("Public Routes", () => { + test("Mendapatkan daftar device untuk public", async () => { + const response = await request(app).get("/public/devices"); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect(response.body[0]).toHaveProperty("device_name"); + expect(response.body[0]).toHaveProperty("price"); + }); + + test("Mendapatkan daftar device dengan parameter query", async () => { + const response = await request(app).get( + "/public/devices?search=xiaomi&sort=price:asc&minPrice=1000000&maxPrice=5000000" + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test("Mendapatkan daftar device dengan sort tidak valid", async () => { + const response = await request(app).get("/public/devices?sort=invalid:asc"); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test("Mendapatkan daftar device dengan parameter minPrice saja", async () => { + const response = await request(app).get("/public/devices?minPrice=1000000"); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test("Mendapatkan daftar device dengan parameter maxPrice saja", async () => { + const response = await request(app).get("/public/devices?maxPrice=5000000"); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test("Mendapatkan detail device untuk public", async () => { + const response = await request(app).get("/public/devices/1"); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("device_name"); + expect(response.body).toHaveProperty("price"); + expect(response.body).toHaveProperty("camera"); + }); + + test("Gagal mendapatkan detail device yang tidak ada untuk public", async () => { + const response = await request(app).get("/public/devices/999"); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty("message", "Device tidak ditemukan"); + }); + + test("Error handling pada public routes", async () => { + jest.spyOn(XiaomiDevice, "findAll").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app).get("/public/devices"); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); + + test("Error handling pada get device by id public route", async () => { + jest.spyOn(XiaomiDevice, "findByPk").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app).get("/public/devices/1"); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); }); describe("Authenticated Endpoints", () => { @@ -135,6 +301,30 @@ describe("Authenticated Endpoints", () => { expect(response.body[0]).toHaveProperty("camera"); }); + test("Mendapatkan daftar device tanpa token", async () => { + const response = await request(app).get("/devices"); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect(response.body[0]).toHaveProperty("device_name"); + expect(response.body[0]).not.toHaveProperty("camera"); + }); + + test("Error handling pada get devices authenticated route", async () => { + jest.spyOn(XiaomiDevice, "findAll").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app) + .get("/devices") + .set("access_token", access_token); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); + test("Mendapatkan detail device lengkap dengan sukses", async () => { const response = await request(app) .get("/devices/1") @@ -145,6 +335,38 @@ describe("Authenticated Endpoints", () => { expect(response.body).toHaveProperty("camera"); }); + test("Mendapatkan detail device tanpa token", async () => { + const response = await request(app).get("/devices/1"); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("device_name"); + expect(response.body).not.toHaveProperty("camera"); + }); + + test("Gagal mendapatkan detail device yang tidak ada", async () => { + const response = await request(app) + .get("/devices/999") + .set("access_token", access_token); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty("message", "Device tidak ditemukan"); + }); + + test("Error handling pada get device by id authenticated route", async () => { + jest.spyOn(XiaomiDevice, "findByPk").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app) + .get("/devices/1") + .set("access_token", access_token); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); + test("Menambahkan device ke favorit dengan sukses", async () => { await Favorite.destroy({ where: { XiaomiDeviceId: 1 } }); @@ -171,6 +393,30 @@ describe("Authenticated Endpoints", () => { ); }); + test("Gagal menambahkan device yang tidak ada ke favorit", async () => { + const response = await request(app) + .post("/favorites/999") + .set("access_token", access_token); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty("message", "Device tidak ditemukan"); + }); + + test("Error handling pada add to favorites route", async () => { + jest.spyOn(XiaomiDevice, "findByPk").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app) + .post("/favorites/1") + .set("access_token", access_token); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); + test("Mendapatkan daftar favorit dengan sukses", async () => { const response = await request(app) .get("/favorites") @@ -181,7 +427,30 @@ describe("Authenticated Endpoints", () => { expect(response.body.length).toBeGreaterThan(0); }); + test("Error handling pada get favorites route", async () => { + jest.spyOn(Favorite, "findAll").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app) + .get("/favorites") + .set("access_token", access_token); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); + test("Menghapus device dari favorit dengan sukses", async () => { + // Tambahkan kembali ke favorit untuk dihapus + await Favorite.create({ + UserId: 1, + XiaomiDeviceId: 1, + createdAt: new Date(), + updatedAt: new Date(), + }); + const response = await request(app) .delete("/favorites/1") .set("access_token", access_token); @@ -204,11 +473,133 @@ describe("Authenticated Endpoints", () => { "Device tidak ada di daftar favorit Anda" ); }); + + test("Error handling pada remove from favorites route", async () => { + jest.spyOn(Favorite, "findOne").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app) + .delete("/favorites/1") + .set("access_token", access_token); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); + + test("Update user profile dengan sukses", async () => { + const response = await request(app) + .put("/users/update") + .set("access_token", access_token) + .send({ + username: "updateduser", + email: "updated@example.com", + }); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("message", "User berhasil diupdate"); + expect(response.body.user).toHaveProperty("username", "updateduser"); + expect(response.body.user).toHaveProperty("email", "updated@example.com"); + }); + + test("Gagal update user profile tanpa data", async () => { + const response = await request(app) + .put("/users/update") + .set("access_token", access_token) + .send({}); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty( + "message", + "Tidak ada data yang diupdate" + ); + }); + + test("Gagal update user profile dengan user tidak ditemukan", async () => { + jest.spyOn(User, "findByPk").mockResolvedValueOnce(null); + + const response = await request(app) + .put("/users/update") + .set("access_token", access_token) + .send({ + username: "newname", + }); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty("message", "User tidak ditemukan"); + + jest.restoreAllMocks(); + }); + + test("Gagal update user profile karena validasi Sequelize", async () => { + const validationError = { + name: "SequelizeValidationError", + errors: [{ message: "Username tidak boleh kosong" }], + }; + + jest.spyOn(User.prototype, "update").mockRejectedValueOnce(validationError); + + const response = await request(app) + .put("/users/update") + .set("access_token", access_token) + .send({ + username: "", + }); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty( + "message", + "Username tidak boleh kosong" + ); + + jest.restoreAllMocks(); + }); + + test("Gagal update user profile karena unique constraint Sequelize", async () => { + const uniqueError = { + name: "SequelizeUniqueConstraintError", + errors: [{ message: "Email sudah digunakan" }], + }; + + jest.spyOn(User.prototype, "update").mockRejectedValueOnce(uniqueError); + + const response = await request(app) + .put("/users/update") + .set("access_token", access_token) + .send({ + email: "test@example.com", + }); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty("message", "Email sudah digunakan"); + + jest.restoreAllMocks(); + }); + + test("Error handling pada update user route", async () => { + jest.spyOn(User, "findByPk").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app) + .put("/users/update") + .set("access_token", access_token) + .send({ + username: "newname", + }); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + + jest.restoreAllMocks(); + }); }); describe("Authentication Middleware", () => { - test("Gagal mengakses endpoint terproteksi tanpa token", async () => { - const response = await request(app).get("/favorites"); + test("Mengakses endpoint publik tanpa token", async () => { + const response = await request(app).get("/public/devices"); expect(response.status).toBe(200); expect(Array.isArray(response.body)).toBe(true); @@ -222,4 +613,189 @@ describe("Authentication Middleware", () => { expect(response.status).toBe(401); expect(response.body).toHaveProperty("message", "Invalid token"); }); + + test("Gagal mengakses endpoint dengan user tidak ditemukan", async () => { + const invalidToken = createToken({ + id: 999, + email: "nonexist@example.com", + }); + + const response = await request(app) + .get("/favorites") + .set("access_token", invalidToken); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty("message", "User not found"); + }); + + test("Error handling di middleware authentication", async () => { + jest.spyOn(User, "findByPk").mockImplementationOnce(() => { + throw new Error("Database error"); + }); + + const response = await request(app) + .get("/favorites") + .set("access_token", access_token); + + expect(response.status).toBe(401); + expect(response.body).toHaveProperty("message", "Invalid token"); + + jest.restoreAllMocks(); + }); +}); + +describe("AI Endpoint", () => { + beforeEach(() => { + // Mock untuk Google Generative AI + const mockModel = { + generateContent: jest.fn().mockResolvedValue({ + response: { + text: () => "Ini adalah respons dari Gemini API", + }, + }), + }; + + GoogleGenerativeAI.mockImplementation(() => ({ + getGenerativeModel: jest.fn().mockReturnValue(mockModel), + })); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test("Mendapatkan respons AI dengan sukses", async () => { + const response = await request(app).post("/ai").send({ + prompt: "Apa kelebihan HP Xiaomi?", + }); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("response"); + }); + + test("Gagal mendapatkan respons AI tanpa prompt", async () => { + const response = await request(app).post("/ai").send({}); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty("message", "Prompt diperlukan"); + }); + + test("Error handling pada AI endpoint", async () => { + GoogleGenerativeAI.mockImplementation(() => ({ + getGenerativeModel: jest.fn().mockImplementation(() => { + throw new Error("API error"); + }), + })); + + const response = await request(app).post("/ai").send({ + prompt: "Apa kelebihan HP Xiaomi?", + }); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + }); +}); + +describe("Testing Routes", () => { + test("Health check endpoint", async () => { + const response = await request(app).get("/testing/health"); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("message", "API is running"); + }); +}); + +describe("Error Handler", () => { + test("Global error handler", async () => { + // Buat router express sederhana dengan error handler dari app.js + const router = express(); + router.use((req, res, next) => { + next(new Error("Test error")); + }); + + // Ekstrak error handler dari app.js + const errorHandler = app._router.stack + .filter((layer) => layer.handle && layer.handle.length === 4) + .pop().handle; + + // Tambahkan error handler ke router + router.use(errorHandler); + + // Test + const response = await request(router).get("/"); + + expect(response.status).toBe(500); + expect(response.body).toHaveProperty("message", "Internal server error"); + }); +}); + +describe("App Error Handling", () => { + // Simulasi error ketika server dijalankan + test("Server error handler", () => { + const mockServer = { + listen: jest.fn().mockReturnThis(), + on: jest.fn().mockImplementation((event, callback) => { + if (event === "error") { + // Simulasi error EADDRINUSE + callback({ code: "EADDRINUSE" }); + } + return mockServer; + }), + }; + + const originalListen = app.listen; + app.listen = jest.fn().mockReturnValue(mockServer); + + const consoleSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + + // Jalankan aplikasi + const serverInstance = app.listen(3000, () => {}); + + // Verifikasi bahwa on("error") dipanggil + expect(serverInstance.on).toHaveBeenCalledWith( + "error", + expect.any(Function) + ); + expect(consoleSpy).toHaveBeenCalled(); + + // Simulasi error lain + serverInstance.on.mock.calls[0][1]({ code: "OTHER_ERROR" }); + expect(consoleSpy).toHaveBeenCalledTimes(2); + + // Restore mocks + consoleSpy.mockRestore(); + app.listen = originalListen; + }); + + test("Gemini API initialization error", () => { + // Backup environment variable + const originalApiKey = process.env.GOOGLE_API_KEY; + + // Simulasikan environment variable tidak ada + delete process.env.GOOGLE_API_KEY; + + const consoleSpy = jest + .spyOn(console, "error") + .mockImplementation(() => {}); + const processExitSpy = jest + .spyOn(process, "exit") + .mockImplementation(() => {}); + + // Require app.js lagi untuk menjalankan code initialization + jest.isolateModules(() => { + try { + require("../app"); + } catch (error) { + // Catch any errors + } + }); + + // Restore environment variable + process.env.GOOGLE_API_KEY = originalApiKey; + + consoleSpy.mockRestore(); + processExitSpy.mockRestore(); + }); }); diff --git a/server/coverage/clover.xml b/server/coverage/clover.xml index 8de94047..e69c9143 100644 --- a/server/coverage/clover.xml +++ b/server/coverage/clover.xml @@ -1,143 +1,163 @@ - - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - + - + - - - - + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + @@ -145,20 +165,20 @@ - - - - - + + + + + - - - - - - - - + + + + + + + + @@ -167,135 +187,135 @@ - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - + - - + + - + - + - - - - - - - + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + - + - - - - - + + + + + - + - - - - - - + + + + + + diff --git a/server/coverage/coverage-final.json b/server/coverage/coverage-final.json index 458fc68a..4957fcff 100644 --- a/server/coverage/coverage-final.json +++ b/server/coverage/coverage-final.json @@ -1,13 +1,13 @@ -{"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\app.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\app.js","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},"1":{"start":{"line":2,"column":2},"end":{"line":2,"column":29}},"2":{"start":{"line":5,"column":16},"end":{"line":5,"column":34}},"3":{"start":{"line":6,"column":12},"end":{"line":6,"column":21}},"4":{"start":{"line":7,"column":13},"end":{"line":7,"column":37}},"5":{"start":{"line":8,"column":13},"end":{"line":8,"column":28}},"6":{"start":{"line":9,"column":23},"end":{"line":9,"column":62}},"7":{"start":{"line":10,"column":23},"end":{"line":10,"column":62}},"8":{"start":{"line":11,"column":19},"end":{"line":11,"column":54}},"9":{"start":{"line":12,"column":22},"end":{"line":12,"column":60}},"10":{"start":{"line":13,"column":22},"end":{"line":13,"column":49}},"11":{"start":{"line":16,"column":0},"end":{"line":16,"column":16}},"12":{"start":{"line":17,"column":0},"end":{"line":17,"column":24}},"13":{"start":{"line":18,"column":0},"end":{"line":18,"column":48}},"14":{"start":{"line":21,"column":0},"end":{"line":21,"column":35}},"15":{"start":{"line":24,"column":0},"end":{"line":24,"column":56}},"16":{"start":{"line":25,"column":0},"end":{"line":25,"column":60}},"17":{"start":{"line":28,"column":0},"end":{"line":28,"column":47}},"18":{"start":{"line":29,"column":0},"end":{"line":29,"column":41}},"19":{"start":{"line":30,"column":0},"end":{"line":30,"column":54}},"20":{"start":{"line":33,"column":0},"end":{"line":33,"column":24}},"21":{"start":{"line":36,"column":0},"end":{"line":36,"column":46}},"22":{"start":{"line":37,"column":0},"end":{"line":37,"column":50}},"23":{"start":{"line":38,"column":0},"end":{"line":38,"column":52}},"24":{"start":{"line":39,"column":0},"end":{"line":39,"column":66}},"25":{"start":{"line":40,"column":0},"end":{"line":40,"column":47}},"26":{"start":{"line":41,"column":0},"end":{"line":41,"column":73}},"27":{"start":{"line":44,"column":0},"end":{"line":47,"column":3}},"28":{"start":{"line":45,"column":2},"end":{"line":45,"column":21}},"29":{"start":{"line":46,"column":2},"end":{"line":46,"column":61}},"30":{"start":{"line":49,"column":0},"end":{"line":51,"column":3}},"31":{"start":{"line":50,"column":2},"end":{"line":50,"column":48}},"32":{"start":{"line":53,"column":0},"end":{"line":53,"column":21}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":44,"column":8},"end":{"line":44,"column":9}},"loc":{"start":{"line":44,"column":33},"end":{"line":47,"column":1}},"line":44},"1":{"name":"(anonymous_1)","decl":{"start":{"line":49,"column":17},"end":{"line":49,"column":18}},"loc":{"start":{"line":49,"column":23},"end":{"line":51,"column":1}},"line":49}},"branchMap":{"0":{"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},"type":"if","locations":[{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},{"start":{},"end":{}}],"line":1},"1":{"loc":{"start":{"line":7,"column":13},"end":{"line":7,"column":37}},"type":"binary-expr","locations":[{"start":{"line":7,"column":13},"end":{"line":7,"column":29}},{"start":{"line":7,"column":33},"end":{"line":7,"column":37}}],"line":7}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":0,"29":0,"30":1,"31":1,"32":1},"f":{"0":0,"1":1},"b":{"0":[1,0],"1":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"3c3ddd184d25f7167b76b885f2d270142c04a16f"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\Controller.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\Controller.js","statementMap":{"0":{"start":{"line":1,"column":41},"end":{"line":1,"column":61}},"1":{"start":{"line":6,"column":4},"end":{"line":31,"column":5}},"2":{"start":{"line":7,"column":22},"end":{"line":7,"column":50}},"3":{"start":{"line":10,"column":25},"end":{"line":10,"column":48}},"4":{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},"5":{"start":{"line":14,"column":31},"end":{"line":22,"column":10}},"6":{"start":{"line":15,"column":10},"end":{"line":21,"column":12}},"7":{"start":{"line":24,"column":8},"end":{"line":24,"column":52}},"8":{"start":{"line":28,"column":6},"end":{"line":28,"column":36}},"9":{"start":{"line":30,"column":6},"end":{"line":30,"column":65}},"10":{"start":{"line":36,"column":4},"end":{"line":65,"column":5}},"11":{"start":{"line":37,"column":21},"end":{"line":37,"column":31}},"12":{"start":{"line":39,"column":21},"end":{"line":39,"column":52}},"13":{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},"14":{"start":{"line":42,"column":8},"end":{"line":42,"column":75}},"15":{"start":{"line":46,"column":25},"end":{"line":46,"column":48}},"16":{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},"17":{"start":{"line":50,"column":30},"end":{"line":56,"column":9}},"18":{"start":{"line":58,"column":8},"end":{"line":58,"column":51}},"19":{"start":{"line":62,"column":6},"end":{"line":62,"column":35}},"20":{"start":{"line":64,"column":6},"end":{"line":64,"column":65}},"21":{"start":{"line":70,"column":4},"end":{"line":100,"column":5}},"22":{"start":{"line":71,"column":21},"end":{"line":71,"column":32}},"23":{"start":{"line":72,"column":33},"end":{"line":72,"column":43}},"24":{"start":{"line":75,"column":21},"end":{"line":75,"column":64}},"25":{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},"26":{"start":{"line":78,"column":8},"end":{"line":78,"column":75}},"27":{"start":{"line":82,"column":31},"end":{"line":84,"column":8}},"28":{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},"29":{"start":{"line":87,"column":8},"end":{"line":89,"column":72}},"30":{"start":{"line":93,"column":6},"end":{"line":93,"column":56}},"31":{"start":{"line":95,"column":6},"end":{"line":97,"column":69}},"32":{"start":{"line":99,"column":6},"end":{"line":99,"column":65}},"33":{"start":{"line":105,"column":4},"end":{"line":119,"column":5}},"34":{"start":{"line":106,"column":21},"end":{"line":106,"column":32}},"35":{"start":{"line":108,"column":24},"end":{"line":111,"column":8}},"36":{"start":{"line":113,"column":30},"end":{"line":113,"column":70}},"37":{"start":{"line":113,"column":53},"end":{"line":113,"column":69}},"38":{"start":{"line":115,"column":6},"end":{"line":115,"column":44}},"39":{"start":{"line":117,"column":6},"end":{"line":117,"column":27}},"40":{"start":{"line":118,"column":6},"end":{"line":118,"column":65}},"41":{"start":{"line":124,"column":4},"end":{"line":145,"column":5}},"42":{"start":{"line":125,"column":21},"end":{"line":125,"column":32}},"43":{"start":{"line":126,"column":33},"end":{"line":126,"column":43}},"44":{"start":{"line":129,"column":23},"end":{"line":131,"column":8}},"45":{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},"46":{"start":{"line":134,"column":8},"end":{"line":136,"column":72}},"47":{"start":{"line":140,"column":6},"end":{"line":140,"column":31}},"48":{"start":{"line":142,"column":6},"end":{"line":142,"column":80}},"49":{"start":{"line":144,"column":6},"end":{"line":144,"column":65}},"50":{"start":{"line":149,"column":0},"end":{"line":149,"column":28}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":2},"end":{"line":5,"column":3}},"loc":{"start":{"line":5,"column":39},"end":{"line":32,"column":3}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":14,"column":43},"end":{"line":14,"column":44}},"loc":{"start":{"line":14,"column":55},"end":{"line":22,"column":9}},"line":14},"2":{"name":"(anonymous_2)","decl":{"start":{"line":35,"column":2},"end":{"line":35,"column":3}},"loc":{"start":{"line":35,"column":39},"end":{"line":66,"column":3}},"line":35},"3":{"name":"(anonymous_3)","decl":{"start":{"line":69,"column":2},"end":{"line":69,"column":3}},"loc":{"start":{"line":69,"column":40},"end":{"line":101,"column":3}},"line":69},"4":{"name":"(anonymous_4)","decl":{"start":{"line":104,"column":2},"end":{"line":104,"column":3}},"loc":{"start":{"line":104,"column":38},"end":{"line":120,"column":3}},"line":104},"5":{"name":"(anonymous_5)","decl":{"start":{"line":113,"column":44},"end":{"line":113,"column":45}},"loc":{"start":{"line":113,"column":53},"end":{"line":113,"column":69}},"line":113},"6":{"name":"(anonymous_6)","decl":{"start":{"line":123,"column":2},"end":{"line":123,"column":3}},"loc":{"start":{"line":123,"column":45},"end":{"line":146,"column":3}},"line":123}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":25},"end":{"line":10,"column":48}},"type":"cond-expr","locations":[{"start":{"line":10,"column":36},"end":{"line":10,"column":40}},{"start":{"line":10,"column":43},"end":{"line":10,"column":48}}],"line":10},"1":{"loc":{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},"type":"if","locations":[{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},{"start":{},"end":{}}],"line":13},"2":{"loc":{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},"type":"if","locations":[{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},{"start":{},"end":{}}],"line":41},"3":{"loc":{"start":{"line":46,"column":25},"end":{"line":46,"column":48}},"type":"cond-expr","locations":[{"start":{"line":46,"column":36},"end":{"line":46,"column":40}},{"start":{"line":46,"column":43},"end":{"line":46,"column":48}}],"line":46},"4":{"loc":{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},"type":"if","locations":[{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},{"start":{},"end":{}}],"line":49},"5":{"loc":{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},"type":"if","locations":[{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},{"start":{},"end":{}}],"line":77},"6":{"loc":{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},"type":"if","locations":[{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},{"start":{},"end":{}}],"line":86},"7":{"loc":{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},"type":"if","locations":[{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},{"start":{},"end":{}}],"line":133}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":0,"6":0,"7":0,"8":1,"9":0,"10":1,"11":1,"12":1,"13":1,"14":0,"15":1,"16":1,"17":0,"18":0,"19":1,"20":0,"21":2,"22":2,"23":2,"24":2,"25":2,"26":0,"27":2,"28":2,"29":1,"30":1,"31":1,"32":0,"33":2,"34":2,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":2,"42":2,"43":2,"44":2,"45":2,"46":1,"47":1,"48":1,"49":0,"50":1},"f":{"0":1,"1":0,"2":1,"3":2,"4":2,"5":1,"6":2},"b":{"0":[1,0],"1":[0,1],"2":[0,1],"3":[1,0],"4":[0,1],"5":[0,2],"6":[1,1],"7":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"737b92e119bcfa4ab8431d4a132d7beeb4e23ad7"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\PubController.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\PubController.js","statementMap":{"0":{"start":{"line":4,"column":4},"end":{"line":4,"column":24}},"1":{"start":{"line":8,"column":4},"end":{"line":60,"column":5}},"2":{"start":{"line":9,"column":51},"end":{"line":9,"column":60}},"3":{"start":{"line":12,"column":25},"end":{"line":12,"column":27}},"4":{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},"5":{"start":{"line":16,"column":8},"end":{"line":21,"column":10}},"6":{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},"7":{"start":{"line":26,"column":8},"end":{"line":29,"column":10}},"8":{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},"9":{"start":{"line":30,"column":22},"end":{"line":30,"column":66}},"10":{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},"11":{"start":{"line":31,"column":22},"end":{"line":31,"column":66}},"12":{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},"13":{"start":{"line":36,"column":31},"end":{"line":36,"column":46}},"14":{"start":{"line":37,"column":28},"end":{"line":37,"column":52}},"15":{"start":{"line":38,"column":28},"end":{"line":38,"column":43}},"16":{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},"17":{"start":{"line":41,"column":10},"end":{"line":41,"column":62}},"18":{"start":{"line":45,"column":22},"end":{"line":45,"column":62}},"19":{"start":{"line":48,"column":29},"end":{"line":54,"column":9}},"20":{"start":{"line":48,"column":54},"end":{"line":54,"column":7}},"21":{"start":{"line":56,"column":6},"end":{"line":56,"column":43}},"22":{"start":{"line":58,"column":6},"end":{"line":58,"column":54}},"23":{"start":{"line":59,"column":6},"end":{"line":59,"column":65}},"24":{"start":{"line":65,"column":4},"end":{"line":78,"column":5}},"25":{"start":{"line":66,"column":21},"end":{"line":66,"column":31}},"26":{"start":{"line":68,"column":21},"end":{"line":68,"column":52}},"27":{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},"28":{"start":{"line":71,"column":8},"end":{"line":71,"column":75}},"29":{"start":{"line":75,"column":6},"end":{"line":75,"column":35}},"30":{"start":{"line":77,"column":6},"end":{"line":77,"column":65}},"31":{"start":{"line":82,"column":0},"end":{"line":82,"column":31}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":7,"column":2},"end":{"line":7,"column":3}},"loc":{"start":{"line":7,"column":39},"end":{"line":61,"column":3}},"line":7},"1":{"name":"(anonymous_1)","decl":{"start":{"line":48,"column":41},"end":{"line":48,"column":42}},"loc":{"start":{"line":48,"column":54},"end":{"line":54,"column":7}},"line":48},"2":{"name":"(anonymous_2)","decl":{"start":{"line":64,"column":2},"end":{"line":64,"column":3}},"loc":{"start":{"line":64,"column":39},"end":{"line":79,"column":3}},"line":64}},"branchMap":{"0":{"loc":{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},"type":"if","locations":[{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},{"start":{},"end":{}}],"line":15},"1":{"loc":{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},"type":"if","locations":[{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},{"start":{},"end":{}}],"line":25},"2":{"loc":{"start":{"line":25,"column":10},"end":{"line":25,"column":30}},"type":"binary-expr","locations":[{"start":{"line":25,"column":10},"end":{"line":25,"column":18}},{"start":{"line":25,"column":22},"end":{"line":25,"column":30}}],"line":25},"3":{"loc":{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},"type":"if","locations":[{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},{"start":{},"end":{}}],"line":30},"4":{"loc":{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},"type":"if","locations":[{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},{"start":{},"end":{}}],"line":31},"5":{"loc":{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},"type":"if","locations":[{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},{"start":{},"end":{}}],"line":35},"6":{"loc":{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},"type":"if","locations":[{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},{"start":{},"end":{}}],"line":40},"7":{"loc":{"start":{"line":40,"column":12},"end":{"line":40,"column":70}},"type":"binary-expr","locations":[{"start":{"line":40,"column":12},"end":{"line":40,"column":39}},{"start":{"line":40,"column":43},"end":{"line":40,"column":70}}],"line":40},"8":{"loc":{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},"type":"if","locations":[{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},{"start":{},"end":{}}],"line":70}},"s":{"0":1,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":1},"f":{"0":0,"1":0,"2":0},"b":{"0":[0,0],"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0],"8":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"50d00d019a8a66eca334a967de982b42c35cca4d"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\UserController.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\UserController.js","statementMap":{"0":{"start":{"line":1,"column":17},"end":{"line":1,"column":37}},"1":{"start":{"line":2,"column":28},"end":{"line":2,"column":56}},"2":{"start":{"line":3,"column":24},"end":{"line":3,"column":49}},"3":{"start":{"line":4,"column":25},"end":{"line":4,"column":55}},"4":{"start":{"line":5,"column":15},"end":{"line":5,"column":33}},"5":{"start":{"line":9,"column":4},"end":{"line":45,"column":5}},"6":{"start":{"line":10,"column":30},"end":{"line":10,"column":38}},"7":{"start":{"line":11,"column":21},"end":{"line":14,"column":8}},"8":{"start":{"line":15,"column":22},"end":{"line":15,"column":41}},"9":{"start":{"line":17,"column":17},"end":{"line":19,"column":8}},"10":{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},"11":{"start":{"line":22,"column":8},"end":{"line":26,"column":11}},"12":{"start":{"line":29,"column":27},"end":{"line":33,"column":7}},"13":{"start":{"line":35,"column":20},"end":{"line":35,"column":45}},"14":{"start":{"line":36,"column":6},"end":{"line":41,"column":9}},"15":{"start":{"line":43,"column":6},"end":{"line":43,"column":25}},"16":{"start":{"line":44,"column":6},"end":{"line":44,"column":65}},"17":{"start":{"line":49,"column":4},"end":{"line":72,"column":5}},"18":{"start":{"line":50,"column":44},"end":{"line":50,"column":52}},"19":{"start":{"line":52,"column":22},"end":{"line":56,"column":8}},"20":{"start":{"line":58,"column":6},"end":{"line":62,"column":9}},"21":{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},"22":{"start":{"line":68,"column":8},"end":{"line":68,"column":67}},"23":{"start":{"line":70,"column":8},"end":{"line":70,"column":67}},"24":{"start":{"line":76,"column":4},"end":{"line":116,"column":5}},"25":{"start":{"line":77,"column":34},"end":{"line":77,"column":42}},"26":{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},"27":{"start":{"line":80,"column":8},"end":{"line":82,"column":64}},"28":{"start":{"line":85,"column":19},"end":{"line":87,"column":8}},"29":{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},"30":{"start":{"line":90,"column":8},"end":{"line":90,"column":67}},"31":{"start":{"line":93,"column":30},"end":{"line":93,"column":70}},"32":{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},"33":{"start":{"line":96,"column":8},"end":{"line":96,"column":69}},"34":{"start":{"line":99,"column":22},"end":{"line":103,"column":7}},"35":{"start":{"line":105,"column":20},"end":{"line":105,"column":40}},"36":{"start":{"line":107,"column":6},"end":{"line":112,"column":9}},"37":{"start":{"line":114,"column":6},"end":{"line":114,"column":27}},"38":{"start":{"line":115,"column":6},"end":{"line":115,"column":65}},"39":{"start":{"line":120,"column":4},"end":{"line":161,"column":5}},"40":{"start":{"line":121,"column":21},"end":{"line":121,"column":32}},"41":{"start":{"line":122,"column":34},"end":{"line":122,"column":42}},"42":{"start":{"line":124,"column":19},"end":{"line":124,"column":46}},"43":{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},"44":{"start":{"line":127,"column":8},"end":{"line":127,"column":73}},"45":{"start":{"line":130,"column":27},"end":{"line":130,"column":29}},"46":{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},"47":{"start":{"line":132,"column":20},"end":{"line":132,"column":53}},"48":{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},"49":{"start":{"line":133,"column":17},"end":{"line":133,"column":44}},"50":{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},"51":{"start":{"line":136,"column":8},"end":{"line":138,"column":61}},"52":{"start":{"line":141,"column":6},"end":{"line":141,"column":38}},"53":{"start":{"line":143,"column":6},"end":{"line":150,"column":9}},"54":{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},"55":{"start":{"line":156,"column":8},"end":{"line":156,"column":67}},"56":{"start":{"line":158,"column":8},"end":{"line":158,"column":29}},"57":{"start":{"line":159,"column":8},"end":{"line":159,"column":67}},"58":{"start":{"line":165,"column":0},"end":{"line":165,"column":32}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":8,"column":2},"end":{"line":8,"column":3}},"loc":{"start":{"line":8,"column":37},"end":{"line":46,"column":3}},"line":8},"1":{"name":"(anonymous_1)","decl":{"start":{"line":48,"column":2},"end":{"line":48,"column":3}},"loc":{"start":{"line":48,"column":34},"end":{"line":73,"column":3}},"line":48},"2":{"name":"(anonymous_2)","decl":{"start":{"line":75,"column":2},"end":{"line":75,"column":3}},"loc":{"start":{"line":75,"column":31},"end":{"line":117,"column":3}},"line":75},"3":{"name":"(anonymous_3)","decl":{"start":{"line":119,"column":2},"end":{"line":119,"column":3}},"loc":{"start":{"line":119,"column":36},"end":{"line":162,"column":3}},"line":119}},"branchMap":{"0":{"loc":{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},"type":"if","locations":[{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},{"start":{},"end":{}}],"line":21},"1":{"loc":{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},"type":"if","locations":[{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},{"start":{"line":69,"column":13},"end":{"line":71,"column":7}}],"line":64},"2":{"loc":{"start":{"line":65,"column":8},"end":{"line":66,"column":55}},"type":"binary-expr","locations":[{"start":{"line":65,"column":8},"end":{"line":65,"column":49}},{"start":{"line":66,"column":8},"end":{"line":66,"column":55}}],"line":65},"3":{"loc":{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},"type":"if","locations":[{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},{"start":{},"end":{}}],"line":79},"4":{"loc":{"start":{"line":79,"column":10},"end":{"line":79,"column":29}},"type":"binary-expr","locations":[{"start":{"line":79,"column":10},"end":{"line":79,"column":16}},{"start":{"line":79,"column":20},"end":{"line":79,"column":29}}],"line":79},"5":{"loc":{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},"type":"if","locations":[{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},{"start":{},"end":{}}],"line":89},"6":{"loc":{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},"type":"if","locations":[{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},{"start":{},"end":{}}],"line":95},"7":{"loc":{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},"type":"if","locations":[{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},{"start":{},"end":{}}],"line":126},"8":{"loc":{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},"type":"if","locations":[{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},{"start":{},"end":{}}],"line":132},"9":{"loc":{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},"type":"if","locations":[{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},{"start":{},"end":{}}],"line":133},"10":{"loc":{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},"type":"if","locations":[{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},{"start":{},"end":{}}],"line":135},"11":{"loc":{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},"type":"if","locations":[{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},{"start":{"line":157,"column":13},"end":{"line":160,"column":7}}],"line":152},"12":{"loc":{"start":{"line":153,"column":8},"end":{"line":154,"column":55}},"type":"binary-expr","locations":[{"start":{"line":153,"column":8},"end":{"line":153,"column":49}},{"start":{"line":154,"column":8},"end":{"line":154,"column":55}}],"line":153}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":2,"18":2,"19":2,"20":1,"21":1,"22":1,"23":0,"24":3,"25":3,"26":3,"27":0,"28":3,"29":3,"30":1,"31":2,"32":2,"33":2,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":1},"f":{"0":0,"1":2,"2":3,"3":0},"b":{"0":[0,0],"1":[1,0],"2":[1,1],"3":[0,3],"4":[3,3],"5":[1,2],"6":[2,0],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"1cc80a39bdb2f0e086371f69dc5963f6c4e5b9ee"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\bcrypt.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\bcrypt.js","statementMap":{"0":{"start":{"line":1,"column":15},"end":{"line":1,"column":34}},"1":{"start":{"line":3,"column":21},"end":{"line":5,"column":1}},"2":{"start":{"line":4,"column":2},"end":{"line":4,"column":39}},"3":{"start":{"line":7,"column":24},"end":{"line":9,"column":1}},"4":{"start":{"line":8,"column":2},"end":{"line":8,"column":54}},"5":{"start":{"line":11,"column":0},"end":{"line":14,"column":2}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":21},"end":{"line":3,"column":22}},"loc":{"start":{"line":3,"column":35},"end":{"line":5,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":24},"end":{"line":7,"column":25}},"loc":{"start":{"line":7,"column":54},"end":{"line":9,"column":1}},"line":7}},"branchMap":{},"s":{"0":1,"1":1,"2":4,"3":1,"4":2,"5":1},"f":{"0":4,"1":2},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c74a0375d91f19b5bcb02453f3e0a1ea35f7a31a"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\jwt.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\jwt.js","statementMap":{"0":{"start":{"line":1,"column":12},"end":{"line":1,"column":35}},"1":{"start":{"line":2,"column":19},"end":{"line":2,"column":41}},"2":{"start":{"line":4,"column":20},"end":{"line":6,"column":1}},"3":{"start":{"line":5,"column":2},"end":{"line":5,"column":39}},"4":{"start":{"line":8,"column":20},"end":{"line":10,"column":1}},"5":{"start":{"line":9,"column":2},"end":{"line":9,"column":39}},"6":{"start":{"line":12,"column":0},"end":{"line":15,"column":2}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":4,"column":20},"end":{"line":4,"column":21}},"loc":{"start":{"line":4,"column":33},"end":{"line":6,"column":1}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":8,"column":20},"end":{"line":8,"column":21}},"loc":{"start":{"line":8,"column":31},"end":{"line":10,"column":1}},"line":8}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":8,"6":1},"f":{"0":1,"1":8},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"70740eafddc547dd656de5caf7cef5487e23bfce"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\middlewares\\authentication.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\middlewares\\authentication.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":1,"column":49}},"1":{"start":{"line":2,"column":17},"end":{"line":2,"column":37}},"2":{"start":{"line":4,"column":23},"end":{"line":31,"column":1}},"3":{"start":{"line":5,"column":2},"end":{"line":30,"column":3}},"4":{"start":{"line":6,"column":29},"end":{"line":6,"column":40}},"5":{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},"6":{"start":{"line":9,"column":6},"end":{"line":9,"column":20}},"7":{"start":{"line":12,"column":20},"end":{"line":12,"column":45}},"8":{"start":{"line":14,"column":17},"end":{"line":14,"column":48}},"9":{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},"10":{"start":{"line":17,"column":6},"end":{"line":17,"column":65}},"11":{"start":{"line":20,"column":4},"end":{"line":24,"column":6}},"12":{"start":{"line":26,"column":4},"end":{"line":26,"column":11}},"13":{"start":{"line":28,"column":4},"end":{"line":28,"column":25}},"14":{"start":{"line":29,"column":4},"end":{"line":29,"column":62}},"15":{"start":{"line":33,"column":0},"end":{"line":33,"column":32}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":4,"column":23},"end":{"line":4,"column":24}},"loc":{"start":{"line":4,"column":49},"end":{"line":31,"column":1}},"line":4}},"branchMap":{"0":{"loc":{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},"type":"if","locations":[{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},{"start":{},"end":{}}],"line":8},"1":{"loc":{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},"type":"if","locations":[{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},{"start":{},"end":{}}],"line":16}},"s":{"0":1,"1":1,"2":1,"3":9,"4":9,"5":9,"6":1,"7":8,"8":7,"9":7,"10":0,"11":7,"12":7,"13":1,"14":1,"15":1},"f":{"0":9},"b":{"0":[1,8],"1":[0,7]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"5d1e4c9847324fb3bbc0718d5578aad6ca86b28f"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\favorite.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\favorite.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":0},"end":{"line":49,"column":2}},"2":{"start":{"line":12,"column":6},"end":{"line":12,"column":64}},"3":{"start":{"line":13,"column":6},"end":{"line":13,"column":80}},"4":{"start":{"line":16,"column":2},"end":{"line":47,"column":4}},"5":{"start":{"line":48,"column":2},"end":{"line":48,"column":18}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":17},"end":{"line":3,"column":18}},"loc":{"start":{"line":3,"column":43},"end":{"line":49,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":4},"end":{"line":10,"column":5}},"loc":{"start":{"line":10,"column":29},"end":{"line":14,"column":5}},"line":10}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1},"f":{"0":1,"1":1},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"a46f0a38c4e9fb02a98da2a78e922bcd91492325"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\index.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\index.js","statementMap":{"0":{"start":{"line":3,"column":11},"end":{"line":3,"column":24}},"1":{"start":{"line":4,"column":13},"end":{"line":4,"column":28}},"2":{"start":{"line":5,"column":18},"end":{"line":5,"column":38}},"3":{"start":{"line":6,"column":16},"end":{"line":6,"column":34}},"4":{"start":{"line":7,"column":17},"end":{"line":7,"column":42}},"5":{"start":{"line":8,"column":12},"end":{"line":8,"column":49}},"6":{"start":{"line":9,"column":15},"end":{"line":9,"column":65}},"7":{"start":{"line":10,"column":11},"end":{"line":10,"column":13}},"8":{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},"9":{"start":{"line":14,"column":2},"end":{"line":14,"column":74}},"10":{"start":{"line":16,"column":2},"end":{"line":16,"column":87}},"11":{"start":{"line":19,"column":0},"end":{"line":32,"column":5}},"12":{"start":{"line":22,"column":4},"end":{"line":27,"column":6}},"13":{"start":{"line":30,"column":18},"end":{"line":30,"column":85}},"14":{"start":{"line":31,"column":4},"end":{"line":31,"column":27}},"15":{"start":{"line":34,"column":0},"end":{"line":38,"column":3}},"16":{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},"17":{"start":{"line":36,"column":4},"end":{"line":36,"column":32}},"18":{"start":{"line":40,"column":0},"end":{"line":40,"column":25}},"19":{"start":{"line":41,"column":0},"end":{"line":41,"column":25}},"20":{"start":{"line":43,"column":0},"end":{"line":43,"column":20}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":21,"column":10},"end":{"line":21,"column":11}},"loc":{"start":{"line":21,"column":18},"end":{"line":28,"column":3}},"line":21},"1":{"name":"(anonymous_1)","decl":{"start":{"line":29,"column":11},"end":{"line":29,"column":12}},"loc":{"start":{"line":29,"column":19},"end":{"line":32,"column":3}},"line":29},"2":{"name":"(anonymous_2)","decl":{"start":{"line":34,"column":24},"end":{"line":34,"column":25}},"loc":{"start":{"line":34,"column":37},"end":{"line":38,"column":1}},"line":34}},"branchMap":{"0":{"loc":{"start":{"line":8,"column":12},"end":{"line":8,"column":49}},"type":"binary-expr","locations":[{"start":{"line":8,"column":12},"end":{"line":8,"column":32}},{"start":{"line":8,"column":36},"end":{"line":8,"column":49}}],"line":8},"1":{"loc":{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},"type":"if","locations":[{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},{"start":{"line":15,"column":7},"end":{"line":17,"column":1}}],"line":13},"2":{"loc":{"start":{"line":23,"column":6},"end":{"line":26,"column":37}},"type":"binary-expr","locations":[{"start":{"line":23,"column":6},"end":{"line":23,"column":29}},{"start":{"line":24,"column":6},"end":{"line":24,"column":23}},{"start":{"line":25,"column":6},"end":{"line":25,"column":30}},{"start":{"line":26,"column":6},"end":{"line":26,"column":37}}],"line":23},"3":{"loc":{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},"type":"if","locations":[{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},{"start":{},"end":{}}],"line":35}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":0,"10":1,"11":1,"12":4,"13":3,"14":3,"15":1,"16":3,"17":3,"18":1,"19":1,"20":1},"f":{"0":4,"1":3,"2":3},"b":{"0":[1,0],"1":[0,1],"2":[4,4,3,3],"3":[3,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"e8d39eb6f77ca39eed5252a0ef5942b3889b988e"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\user.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\user.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":25},"end":{"line":3,"column":53}},"2":{"start":{"line":5,"column":0},"end":{"line":71,"column":2}},"3":{"start":{"line":14,"column":6},"end":{"line":17,"column":9}},"4":{"start":{"line":20,"column":2},"end":{"line":69,"column":4}},"5":{"start":{"line":63,"column":10},"end":{"line":63,"column":54}},"6":{"start":{"line":70,"column":2},"end":{"line":70,"column":14}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":17},"end":{"line":5,"column":18}},"loc":{"start":{"line":5,"column":43},"end":{"line":71,"column":1}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":12,"column":4},"end":{"line":12,"column":5}},"loc":{"start":{"line":12,"column":29},"end":{"line":18,"column":5}},"line":12},"2":{"name":"(anonymous_2)","decl":{"start":{"line":62,"column":22},"end":{"line":62,"column":23}},"loc":{"start":{"line":62,"column":32},"end":{"line":64,"column":9}},"line":62}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":3,"6":1},"f":{"0":1,"1":1,"2":3},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d6cc21ed517c0c2dd5d8da5464a276b765e1e5bd"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\xiaomidevice.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\xiaomidevice.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":0},"end":{"line":43,"column":2}},"2":{"start":{"line":12,"column":6},"end":{"line":15,"column":9}},"3":{"start":{"line":18,"column":2},"end":{"line":41,"column":4}},"4":{"start":{"line":42,"column":2},"end":{"line":42,"column":22}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":17},"end":{"line":3,"column":18}},"loc":{"start":{"line":3,"column":43},"end":{"line":43,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":4},"end":{"line":10,"column":5}},"loc":{"start":{"line":10,"column":29},"end":{"line":16,"column":5}},"line":10}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":1,"4":1},"f":{"0":1,"1":1},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"3d210710bceff5c6be55260373affab9d6228f0d"} -,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\routes\\testing.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\routes\\testing.js","statementMap":{"0":{"start":{"line":1,"column":16},"end":{"line":1,"column":34}},"1":{"start":{"line":2,"column":15},"end":{"line":2,"column":31}},"2":{"start":{"line":5,"column":0},"end":{"line":7,"column":3}},"3":{"start":{"line":6,"column":2},"end":{"line":6,"column":54}},"4":{"start":{"line":9,"column":0},"end":{"line":9,"column":24}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":22},"end":{"line":5,"column":23}},"loc":{"start":{"line":5,"column":36},"end":{"line":7,"column":1}},"line":5}},"branchMap":{},"s":{"0":1,"1":1,"2":1,"3":0,"4":1},"f":{"0":0},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"97208ba7b910d8e53a4dc377db1b8047b51b5c08"} +{"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\app.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\app.js","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},"1":{"start":{"line":2,"column":2},"end":{"line":2,"column":29}},"2":{"start":{"line":5,"column":16},"end":{"line":5,"column":34}},"3":{"start":{"line":6,"column":12},"end":{"line":6,"column":21}},"4":{"start":{"line":7,"column":13},"end":{"line":7,"column":37}},"5":{"start":{"line":8,"column":13},"end":{"line":8,"column":28}},"6":{"start":{"line":9,"column":23},"end":{"line":9,"column":62}},"7":{"start":{"line":10,"column":23},"end":{"line":10,"column":62}},"8":{"start":{"line":11,"column":19},"end":{"line":11,"column":54}},"9":{"start":{"line":12,"column":22},"end":{"line":12,"column":60}},"10":{"start":{"line":13,"column":22},"end":{"line":13,"column":49}},"11":{"start":{"line":14,"column":31},"end":{"line":14,"column":63}},"12":{"start":{"line":17,"column":0},"end":{"line":20,"column":1}},"13":{"start":{"line":18,"column":2},"end":{"line":18,"column":75}},"14":{"start":{"line":19,"column":2},"end":{"line":19,"column":18}},"15":{"start":{"line":22,"column":14},"end":{"line":22,"column":64}},"16":{"start":{"line":25,"column":2},"end":{"line":47,"column":3}},"17":{"start":{"line":26,"column":18},"end":{"line":26,"column":73}},"18":{"start":{"line":29,"column":27},"end":{"line":39,"column":5}},"19":{"start":{"line":41,"column":19},"end":{"line":41,"column":62}},"20":{"start":{"line":42,"column":21},"end":{"line":42,"column":42}},"21":{"start":{"line":43,"column":4},"end":{"line":43,"column":27}},"22":{"start":{"line":45,"column":4},"end":{"line":45,"column":48}},"23":{"start":{"line":46,"column":4},"end":{"line":46,"column":16}},"24":{"start":{"line":51,"column":0},"end":{"line":51,"column":16}},"25":{"start":{"line":52,"column":0},"end":{"line":52,"column":24}},"26":{"start":{"line":53,"column":0},"end":{"line":53,"column":48}},"27":{"start":{"line":56,"column":0},"end":{"line":56,"column":35}},"28":{"start":{"line":59,"column":0},"end":{"line":59,"column":56}},"29":{"start":{"line":60,"column":0},"end":{"line":60,"column":60}},"30":{"start":{"line":63,"column":0},"end":{"line":75,"column":3}},"31":{"start":{"line":64,"column":2},"end":{"line":74,"column":3}},"32":{"start":{"line":65,"column":23},"end":{"line":65,"column":31}},"33":{"start":{"line":66,"column":4},"end":{"line":68,"column":5}},"34":{"start":{"line":67,"column":6},"end":{"line":67,"column":68}},"35":{"start":{"line":69,"column":21},"end":{"line":69,"column":48}},"36":{"start":{"line":70,"column":4},"end":{"line":70,"column":39}},"37":{"start":{"line":72,"column":4},"end":{"line":72,"column":47}},"38":{"start":{"line":73,"column":4},"end":{"line":73,"column":63}},"39":{"start":{"line":78,"column":0},"end":{"line":78,"column":47}},"40":{"start":{"line":79,"column":0},"end":{"line":79,"column":41}},"41":{"start":{"line":80,"column":0},"end":{"line":80,"column":54}},"42":{"start":{"line":83,"column":0},"end":{"line":83,"column":24}},"43":{"start":{"line":86,"column":0},"end":{"line":86,"column":46}},"44":{"start":{"line":87,"column":0},"end":{"line":87,"column":50}},"45":{"start":{"line":88,"column":0},"end":{"line":88,"column":52}},"46":{"start":{"line":89,"column":0},"end":{"line":89,"column":66}},"47":{"start":{"line":90,"column":0},"end":{"line":90,"column":47}},"48":{"start":{"line":91,"column":0},"end":{"line":91,"column":73}},"49":{"start":{"line":94,"column":0},"end":{"line":97,"column":3}},"50":{"start":{"line":95,"column":2},"end":{"line":95,"column":38}},"51":{"start":{"line":96,"column":2},"end":{"line":96,"column":61}},"52":{"start":{"line":114,"column":0},"end":{"line":114,"column":21}}},"fnMap":{"0":{"name":"geminiApi","decl":{"start":{"line":24,"column":15},"end":{"line":24,"column":24}},"loc":{"start":{"line":24,"column":37},"end":{"line":48,"column":1}},"line":24},"1":{"name":"(anonymous_1)","decl":{"start":{"line":63,"column":16},"end":{"line":63,"column":17}},"loc":{"start":{"line":63,"column":36},"end":{"line":75,"column":1}},"line":63},"2":{"name":"(anonymous_2)","decl":{"start":{"line":94,"column":8},"end":{"line":94,"column":9}},"loc":{"start":{"line":94,"column":33},"end":{"line":97,"column":1}},"line":94}},"branchMap":{"0":{"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},"type":"if","locations":[{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},{"start":{},"end":{}}],"line":1},"1":{"loc":{"start":{"line":7,"column":13},"end":{"line":7,"column":37}},"type":"binary-expr","locations":[{"start":{"line":7,"column":13},"end":{"line":7,"column":29}},{"start":{"line":7,"column":33},"end":{"line":7,"column":37}}],"line":7},"2":{"loc":{"start":{"line":17,"column":0},"end":{"line":20,"column":1}},"type":"if","locations":[{"start":{"line":17,"column":0},"end":{"line":20,"column":1}},{"start":{},"end":{}}],"line":17},"3":{"loc":{"start":{"line":66,"column":4},"end":{"line":68,"column":5}},"type":"if","locations":[{"start":{"line":66,"column":4},"end":{"line":68,"column":5}},{"start":{},"end":{}}],"line":66}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2,"6":2,"7":2,"8":2,"9":2,"10":2,"11":2,"12":2,"13":0,"14":0,"15":2,"16":2,"17":2,"18":2,"19":2,"20":0,"21":0,"22":2,"23":2,"24":2,"25":2,"26":2,"27":2,"28":2,"29":2,"30":2,"31":3,"32":3,"33":3,"34":1,"35":2,"36":0,"37":2,"38":2,"39":2,"40":2,"41":2,"42":2,"43":2,"44":2,"45":2,"46":2,"47":2,"48":2,"49":2,"50":0,"51":0,"52":2},"f":{"0":2,"1":3,"2":0},"b":{"0":[2,0],"1":[2,2],"2":[0,2],"3":[1,2]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"7e3d108e96df263bc7369faea4f9b1bb985ed883"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\Controller.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\Controller.js","statementMap":{"0":{"start":{"line":1,"column":41},"end":{"line":1,"column":61}},"1":{"start":{"line":6,"column":4},"end":{"line":31,"column":5}},"2":{"start":{"line":7,"column":22},"end":{"line":7,"column":50}},"3":{"start":{"line":10,"column":25},"end":{"line":10,"column":48}},"4":{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},"5":{"start":{"line":14,"column":31},"end":{"line":22,"column":10}},"6":{"start":{"line":15,"column":10},"end":{"line":21,"column":12}},"7":{"start":{"line":24,"column":8},"end":{"line":24,"column":52}},"8":{"start":{"line":28,"column":6},"end":{"line":28,"column":36}},"9":{"start":{"line":30,"column":6},"end":{"line":30,"column":65}},"10":{"start":{"line":36,"column":4},"end":{"line":65,"column":5}},"11":{"start":{"line":37,"column":21},"end":{"line":37,"column":31}},"12":{"start":{"line":39,"column":21},"end":{"line":39,"column":52}},"13":{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},"14":{"start":{"line":42,"column":8},"end":{"line":42,"column":75}},"15":{"start":{"line":46,"column":25},"end":{"line":46,"column":48}},"16":{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},"17":{"start":{"line":50,"column":30},"end":{"line":56,"column":9}},"18":{"start":{"line":58,"column":8},"end":{"line":58,"column":51}},"19":{"start":{"line":62,"column":6},"end":{"line":62,"column":35}},"20":{"start":{"line":64,"column":6},"end":{"line":64,"column":65}},"21":{"start":{"line":70,"column":4},"end":{"line":100,"column":5}},"22":{"start":{"line":71,"column":21},"end":{"line":71,"column":32}},"23":{"start":{"line":72,"column":33},"end":{"line":72,"column":43}},"24":{"start":{"line":75,"column":21},"end":{"line":75,"column":64}},"25":{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},"26":{"start":{"line":78,"column":8},"end":{"line":78,"column":75}},"27":{"start":{"line":82,"column":31},"end":{"line":84,"column":8}},"28":{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},"29":{"start":{"line":87,"column":8},"end":{"line":89,"column":72}},"30":{"start":{"line":93,"column":6},"end":{"line":93,"column":56}},"31":{"start":{"line":95,"column":6},"end":{"line":97,"column":69}},"32":{"start":{"line":99,"column":6},"end":{"line":99,"column":65}},"33":{"start":{"line":105,"column":4},"end":{"line":119,"column":5}},"34":{"start":{"line":106,"column":21},"end":{"line":106,"column":32}},"35":{"start":{"line":108,"column":24},"end":{"line":111,"column":8}},"36":{"start":{"line":113,"column":30},"end":{"line":113,"column":70}},"37":{"start":{"line":113,"column":53},"end":{"line":113,"column":69}},"38":{"start":{"line":115,"column":6},"end":{"line":115,"column":44}},"39":{"start":{"line":117,"column":6},"end":{"line":117,"column":27}},"40":{"start":{"line":118,"column":6},"end":{"line":118,"column":65}},"41":{"start":{"line":124,"column":4},"end":{"line":145,"column":5}},"42":{"start":{"line":125,"column":21},"end":{"line":125,"column":32}},"43":{"start":{"line":126,"column":33},"end":{"line":126,"column":43}},"44":{"start":{"line":129,"column":23},"end":{"line":131,"column":8}},"45":{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},"46":{"start":{"line":134,"column":8},"end":{"line":136,"column":72}},"47":{"start":{"line":140,"column":6},"end":{"line":140,"column":31}},"48":{"start":{"line":142,"column":6},"end":{"line":142,"column":80}},"49":{"start":{"line":144,"column":6},"end":{"line":144,"column":65}},"50":{"start":{"line":149,"column":0},"end":{"line":149,"column":28}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":2},"end":{"line":5,"column":3}},"loc":{"start":{"line":5,"column":39},"end":{"line":32,"column":3}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":14,"column":43},"end":{"line":14,"column":44}},"loc":{"start":{"line":14,"column":55},"end":{"line":22,"column":9}},"line":14},"2":{"name":"(anonymous_2)","decl":{"start":{"line":35,"column":2},"end":{"line":35,"column":3}},"loc":{"start":{"line":35,"column":39},"end":{"line":66,"column":3}},"line":35},"3":{"name":"(anonymous_3)","decl":{"start":{"line":69,"column":2},"end":{"line":69,"column":3}},"loc":{"start":{"line":69,"column":40},"end":{"line":101,"column":3}},"line":69},"4":{"name":"(anonymous_4)","decl":{"start":{"line":104,"column":2},"end":{"line":104,"column":3}},"loc":{"start":{"line":104,"column":38},"end":{"line":120,"column":3}},"line":104},"5":{"name":"(anonymous_5)","decl":{"start":{"line":113,"column":44},"end":{"line":113,"column":45}},"loc":{"start":{"line":113,"column":53},"end":{"line":113,"column":69}},"line":113},"6":{"name":"(anonymous_6)","decl":{"start":{"line":123,"column":2},"end":{"line":123,"column":3}},"loc":{"start":{"line":123,"column":45},"end":{"line":146,"column":3}},"line":123}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":25},"end":{"line":10,"column":48}},"type":"cond-expr","locations":[{"start":{"line":10,"column":36},"end":{"line":10,"column":40}},{"start":{"line":10,"column":43},"end":{"line":10,"column":48}}],"line":10},"1":{"loc":{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},"type":"if","locations":[{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},{"start":{},"end":{}}],"line":13},"2":{"loc":{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},"type":"if","locations":[{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},{"start":{},"end":{}}],"line":41},"3":{"loc":{"start":{"line":46,"column":25},"end":{"line":46,"column":48}},"type":"cond-expr","locations":[{"start":{"line":46,"column":36},"end":{"line":46,"column":40}},{"start":{"line":46,"column":43},"end":{"line":46,"column":48}}],"line":46},"4":{"loc":{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},"type":"if","locations":[{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},{"start":{},"end":{}}],"line":49},"5":{"loc":{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},"type":"if","locations":[{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},{"start":{},"end":{}}],"line":77},"6":{"loc":{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},"type":"if","locations":[{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},{"start":{},"end":{}}],"line":86},"7":{"loc":{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},"type":"if","locations":[{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},{"start":{},"end":{}}],"line":133}},"s":{"0":2,"1":3,"2":3,"3":2,"4":2,"5":1,"6":1,"7":1,"8":1,"9":1,"10":4,"11":4,"12":4,"13":3,"14":1,"15":2,"16":2,"17":1,"18":1,"19":1,"20":1,"21":4,"22":4,"23":4,"24":4,"25":3,"26":1,"27":2,"28":2,"29":1,"30":1,"31":1,"32":1,"33":2,"34":2,"35":2,"36":1,"37":1,"38":1,"39":1,"40":1,"41":3,"42":3,"43":3,"44":3,"45":2,"46":1,"47":1,"48":1,"49":1,"50":2},"f":{"0":3,"1":1,"2":4,"3":4,"4":2,"5":1,"6":3},"b":{"0":[1,1],"1":[1,1],"2":[1,2],"3":[1,1],"4":[1,1],"5":[1,2],"6":[1,1],"7":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"737b92e119bcfa4ab8431d4a132d7beeb4e23ad7"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\PubController.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\PubController.js","statementMap":{"0":{"start":{"line":4,"column":4},"end":{"line":4,"column":24}},"1":{"start":{"line":8,"column":4},"end":{"line":60,"column":5}},"2":{"start":{"line":9,"column":51},"end":{"line":9,"column":60}},"3":{"start":{"line":12,"column":25},"end":{"line":12,"column":27}},"4":{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},"5":{"start":{"line":16,"column":8},"end":{"line":21,"column":10}},"6":{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},"7":{"start":{"line":26,"column":8},"end":{"line":29,"column":10}},"8":{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},"9":{"start":{"line":30,"column":22},"end":{"line":30,"column":66}},"10":{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},"11":{"start":{"line":31,"column":22},"end":{"line":31,"column":66}},"12":{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},"13":{"start":{"line":36,"column":31},"end":{"line":36,"column":46}},"14":{"start":{"line":37,"column":28},"end":{"line":37,"column":52}},"15":{"start":{"line":38,"column":28},"end":{"line":38,"column":43}},"16":{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},"17":{"start":{"line":41,"column":10},"end":{"line":41,"column":62}},"18":{"start":{"line":45,"column":22},"end":{"line":45,"column":62}},"19":{"start":{"line":48,"column":29},"end":{"line":54,"column":9}},"20":{"start":{"line":48,"column":54},"end":{"line":54,"column":7}},"21":{"start":{"line":56,"column":6},"end":{"line":56,"column":43}},"22":{"start":{"line":58,"column":6},"end":{"line":58,"column":54}},"23":{"start":{"line":59,"column":6},"end":{"line":59,"column":65}},"24":{"start":{"line":65,"column":4},"end":{"line":78,"column":5}},"25":{"start":{"line":66,"column":21},"end":{"line":66,"column":31}},"26":{"start":{"line":68,"column":21},"end":{"line":68,"column":52}},"27":{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},"28":{"start":{"line":71,"column":8},"end":{"line":71,"column":75}},"29":{"start":{"line":75,"column":6},"end":{"line":75,"column":35}},"30":{"start":{"line":77,"column":6},"end":{"line":77,"column":65}},"31":{"start":{"line":82,"column":0},"end":{"line":82,"column":31}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":7,"column":2},"end":{"line":7,"column":3}},"loc":{"start":{"line":7,"column":39},"end":{"line":61,"column":3}},"line":7},"1":{"name":"(anonymous_1)","decl":{"start":{"line":48,"column":41},"end":{"line":48,"column":42}},"loc":{"start":{"line":48,"column":54},"end":{"line":54,"column":7}},"line":48},"2":{"name":"(anonymous_2)","decl":{"start":{"line":64,"column":2},"end":{"line":64,"column":3}},"loc":{"start":{"line":64,"column":39},"end":{"line":79,"column":3}},"line":64}},"branchMap":{"0":{"loc":{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},"type":"if","locations":[{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},{"start":{},"end":{}}],"line":15},"1":{"loc":{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},"type":"if","locations":[{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},{"start":{},"end":{}}],"line":25},"2":{"loc":{"start":{"line":25,"column":10},"end":{"line":25,"column":30}},"type":"binary-expr","locations":[{"start":{"line":25,"column":10},"end":{"line":25,"column":18}},{"start":{"line":25,"column":22},"end":{"line":25,"column":30}}],"line":25},"3":{"loc":{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},"type":"if","locations":[{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},{"start":{},"end":{}}],"line":30},"4":{"loc":{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},"type":"if","locations":[{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},{"start":{},"end":{}}],"line":31},"5":{"loc":{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},"type":"if","locations":[{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},{"start":{},"end":{}}],"line":35},"6":{"loc":{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},"type":"if","locations":[{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},{"start":{},"end":{}}],"line":40},"7":{"loc":{"start":{"line":40,"column":12},"end":{"line":40,"column":70}},"type":"binary-expr","locations":[{"start":{"line":40,"column":12},"end":{"line":40,"column":39}},{"start":{"line":40,"column":43},"end":{"line":40,"column":70}}],"line":40},"8":{"loc":{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},"type":"if","locations":[{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},{"start":{},"end":{}}],"line":70}},"s":{"0":2,"1":7,"2":7,"3":7,"4":7,"5":1,"6":7,"7":3,"8":3,"9":2,"10":3,"11":2,"12":7,"13":2,"14":2,"15":2,"16":2,"17":1,"18":7,"19":6,"20":6,"21":6,"22":1,"23":1,"24":3,"25":3,"26":3,"27":2,"28":1,"29":1,"30":1,"31":2},"f":{"0":7,"1":6,"2":3},"b":{"0":[1,6],"1":[3,4],"2":[7,5],"3":[2,1],"4":[2,1],"5":[2,5],"6":[1,1],"7":[2,1],"8":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"50d00d019a8a66eca334a967de982b42c35cca4d"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\UserController.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\UserController.js","statementMap":{"0":{"start":{"line":1,"column":17},"end":{"line":1,"column":37}},"1":{"start":{"line":2,"column":28},"end":{"line":2,"column":56}},"2":{"start":{"line":3,"column":24},"end":{"line":3,"column":49}},"3":{"start":{"line":4,"column":25},"end":{"line":4,"column":55}},"4":{"start":{"line":5,"column":15},"end":{"line":5,"column":33}},"5":{"start":{"line":9,"column":4},"end":{"line":45,"column":5}},"6":{"start":{"line":10,"column":30},"end":{"line":10,"column":38}},"7":{"start":{"line":11,"column":21},"end":{"line":14,"column":8}},"8":{"start":{"line":15,"column":22},"end":{"line":15,"column":41}},"9":{"start":{"line":17,"column":17},"end":{"line":19,"column":8}},"10":{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},"11":{"start":{"line":22,"column":8},"end":{"line":26,"column":11}},"12":{"start":{"line":29,"column":27},"end":{"line":33,"column":7}},"13":{"start":{"line":35,"column":20},"end":{"line":35,"column":45}},"14":{"start":{"line":36,"column":6},"end":{"line":41,"column":9}},"15":{"start":{"line":43,"column":6},"end":{"line":43,"column":25}},"16":{"start":{"line":44,"column":6},"end":{"line":44,"column":65}},"17":{"start":{"line":49,"column":4},"end":{"line":72,"column":5}},"18":{"start":{"line":50,"column":44},"end":{"line":50,"column":52}},"19":{"start":{"line":52,"column":22},"end":{"line":56,"column":8}},"20":{"start":{"line":58,"column":6},"end":{"line":62,"column":9}},"21":{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},"22":{"start":{"line":68,"column":8},"end":{"line":68,"column":67}},"23":{"start":{"line":70,"column":8},"end":{"line":70,"column":67}},"24":{"start":{"line":76,"column":4},"end":{"line":116,"column":5}},"25":{"start":{"line":77,"column":34},"end":{"line":77,"column":42}},"26":{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},"27":{"start":{"line":80,"column":8},"end":{"line":82,"column":64}},"28":{"start":{"line":85,"column":19},"end":{"line":87,"column":8}},"29":{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},"30":{"start":{"line":90,"column":8},"end":{"line":90,"column":67}},"31":{"start":{"line":93,"column":30},"end":{"line":93,"column":70}},"32":{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},"33":{"start":{"line":96,"column":8},"end":{"line":96,"column":69}},"34":{"start":{"line":99,"column":22},"end":{"line":103,"column":7}},"35":{"start":{"line":105,"column":20},"end":{"line":105,"column":40}},"36":{"start":{"line":107,"column":6},"end":{"line":112,"column":9}},"37":{"start":{"line":114,"column":6},"end":{"line":114,"column":27}},"38":{"start":{"line":115,"column":6},"end":{"line":115,"column":65}},"39":{"start":{"line":120,"column":4},"end":{"line":161,"column":5}},"40":{"start":{"line":121,"column":21},"end":{"line":121,"column":32}},"41":{"start":{"line":122,"column":34},"end":{"line":122,"column":42}},"42":{"start":{"line":124,"column":19},"end":{"line":124,"column":46}},"43":{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},"44":{"start":{"line":127,"column":8},"end":{"line":127,"column":73}},"45":{"start":{"line":130,"column":27},"end":{"line":130,"column":29}},"46":{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},"47":{"start":{"line":132,"column":20},"end":{"line":132,"column":53}},"48":{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},"49":{"start":{"line":133,"column":17},"end":{"line":133,"column":44}},"50":{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},"51":{"start":{"line":136,"column":8},"end":{"line":138,"column":61}},"52":{"start":{"line":141,"column":6},"end":{"line":141,"column":38}},"53":{"start":{"line":143,"column":6},"end":{"line":150,"column":9}},"54":{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},"55":{"start":{"line":156,"column":8},"end":{"line":156,"column":67}},"56":{"start":{"line":158,"column":8},"end":{"line":158,"column":29}},"57":{"start":{"line":159,"column":8},"end":{"line":159,"column":67}},"58":{"start":{"line":165,"column":0},"end":{"line":165,"column":32}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":8,"column":2},"end":{"line":8,"column":3}},"loc":{"start":{"line":8,"column":37},"end":{"line":46,"column":3}},"line":8},"1":{"name":"(anonymous_1)","decl":{"start":{"line":48,"column":2},"end":{"line":48,"column":3}},"loc":{"start":{"line":48,"column":34},"end":{"line":73,"column":3}},"line":48},"2":{"name":"(anonymous_2)","decl":{"start":{"line":75,"column":2},"end":{"line":75,"column":3}},"loc":{"start":{"line":75,"column":31},"end":{"line":117,"column":3}},"line":75},"3":{"name":"(anonymous_3)","decl":{"start":{"line":119,"column":2},"end":{"line":119,"column":3}},"loc":{"start":{"line":119,"column":36},"end":{"line":162,"column":3}},"line":119}},"branchMap":{"0":{"loc":{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},"type":"if","locations":[{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},{"start":{},"end":{}}],"line":21},"1":{"loc":{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},"type":"if","locations":[{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},{"start":{"line":69,"column":13},"end":{"line":71,"column":7}}],"line":64},"2":{"loc":{"start":{"line":65,"column":8},"end":{"line":66,"column":55}},"type":"binary-expr","locations":[{"start":{"line":65,"column":8},"end":{"line":65,"column":49}},{"start":{"line":66,"column":8},"end":{"line":66,"column":55}}],"line":65},"3":{"loc":{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},"type":"if","locations":[{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},{"start":{},"end":{}}],"line":79},"4":{"loc":{"start":{"line":79,"column":10},"end":{"line":79,"column":29}},"type":"binary-expr","locations":[{"start":{"line":79,"column":10},"end":{"line":79,"column":16}},{"start":{"line":79,"column":20},"end":{"line":79,"column":29}}],"line":79},"5":{"loc":{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},"type":"if","locations":[{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},{"start":{},"end":{}}],"line":89},"6":{"loc":{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},"type":"if","locations":[{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},{"start":{},"end":{}}],"line":95},"7":{"loc":{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},"type":"if","locations":[{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},{"start":{},"end":{}}],"line":126},"8":{"loc":{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},"type":"if","locations":[{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},{"start":{},"end":{}}],"line":132},"9":{"loc":{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},"type":"if","locations":[{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},{"start":{},"end":{}}],"line":133},"10":{"loc":{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},"type":"if","locations":[{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},{"start":{},"end":{}}],"line":135},"11":{"loc":{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},"type":"if","locations":[{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},{"start":{"line":157,"column":13},"end":{"line":160,"column":7}}],"line":152},"12":{"loc":{"start":{"line":153,"column":8},"end":{"line":154,"column":55}},"type":"binary-expr","locations":[{"start":{"line":153,"column":8},"end":{"line":153,"column":49}},{"start":{"line":154,"column":8},"end":{"line":154,"column":55}}],"line":153}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":1,"6":1,"7":1,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":1,"16":1,"17":4,"18":4,"19":4,"20":1,"21":3,"22":2,"23":1,"24":5,"25":5,"26":5,"27":1,"28":4,"29":3,"30":1,"31":2,"32":2,"33":2,"34":0,"35":0,"36":0,"37":1,"38":1,"39":4,"40":4,"41":4,"42":4,"43":4,"44":0,"45":4,"46":4,"47":1,"48":4,"49":2,"50":4,"51":2,"52":2,"53":1,"54":1,"55":1,"56":0,"57":0,"58":2},"f":{"0":1,"1":4,"2":5,"3":4},"b":{"0":[0,0],"1":[2,1],"2":[3,2],"3":[1,4],"4":[5,5],"5":[1,2],"6":[2,0],"7":[0,4],"8":[1,3],"9":[2,2],"10":[2,2],"11":[1,0],"12":[1,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"1cc80a39bdb2f0e086371f69dc5963f6c4e5b9ee"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\bcrypt.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\bcrypt.js","statementMap":{"0":{"start":{"line":1,"column":15},"end":{"line":1,"column":34}},"1":{"start":{"line":3,"column":21},"end":{"line":5,"column":1}},"2":{"start":{"line":4,"column":2},"end":{"line":4,"column":39}},"3":{"start":{"line":7,"column":24},"end":{"line":9,"column":1}},"4":{"start":{"line":8,"column":2},"end":{"line":8,"column":54}},"5":{"start":{"line":11,"column":0},"end":{"line":14,"column":2}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":21},"end":{"line":3,"column":22}},"loc":{"start":{"line":3,"column":35},"end":{"line":5,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":24},"end":{"line":7,"column":25}},"loc":{"start":{"line":7,"column":54},"end":{"line":9,"column":1}},"line":7}},"branchMap":{},"s":{"0":2,"1":2,"2":4,"3":2,"4":2,"5":2},"f":{"0":4,"1":2},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c74a0375d91f19b5bcb02453f3e0a1ea35f7a31a"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\jwt.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\jwt.js","statementMap":{"0":{"start":{"line":1,"column":12},"end":{"line":1,"column":35}},"1":{"start":{"line":2,"column":19},"end":{"line":2,"column":41}},"2":{"start":{"line":4,"column":20},"end":{"line":6,"column":1}},"3":{"start":{"line":5,"column":2},"end":{"line":5,"column":39}},"4":{"start":{"line":8,"column":20},"end":{"line":10,"column":1}},"5":{"start":{"line":9,"column":2},"end":{"line":9,"column":39}},"6":{"start":{"line":12,"column":0},"end":{"line":15,"column":2}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":4,"column":20},"end":{"line":4,"column":21}},"loc":{"start":{"line":4,"column":33},"end":{"line":6,"column":1}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":8,"column":20},"end":{"line":8,"column":21}},"loc":{"start":{"line":8,"column":31},"end":{"line":10,"column":1}},"line":8}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":23,"6":2},"f":{"0":2,"1":23},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"70740eafddc547dd656de5caf7cef5487e23bfce"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\middlewares\\authentication.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\middlewares\\authentication.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":1,"column":49}},"1":{"start":{"line":2,"column":17},"end":{"line":2,"column":37}},"2":{"start":{"line":4,"column":23},"end":{"line":31,"column":1}},"3":{"start":{"line":5,"column":2},"end":{"line":30,"column":3}},"4":{"start":{"line":6,"column":29},"end":{"line":6,"column":40}},"5":{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},"6":{"start":{"line":9,"column":6},"end":{"line":9,"column":20}},"7":{"start":{"line":12,"column":20},"end":{"line":12,"column":45}},"8":{"start":{"line":14,"column":17},"end":{"line":14,"column":48}},"9":{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},"10":{"start":{"line":17,"column":6},"end":{"line":17,"column":65}},"11":{"start":{"line":20,"column":4},"end":{"line":24,"column":6}},"12":{"start":{"line":26,"column":4},"end":{"line":26,"column":11}},"13":{"start":{"line":28,"column":4},"end":{"line":28,"column":25}},"14":{"start":{"line":29,"column":4},"end":{"line":29,"column":62}},"15":{"start":{"line":33,"column":0},"end":{"line":33,"column":32}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":4,"column":23},"end":{"line":4,"column":24}},"loc":{"start":{"line":4,"column":49},"end":{"line":31,"column":1}},"line":4}},"branchMap":{"0":{"loc":{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},"type":"if","locations":[{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},{"start":{},"end":{}}],"line":8},"1":{"loc":{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},"type":"if","locations":[{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},{"start":{},"end":{}}],"line":16}},"s":{"0":2,"1":2,"2":2,"3":25,"4":25,"5":25,"6":2,"7":23,"8":22,"9":20,"10":2,"11":18,"12":18,"13":3,"14":3,"15":2},"f":{"0":25},"b":{"0":[2,23],"1":[2,18]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"5d1e4c9847324fb3bbc0718d5578aad6ca86b28f"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\favorite.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\favorite.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":0},"end":{"line":49,"column":2}},"2":{"start":{"line":12,"column":6},"end":{"line":12,"column":64}},"3":{"start":{"line":13,"column":6},"end":{"line":13,"column":80}},"4":{"start":{"line":16,"column":2},"end":{"line":47,"column":4}},"5":{"start":{"line":48,"column":2},"end":{"line":48,"column":18}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":17},"end":{"line":3,"column":18}},"loc":{"start":{"line":3,"column":43},"end":{"line":49,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":4},"end":{"line":10,"column":5}},"loc":{"start":{"line":10,"column":29},"end":{"line":14,"column":5}},"line":10}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2},"f":{"0":2,"1":2},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"a46f0a38c4e9fb02a98da2a78e922bcd91492325"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\index.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\index.js","statementMap":{"0":{"start":{"line":3,"column":11},"end":{"line":3,"column":24}},"1":{"start":{"line":4,"column":13},"end":{"line":4,"column":28}},"2":{"start":{"line":5,"column":18},"end":{"line":5,"column":38}},"3":{"start":{"line":6,"column":16},"end":{"line":6,"column":34}},"4":{"start":{"line":7,"column":17},"end":{"line":7,"column":42}},"5":{"start":{"line":8,"column":12},"end":{"line":8,"column":49}},"6":{"start":{"line":9,"column":15},"end":{"line":9,"column":65}},"7":{"start":{"line":10,"column":11},"end":{"line":10,"column":13}},"8":{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},"9":{"start":{"line":14,"column":2},"end":{"line":14,"column":74}},"10":{"start":{"line":16,"column":2},"end":{"line":16,"column":87}},"11":{"start":{"line":19,"column":0},"end":{"line":32,"column":5}},"12":{"start":{"line":22,"column":4},"end":{"line":27,"column":6}},"13":{"start":{"line":30,"column":18},"end":{"line":30,"column":85}},"14":{"start":{"line":31,"column":4},"end":{"line":31,"column":27}},"15":{"start":{"line":34,"column":0},"end":{"line":38,"column":3}},"16":{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},"17":{"start":{"line":36,"column":4},"end":{"line":36,"column":32}},"18":{"start":{"line":40,"column":0},"end":{"line":40,"column":25}},"19":{"start":{"line":41,"column":0},"end":{"line":41,"column":25}},"20":{"start":{"line":43,"column":0},"end":{"line":43,"column":20}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":21,"column":10},"end":{"line":21,"column":11}},"loc":{"start":{"line":21,"column":18},"end":{"line":28,"column":3}},"line":21},"1":{"name":"(anonymous_1)","decl":{"start":{"line":29,"column":11},"end":{"line":29,"column":12}},"loc":{"start":{"line":29,"column":19},"end":{"line":32,"column":3}},"line":29},"2":{"name":"(anonymous_2)","decl":{"start":{"line":34,"column":24},"end":{"line":34,"column":25}},"loc":{"start":{"line":34,"column":37},"end":{"line":38,"column":1}},"line":34}},"branchMap":{"0":{"loc":{"start":{"line":8,"column":12},"end":{"line":8,"column":49}},"type":"binary-expr","locations":[{"start":{"line":8,"column":12},"end":{"line":8,"column":32}},{"start":{"line":8,"column":36},"end":{"line":8,"column":49}}],"line":8},"1":{"loc":{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},"type":"if","locations":[{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},{"start":{"line":15,"column":7},"end":{"line":17,"column":1}}],"line":13},"2":{"loc":{"start":{"line":23,"column":6},"end":{"line":26,"column":37}},"type":"binary-expr","locations":[{"start":{"line":23,"column":6},"end":{"line":23,"column":29}},{"start":{"line":24,"column":6},"end":{"line":24,"column":23}},{"start":{"line":25,"column":6},"end":{"line":25,"column":30}},{"start":{"line":26,"column":6},"end":{"line":26,"column":37}}],"line":23},"3":{"loc":{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},"type":"if","locations":[{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},{"start":{},"end":{}}],"line":35}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2,"6":2,"7":2,"8":2,"9":0,"10":2,"11":2,"12":8,"13":6,"14":6,"15":2,"16":6,"17":6,"18":2,"19":2,"20":2},"f":{"0":8,"1":6,"2":6},"b":{"0":[2,0],"1":[0,2],"2":[8,8,6,6],"3":[6,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"e8d39eb6f77ca39eed5252a0ef5942b3889b988e"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\user.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\user.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":25},"end":{"line":3,"column":53}},"2":{"start":{"line":5,"column":0},"end":{"line":71,"column":2}},"3":{"start":{"line":14,"column":6},"end":{"line":17,"column":9}},"4":{"start":{"line":20,"column":2},"end":{"line":69,"column":4}},"5":{"start":{"line":63,"column":10},"end":{"line":63,"column":54}},"6":{"start":{"line":70,"column":2},"end":{"line":70,"column":14}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":17},"end":{"line":5,"column":18}},"loc":{"start":{"line":5,"column":43},"end":{"line":71,"column":1}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":12,"column":4},"end":{"line":12,"column":5}},"loc":{"start":{"line":12,"column":29},"end":{"line":18,"column":5}},"line":12},"2":{"name":"(anonymous_2)","decl":{"start":{"line":62,"column":22},"end":{"line":62,"column":23}},"loc":{"start":{"line":62,"column":32},"end":{"line":64,"column":9}},"line":62}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":3,"6":2},"f":{"0":2,"1":2,"2":3},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d6cc21ed517c0c2dd5d8da5464a276b765e1e5bd"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\xiaomidevice.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\xiaomidevice.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":0},"end":{"line":43,"column":2}},"2":{"start":{"line":12,"column":6},"end":{"line":15,"column":9}},"3":{"start":{"line":18,"column":2},"end":{"line":41,"column":4}},"4":{"start":{"line":42,"column":2},"end":{"line":42,"column":22}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":17},"end":{"line":3,"column":18}},"loc":{"start":{"line":3,"column":43},"end":{"line":43,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":4},"end":{"line":10,"column":5}},"loc":{"start":{"line":10,"column":29},"end":{"line":16,"column":5}},"line":10}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2},"f":{"0":2,"1":2},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"3d210710bceff5c6be55260373affab9d6228f0d"} +,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\routes\\testing.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\routes\\testing.js","statementMap":{"0":{"start":{"line":1,"column":16},"end":{"line":1,"column":34}},"1":{"start":{"line":2,"column":15},"end":{"line":2,"column":31}},"2":{"start":{"line":5,"column":0},"end":{"line":7,"column":3}},"3":{"start":{"line":6,"column":2},"end":{"line":6,"column":54}},"4":{"start":{"line":9,"column":0},"end":{"line":9,"column":24}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":22},"end":{"line":5,"column":23}},"loc":{"start":{"line":5,"column":36},"end":{"line":7,"column":1}},"line":5}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":1,"4":2},"f":{"0":1},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"97208ba7b910d8e53a4dc377db1b8047b51b5c08"} } diff --git a/server/coverage/lcov-report/index.html b/server/coverage/lcov-report/index.html index 994a3cd5..79da4012 100644 --- a/server/coverage/lcov-report/index.html +++ b/server/coverage/lcov-report/index.html @@ -23,30 +23,30 @@

All files

- 66.12% + 92.16% Statements - 164/248 + 247/268
- 41.02% + 86.58% Branches - 32/78 + 71/82
- 75% + 96.96% Functions - 24/32 + 32/33
- 67.35% + 91.98% Lines - 163/242 + 241/262
@@ -61,7 +61,7 @@

All files

-
+
@@ -80,32 +80,32 @@

All files

- - - + + - - - - - + + + + + - - + - - - - - - - - + + + + + + + + @@ -125,17 +125,17 @@

All files

- - - - - + + + + - - + + @@ -155,17 +155,17 @@

All files

- - - + + - - - - + + + + @@ -176,7 +176,7 @@

All files

server -
+
+
93.93%31/3386.79%46/53 75%3/450%1/293.93%31/336/866.66%2/386.79%46/53
server/controllers -
+
server/controllers +
44.36%63/14231.66%19/6057.14%8/1445.58%62/13690.84%129/14290%54/60100%14/1490.44%123/136
server/middlewares -
+
+
93.75%15/1675%3/4100%16/16100%4/4 100% 1/193.75%15/16100%16/16
server/routes -
+
+
80%4/5100%5/5 100% 0/00%0/180%4/5100%1/1100%5/5