From a79e094485f73bb1648758a92c0cac6d4c5ba76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Mon, 10 Mar 2025 15:05:02 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feature/=20dashboardClient=20controller,?= =?UTF-8?q?=20service,=20model=20=EC=83=9D=EC=84=B1,=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=EA=B8=B0=EB=8A=A5(apikey=EB=B0=9C=EA=B8=89,?= =?UTF-8?q?=20apikey=EC=99=80=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20has?= =?UTF-8?q?h=EC=B2=98=EB=A6=AC=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 532 ++++++++++++++++++++++++- package.json | 1 + src/controllers/dashboardController.ts | 11 + src/models/dashboardClientModel.ts | 22 + src/routes/dashBoardRoutes.ts | 1 + src/services/dashboardClientService.ts | 18 + 6 files changed, 579 insertions(+), 6 deletions(-) create mode 100644 src/models/dashboardClientModel.ts create mode 100644 src/services/dashboardClientService.ts diff --git a/package-lock.json b/package-lock.json index 2a1717a..be9f1ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcryptjs": "^3.0.2", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.7", @@ -308,6 +309,25 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -788,6 +808,11 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -833,6 +858,38 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/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==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -849,6 +906,14 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -864,6 +929,24 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -892,8 +975,28 @@ "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 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "bin": { + "bcrypt": "bin/bcrypt" + } }, "node_modules/body-parser": { "version": "1.20.3", @@ -922,7 +1025,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1000,6 +1102,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1018,11 +1128,23 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -1122,6 +1244,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -1147,6 +1274,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1190,6 +1325,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1659,6 +1799,33 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1667,6 +1834,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -1710,6 +1897,26 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1771,6 +1978,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1797,6 +2009,39 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/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==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1850,6 +2095,16 @@ "node >= 0.4.0" ] }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1872,6 +2127,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2009,6 +2272,28 @@ "url": "https://github.com/sponsors/wellwelwel" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2103,7 +2388,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2111,6 +2395,48 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -2190,6 +2516,56 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2220,6 +2596,14 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2296,6 +2680,14 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2440,6 +2832,19 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2464,6 +2869,21 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2675,6 +3095,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -2769,6 +3194,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -2785,6 +3215,38 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2825,6 +3287,22 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2850,6 +3328,11 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", @@ -2992,6 +3475,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -3034,6 +3522,20 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3049,6 +3551,14 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -3066,6 +3576,16 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 15816bb..64cd0db 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "typescript-eslint": "^8.25.0" }, "dependencies": { + "bcryptjs": "^3.0.2", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.7", diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index 9801bf2..2d7c87a 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -1,4 +1,5 @@ import { NextFunction, Request, Response } from 'express'; +import { dashboardClientService } from '../services/dashboardClientService'; import { userActionService } from '../services/userActionService'; import { userConnectionService } from '../services/userConnectionService'; import { userDeviceService } from '../services/userDeviceService'; @@ -174,4 +175,14 @@ export const dashboardController = { next(err); } }, + + enrollClient: async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password, domain } = req.body; + const apiKey = await dashboardClientService.enrollClient(email, password, domain); + res.status(201).json({ message: '회원가입성공', apiKey }); + } catch (err) { + next(err); + } + }, }; diff --git a/src/models/dashboardClientModel.ts b/src/models/dashboardClientModel.ts new file mode 100644 index 0000000..d45c13f --- /dev/null +++ b/src/models/dashboardClientModel.ts @@ -0,0 +1,22 @@ +import { DataTypes, Model } from 'sequelize'; +import sequelize from '../config/db'; + +class DashboardClient extends Model {} + +DashboardClient.init( + { + id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, + email: { type: DataTypes.STRING, allowNull: false, primaryKey: true, unique: true }, + hashedPassword: { type: DataTypes.STRING, allowNull: false }, + domain: { type: DataTypes.STRING, allowNull: false, unique: true }, + hashedApiKey: { type: DataTypes.STRING, allowNull: false }, + }, + { + sequelize, + modelName: 'dashboardClient', + tableName: 'dashboardClients', + timestamps: true, + } +); + +export const DashboardClientModel = DashboardClient; diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index 543d67b..fbd0daa 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -39,3 +39,4 @@ dashboardRouter.get( `/domains/:domain/pageInfo/totalVisitorsCount`, dashboardController.getTotalVisitors ); +dashboardRouter.post('/enrollClient', dashboardController.enrollClient); diff --git a/src/services/dashboardClientService.ts b/src/services/dashboardClientService.ts new file mode 100644 index 0000000..2201b0b --- /dev/null +++ b/src/services/dashboardClientService.ts @@ -0,0 +1,18 @@ +import bcrypt from 'bcryptjs'; +import { v4 as UUIDV4 } from 'uuid'; +import { DashboardClientModel } from '../models/dashboardClientModel'; + +export const dashboardClientService = { + enrollClient: async (email: string, password: string, domain: string) => { + const apiKey = UUIDV4(); + const hashedApiKey = await bcrypt.hash(apiKey, 10); + const hashedPassword = await bcrypt.hash(password, 10); + await DashboardClientModel.create({ + email, + hashedPassword, + domain, + hashedApiKey, + }); + return apiKey; + }, +}; From 7b7f726e9a8b8bbbf38f25e308b8f0f5b319a996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Mon, 10 Mar 2025 16:33:00 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feature:=20dashboardClient=20=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/dashboardController.ts | 10 ++++++++++ src/routes/dashBoardRoutes.ts | 1 + src/services/dashboardClientService.ts | 17 +++++++++++++++++ src/types/dashboardClientType.ts | 4 ++++ 4 files changed, 32 insertions(+) create mode 100644 src/types/dashboardClientType.ts diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index 2d7c87a..e2c3480 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -185,4 +185,14 @@ export const dashboardController = { next(err); } }, + + loginClient: async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password } = req.body; + const clientDomain = await dashboardClientService.loginClient(email, password); + res.status(200).json({ message: '로그인 성공', clientDomain }); + } catch (err) { + next(err); + } + }, }; diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index fbd0daa..03d1960 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -40,3 +40,4 @@ dashboardRouter.get( dashboardController.getTotalVisitors ); dashboardRouter.post('/enrollClient', dashboardController.enrollClient); +dashboardRouter.post('/loginClient', dashboardController.loginClient); diff --git a/src/services/dashboardClientService.ts b/src/services/dashboardClientService.ts index 2201b0b..e3bbafc 100644 --- a/src/services/dashboardClientService.ts +++ b/src/services/dashboardClientService.ts @@ -1,6 +1,7 @@ import bcrypt from 'bcryptjs'; import { v4 as UUIDV4 } from 'uuid'; import { DashboardClientModel } from '../models/dashboardClientModel'; +import { ClientType } from '../types/dashboardClientType'; export const dashboardClientService = { enrollClient: async (email: string, password: string, domain: string) => { @@ -15,4 +16,20 @@ export const dashboardClientService = { }); return apiKey; }, + + loginClient: async (email: string, password: string) => { + const client = (await DashboardClientModel.findOne({ + where: { email }, + attributes: ['hashedPassword', 'domain'], + raw: true, + })) as ClientType | null; + if (!client) { + throw new Error('등록된 유저가 아닙니다.'); + } + const isValidPassword = await bcrypt.compare(password, client.hashedPassword); + if (!isValidPassword) { + throw new Error('비밀번호가 올바르지 않습니다.'); + } + return client.domain; + }, }; diff --git a/src/types/dashboardClientType.ts b/src/types/dashboardClientType.ts new file mode 100644 index 0000000..d5fbb5b --- /dev/null +++ b/src/types/dashboardClientType.ts @@ -0,0 +1,4 @@ +export interface ClientType { + hashedPassword: string; + domain: string; +} From ee183491994424c2adaab20ddcb0504554eb40c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Mon, 10 Mar 2025 16:33:44 +0900 Subject: [PATCH 03/14] =?UTF-8?q?refactor:=20errorHandle=20=20=EA=B8=80?= =?UTF-8?q?=EB=A1=9C=EB=B2=8C=20=EC=97=90=EB=9F=AC=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/middleware/errorHandle.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/middleware/errorHandle.ts b/src/middleware/errorHandle.ts index e4826ee..88d9372 100644 --- a/src/middleware/errorHandle.ts +++ b/src/middleware/errorHandle.ts @@ -5,5 +5,11 @@ export const errorHandle = (err: Error, req: Request, res: Response, next: NextF if (res.headersSent) { return next(err); } + if ( + err.message === '등록된 유저가 아닙니다.' || + err.message === '비밀번호가 올바르지 않습니다.' + ) { + res.status(401).json({ message: err.message }); + } res.status(500).json({ message: '서버 내부 오류', err: err.message }); }; From 8af0afa37f79ddd1f85c4890fc1a7e024651ebfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Mon, 10 Mar 2025 18:25:17 +0900 Subject: [PATCH 04/14] =?UTF-8?q?refactor:=20dashboardClientService(apikey?= =?UTF-8?q?=EB=A5=BC=20bcrypt=20=EC=97=90=EC=84=9C=20sha256=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/dashboardClientService.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/services/dashboardClientService.ts b/src/services/dashboardClientService.ts index e3bbafc..60f8936 100644 --- a/src/services/dashboardClientService.ts +++ b/src/services/dashboardClientService.ts @@ -1,12 +1,11 @@ import bcrypt from 'bcryptjs'; -import { v4 as UUIDV4 } from 'uuid'; +import * as crypto from 'crypto'; import { DashboardClientModel } from '../models/dashboardClientModel'; import { ClientType } from '../types/dashboardClientType'; - export const dashboardClientService = { enrollClient: async (email: string, password: string, domain: string) => { - const apiKey = UUIDV4(); - const hashedApiKey = await bcrypt.hash(apiKey, 10); + const apiKey = crypto.randomBytes(32).toString('hex'); + const hashedApiKey = crypto.createHash('sha256').update(apiKey).digest('hex'); const hashedPassword = await bcrypt.hash(password, 10); await DashboardClientModel.create({ email, From ccd0ef5f36046681ce65dddd9f6856f5b7b4e455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Mon, 10 Mar 2025 19:04:01 +0900 Subject: [PATCH 05/14] =?UTF-8?q?refactor:=20authenticate(SHA-256=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20hash=ED=95=9C=EA=B2=83=EC=9C=BC=EB=A1=9C=20hahsedDo?= =?UTF-8?q?main=EB=81=BC=EB=A6=AC=20=EC=A1=B0=ED=9A=8C=20=ED=9B=84=20domai?= =?UTF-8?q?n=EB=B0=98=ED=99=98=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/middleware/authenticateAPIKey.ts | 10 +++------- src/middleware/errorHandle.ts | 4 +++- src/models/apikeyModel.ts | 20 -------------------- src/services/apiKeyService.ts | 16 +++++++++++----- 4 files changed, 17 insertions(+), 33 deletions(-) delete mode 100644 src/models/apikeyModel.ts diff --git a/src/middleware/authenticateAPIKey.ts b/src/middleware/authenticateAPIKey.ts index c6bb062..8e78d2c 100644 --- a/src/middleware/authenticateAPIKey.ts +++ b/src/middleware/authenticateAPIKey.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'; -import { getAPIKeyFromDB } from '../services/apiKeyService'; +import { getClientDomain } from '../services/apiKeyService'; export const authenticateAPIKey: RequestHandler = async ( req: Request, @@ -13,12 +13,8 @@ export const authenticateAPIKey: RequestHandler = async ( } const apiKey = authHeader.split(' ')[1]; try { - const domain = await getAPIKeyFromDB(apiKey); - if (!domain) { - res.status(403).json({ error: 'Forbidden: Invalid API Key' }); - return; - } - res.locals.domain = domain; + const userDomain = await getClientDomain(apiKey); + res.locals.domain = userDomain; next(); } catch (err) { next(err); diff --git a/src/middleware/errorHandle.ts b/src/middleware/errorHandle.ts index 88d9372..fb74620 100644 --- a/src/middleware/errorHandle.ts +++ b/src/middleware/errorHandle.ts @@ -7,9 +7,11 @@ export const errorHandle = (err: Error, req: Request, res: Response, next: NextF } if ( err.message === '등록된 유저가 아닙니다.' || - err.message === '비밀번호가 올바르지 않습니다.' + err.message === '비밀번호가 올바르지 않습니다.' || + err.message === 'apiKey가 유효하지 않습니다' ) { res.status(401).json({ message: err.message }); } + res.status(500).json({ message: '서버 내부 오류', err: err.message }); }; diff --git a/src/models/apikeyModel.ts b/src/models/apikeyModel.ts deleted file mode 100644 index 1f5e73d..0000000 --- a/src/models/apikeyModel.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DataTypes, Model } from 'sequelize'; -import sequelize from '../config/db'; - -class APIKeyModel extends Model {} - -APIKeyModel.init( - { - id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, - api_key: { type: DataTypes.STRING, allowNull: false, unique: true }, - domain: { type: DataTypes.STRING, allowNull: false }, - created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - }, - { - sequelize, - modelName: 'apiKeys', - tableName: 'apiKeys', - timestamps: true, - } -); -export const apiKeyModel = APIKeyModel; diff --git a/src/services/apiKeyService.ts b/src/services/apiKeyService.ts index 058bb64..9e6ab1d 100644 --- a/src/services/apiKeyService.ts +++ b/src/services/apiKeyService.ts @@ -1,10 +1,16 @@ -import { apiKeyModel } from '../models/apiKeyModel'; +import * as crypto from 'crypto'; +import { DashboardClientModel } from '../models/dashboardClientModel'; -export async function getAPIKeyFromDB(apiKey: string) { - const apiKeyData = await apiKeyModel.findOne({ - where: { apiKey }, +export async function getClientDomain(apiKey: string) { + const hashedApiKey = crypto.createHash('sha256').update(apiKey).digest('hex'); + const clientDomain = await DashboardClientModel.findOne({ + where: { hashedApiKey }, attributes: ['domain'], + raw: true, }); + if (!clientDomain) { + throw new Error('apiKey가 유효하지 않습니다'); + } - return apiKeyData ? apiKeyData.toJSON() : null; + return clientDomain; } From fc53d2029d1a1477c070529fb30761a5f222c883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Tue, 11 Mar 2025 02:18:11 +0900 Subject: [PATCH 06/14] =?UTF-8?q?refactor:=20dashboardClient=20=20service(?= =?UTF-8?q?=ED=83=80=EC=9E=85=EB=8B=A8=EC=96=B8=20=EC=88=98=EC=A0=95(get?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=BC=EB=B0=98=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0,=20)=20),=20co?= =?UTF-8?q?ntroller(email,=20domain=EC=9D=84=20session=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5),=20=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4=20s?= =?UTF-8?q?ession=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 588 +++---------------------- package.json | 1 + src/controllers/dashboardController.ts | 5 +- src/middleware/session.ts | 10 + src/server.ts | 3 +- src/services/dashboardClientService.ts | 15 +- src/types/dashboardClientType.ts | 4 - src/types/global.d.ts | 10 + 8 files changed, 106 insertions(+), 530 deletions(-) create mode 100644 src/middleware/session.ts delete mode 100644 src/types/dashboardClientType.ts create mode 100644 src/types/global.d.ts diff --git a/package-lock.json b/package-lock.json index be9f1ab..9246f79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", + "express-session": "^1.18.1", "mysql2": "^3.12.0", "sequelize": "^6.37.5", "uuid": "^11.1.0" @@ -23,6 +24,7 @@ "@types/cookie-parser": "^1.4.8", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", "@types/node": "^22.13.5", "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", @@ -309,25 +311,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -474,6 +457,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-S6TkD/lljxDlQ2u/4A70luD8/ZxZcrU5pQwI1rVXCiaVIywoFgbA+PIUNDjPhQpPdK0dGleLtYc/y7XWBfclBg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -808,11 +800,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -858,38 +845,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/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==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -906,14 +861,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -929,24 +876,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -975,20 +904,8 @@ "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==" - }, - "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", - "hasInstallScript": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" - }, - "engines": { - "node": ">= 10.0.0" - } + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/bcryptjs": { "version": "3.0.2", @@ -1025,6 +942,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1102,14 +1020,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1128,23 +1038,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "bin": { - "color-support": "bin.js" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -1244,11 +1142,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -1274,14 +1167,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "engines": { - "node": ">=8" - } - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1325,11 +1210,6 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1646,6 +1526,37 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1799,33 +1710,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1834,26 +1718,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -1897,26 +1761,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1978,11 +1822,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2009,39 +1848,6 @@ "node": ">= 0.8" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/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==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2095,16 +1901,6 @@ "node >= 0.4.0" ] }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2127,14 +1923,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2272,28 +2060,6 @@ "url": "https://github.com/sponsors/wellwelwel" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2388,6 +2154,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2395,48 +2162,6 @@ "node": "*" } }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -2516,56 +2241,6 @@ "node": ">= 0.6" } }, - "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2596,12 +2271,12 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" } }, "node_modules/optionator": { @@ -2680,14 +2355,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2810,6 +2477,14 @@ } ] }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2832,19 +2507,6 @@ "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2869,21 +2531,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3095,11 +2742,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -3194,11 +2836,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -3215,38 +2852,6 @@ "node": ">= 0.8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3287,22 +2892,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3328,11 +2917,6 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/ts-api-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", @@ -3453,6 +3037,17 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -3475,11 +3070,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -3522,20 +3112,6 @@ "node": ">= 0.8" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3551,14 +3127,6 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -3576,16 +3144,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 64cd0db..fd6d202 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/cookie-parser": "^1.4.8", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", "@types/node": "^22.13.5", "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index e2c3480..76929cb 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -189,8 +189,9 @@ export const dashboardController = { loginClient: async (req: Request, res: Response, next: NextFunction) => { try { const { email, password } = req.body; - const clientDomain = await dashboardClientService.loginClient(email, password); - res.status(200).json({ message: '로그인 성공', clientDomain }); + const domain = await dashboardClientService.loginClient(email, password); + req.session.client = { email, domain }; + res.status(200).json({ message: '로그인 성공' }); } catch (err) { next(err); } diff --git a/src/middleware/session.ts b/src/middleware/session.ts new file mode 100644 index 0000000..e2c7677 --- /dev/null +++ b/src/middleware/session.ts @@ -0,0 +1,10 @@ +import session from 'express-session'; + +export function createSession() { + return session({ + secret: process.env.SESSION_SECRET as string, + resave: false, + saveUninitialized: false, + cookie: { secure: false, httpOnly: true }, + }); +} diff --git a/src/server.ts b/src/server.ts index 76baa01..53d2db4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,13 +4,14 @@ import express from 'express'; import { UUIDV4 } from 'sequelize'; import { authenticateAPIKey } from './middleware/authenticateAPIKey'; import { errorHandle } from './middleware/errorHandle'; +import { createSession } from './middleware/session'; import { dashboardRouter } from './routes/dashboardRoutes'; import { trackerSdkRouter } from './routes/trackerSdkRoutes'; - const app = express(); const port = 3000; app.use(authenticateAPIKey); +app.use(createSession()); app.use( cors({ origin: 'http://client-tracker-sdk', diff --git a/src/services/dashboardClientService.ts b/src/services/dashboardClientService.ts index 60f8936..c2a9982 100644 --- a/src/services/dashboardClientService.ts +++ b/src/services/dashboardClientService.ts @@ -1,7 +1,6 @@ import bcrypt from 'bcryptjs'; import * as crypto from 'crypto'; import { DashboardClientModel } from '../models/dashboardClientModel'; -import { ClientType } from '../types/dashboardClientType'; export const dashboardClientService = { enrollClient: async (email: string, password: string, domain: string) => { const apiKey = crypto.randomBytes(32).toString('hex'); @@ -17,18 +16,18 @@ export const dashboardClientService = { }, loginClient: async (email: string, password: string) => { - const client = (await DashboardClientModel.findOne({ + const client = await DashboardClientModel.findOne({ where: { email }, attributes: ['hashedPassword', 'domain'], - raw: true, - })) as ClientType | null; + }); if (!client) { - throw new Error('등록된 유저가 아닙니다.'); + throw new Error('로그인 에러'); } - const isValidPassword = await bcrypt.compare(password, client.hashedPassword); + const clientData: { hashedPassword: string; domain: string } = client.get({ plain: true }); + const isValidPassword = await bcrypt.compare(password, clientData.hashedPassword); if (!isValidPassword) { - throw new Error('비밀번호가 올바르지 않습니다.'); + throw new Error('로그인 에러'); } - return client.domain; + return clientData.domain; }, }; diff --git a/src/types/dashboardClientType.ts b/src/types/dashboardClientType.ts deleted file mode 100644 index d5fbb5b..0000000 --- a/src/types/dashboardClientType.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ClientType { - hashedPassword: string; - domain: string; -} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..4895ffc --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,10 @@ +import 'express-session'; + +declare module 'express-session' { + interface SessionData { + client?: { + email: string; + domain: string; + }; + } +} From 67ab02d5865f8ca6c17b47593a22de06e58cf416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Tue, 11 Mar 2025 02:29:19 +0900 Subject: [PATCH 07/14] =?UTF-8?q?refactor:=20dashboardClinet=20=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/middleware/errorHandle.ts | 6 +----- src/services/apiKeyService.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/middleware/errorHandle.ts b/src/middleware/errorHandle.ts index fb74620..184695b 100644 --- a/src/middleware/errorHandle.ts +++ b/src/middleware/errorHandle.ts @@ -5,11 +5,7 @@ export const errorHandle = (err: Error, req: Request, res: Response, next: NextF if (res.headersSent) { return next(err); } - if ( - err.message === '등록된 유저가 아닙니다.' || - err.message === '비밀번호가 올바르지 않습니다.' || - err.message === 'apiKey가 유효하지 않습니다' - ) { + if (err.message === '로그인 에러' || err.message === 'apiKey 인증실패') { res.status(401).json({ message: err.message }); } diff --git a/src/services/apiKeyService.ts b/src/services/apiKeyService.ts index 9e6ab1d..f0e7600 100644 --- a/src/services/apiKeyService.ts +++ b/src/services/apiKeyService.ts @@ -9,7 +9,7 @@ export async function getClientDomain(apiKey: string) { raw: true, }); if (!clientDomain) { - throw new Error('apiKey가 유효하지 않습니다'); + throw new Error('apiKey 인증에러'); } return clientDomain; From 6ef8edb0e57913c09e2e4208a0f29e7dc3e9a501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Tue, 11 Mar 2025 13:43:17 +0900 Subject: [PATCH 08/14] =?UTF-8?q?refactor:=20sdkClientUser=20=20apiKeyserv?= =?UTF-8?q?ice(=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=A1=B0=ED=9A=8C=EB=B0=A9?= =?UTF-8?q?=EB=B2=95=EB=B3=80=EA=B2=BD(raw=20=EC=A0=9C=EA=B1=B0,=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=ED=9B=84=20=EC=9D=BC=EB=B0=98=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EB=A1=9C=20=EB=B0=98=ED=99=98=20)=20,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/middleware/authenticateAPIKey.ts | 4 ++-- src/services/apiKeyService.ts | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/middleware/authenticateAPIKey.ts b/src/middleware/authenticateAPIKey.ts index 8e78d2c..d08ba51 100644 --- a/src/middleware/authenticateAPIKey.ts +++ b/src/middleware/authenticateAPIKey.ts @@ -13,8 +13,8 @@ export const authenticateAPIKey: RequestHandler = async ( } const apiKey = authHeader.split(' ')[1]; try { - const userDomain = await getClientDomain(apiKey); - res.locals.domain = userDomain; + const { domain } = await getClientDomain(apiKey); + res.locals.domain = domain; next(); } catch (err) { next(err); diff --git a/src/services/apiKeyService.ts b/src/services/apiKeyService.ts index f0e7600..e163bd2 100644 --- a/src/services/apiKeyService.ts +++ b/src/services/apiKeyService.ts @@ -1,16 +1,14 @@ -import * as crypto from 'crypto'; import { DashboardClientModel } from '../models/dashboardClientModel'; export async function getClientDomain(apiKey: string) { - const hashedApiKey = crypto.createHash('sha256').update(apiKey).digest('hex'); const clientDomain = await DashboardClientModel.findOne({ - where: { hashedApiKey }, + where: { apiKey }, attributes: ['domain'], - raw: true, }); if (!clientDomain) { throw new Error('apiKey 인증에러'); } + const clientDomainForSdk: { domain: string } = clientDomain.get({ plain: true }); - return clientDomain; + return clientDomainForSdk; } From b961fc45847aa95f013d8bb116798817225121e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Tue, 11 Mar 2025 13:50:40 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20dashboardClient(apikey=20=09?= =?UTF-8?q?=EC=95=94=ED=98=B8=ED=99=94=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EC=97=90=20=EC=95=94=ED=98=B8=ED=99=94?= =?UTF-8?q?=EB=90=98=EC=A7=80=EC=95=8A=EC=9D=80=20apiKey=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/dashboardClientModel.ts | 2 +- src/services/dashboardClientService.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/models/dashboardClientModel.ts b/src/models/dashboardClientModel.ts index d45c13f..6dd8206 100644 --- a/src/models/dashboardClientModel.ts +++ b/src/models/dashboardClientModel.ts @@ -9,7 +9,7 @@ DashboardClient.init( email: { type: DataTypes.STRING, allowNull: false, primaryKey: true, unique: true }, hashedPassword: { type: DataTypes.STRING, allowNull: false }, domain: { type: DataTypes.STRING, allowNull: false, unique: true }, - hashedApiKey: { type: DataTypes.STRING, allowNull: false }, + apiKey: { type: DataTypes.STRING, allowNull: false }, }, { sequelize, diff --git a/src/services/dashboardClientService.ts b/src/services/dashboardClientService.ts index c2a9982..81ae97e 100644 --- a/src/services/dashboardClientService.ts +++ b/src/services/dashboardClientService.ts @@ -4,13 +4,12 @@ import { DashboardClientModel } from '../models/dashboardClientModel'; export const dashboardClientService = { enrollClient: async (email: string, password: string, domain: string) => { const apiKey = crypto.randomBytes(32).toString('hex'); - const hashedApiKey = crypto.createHash('sha256').update(apiKey).digest('hex'); const hashedPassword = await bcrypt.hash(password, 10); await DashboardClientModel.create({ email, hashedPassword, domain, - hashedApiKey, + apiKey, }); return apiKey; }, From 0b857612360c30d3d9ac7e9e7301c3575e9bdd8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Tue, 11 Mar 2025 14:12:15 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feature:=20dashboardClient(=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/dashboardController.ts | 10 ++++++++++ src/routes/dashBoardRoutes.ts | 1 + 2 files changed, 11 insertions(+) diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index 76929cb..e264540 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -196,4 +196,14 @@ export const dashboardController = { next(err); } }, + + logoutClient: (req: Request, res: Response) => { + if (!req.session.client) { + return; + } + req.session.destroy(() => { + res.clearCookie('connect.sid'); + res.json({ message: '로그아웃 성공' }); + }); + }, }; diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index 03d1960..72bf13e 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -41,3 +41,4 @@ dashboardRouter.get( ); dashboardRouter.post('/enrollClient', dashboardController.enrollClient); dashboardRouter.post('/loginClient', dashboardController.loginClient); +dashboardRouter.post('/logoutClient', dashboardController.logoutClient); From ee4f480b562e95dea6167f55f566483a0d26771b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Tue, 11 Mar 2025 14:40:25 +0900 Subject: [PATCH 11/14] =?UTF-8?q?refactor:=20dashboardController=20(?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20session=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9),=20errorHanle(=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EC=97=86=EC=9D=84=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80),=20dashboardRoutes=20(=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=EC=97=90=EC=84=9C=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94?= =?UTF-8?q?=20api=20url=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/dashboardController.ts | 100 ++++++++++++++++++++----- src/middleware/errorHandle.ts | 7 +- src/routes/dashBoardRoutes.ts | 54 +++++-------- 3 files changed, 107 insertions(+), 54 deletions(-) diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index e264540..ca359c3 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -9,7 +9,11 @@ import { userPageInfoService } from '../services/userPageInfoService'; export const dashboardController = { getOnlineUsersCount: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const onlineUsersCount = await userConnectionService.getOnlineUsersCount(domain); res.status(200).json({ onlineUsersCount }); } catch (err) { @@ -19,7 +23,11 @@ export const dashboardController = { getPerPageAverageScrollDepth: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const perPageAverageScrollDepth = await userActionService.getPerPageAverageScrollDepth(domain); res.status(200).json(perPageAverageScrollDepth); @@ -30,7 +38,11 @@ export const dashboardController = { getPerPageBounceRate: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const bounceRate = await userActionService.getPerPageBounceRate(domain); res.status(200).json(bounceRate); } catch (err) { @@ -40,7 +52,11 @@ export const dashboardController = { getBrowserStats: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const userBrowserStats = await userDeviceService.getBrowserStats(domain); res.status(200).json(userBrowserStats); } catch (err) { @@ -50,7 +66,11 @@ export const dashboardController = { getOsStats: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const userOsStats = await userDeviceService.getOsStats(domain); res.status(200).json(userOsStats); } catch (err) { @@ -60,7 +80,11 @@ export const dashboardController = { getDeviceStats: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const userDeviceStats = await userDeviceService.getDeviceStats(domain); res.status(200).json(userDeviceStats); } catch (err) { @@ -70,7 +94,11 @@ export const dashboardController = { getResolutionStats: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const userResolutionStats = await userDeviceService.getResolutionStats(domain); res.status(200).json(userResolutionStats); } catch (err) { @@ -80,7 +108,11 @@ export const dashboardController = { getLanguageStats: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const languageStats = await userInfoService.getLanguageStats(domain); res.status(200).json(languageStats); } catch (err) { @@ -90,7 +122,11 @@ export const dashboardController = { getCountryStats: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const countryStats = await userInfoService.getCountryStats(domain); res.status(200).json(countryStats); } catch (err) { @@ -100,7 +136,11 @@ export const dashboardController = { getVisitedUsersRate: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const getVisitedUsersRate = await userInfoService.getVisitedUsersRate(domain); res.status(200).json(getVisitedUsersRate); } catch (err) { @@ -110,7 +150,11 @@ export const dashboardController = { getReferrerStats: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const referrerStatus = await userPageInfoService.getReferrerStats(domain); res.status(200).json(referrerStatus); } catch (err) { @@ -120,7 +164,11 @@ export const dashboardController = { getAveragePageLoadTime: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const avgLoadTime = await userPageInfoService.getAveragePageLoadTime(domain); res.status(200).json(avgLoadTime); } catch (err) { @@ -130,7 +178,11 @@ export const dashboardController = { getPageViewCount: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: '시작 날짜와 종료날짜 올바르게 입력하세요' }); @@ -149,7 +201,11 @@ export const dashboardController = { getVisitorsByPeriod: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: 'startDate와 endDate는 필수입니다.' }); @@ -168,7 +224,11 @@ export const dashboardController = { getTotalVisitors: async (req: Request, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const totalVisitorsData = await userPageInfoService.getTotalVisitors(domain); res.status(200).json(totalVisitorsData); } catch (err) { @@ -178,7 +238,12 @@ export const dashboardController = { enrollClient: async (req: Request, res: Response, next: NextFunction) => { try { - const { email, password, domain } = req.body; + const { email, password } = req.body; + const domain = req.session.client?.domain; + if (!domain) { + next(new Error('로그인 필요')); + return; + } const apiKey = await dashboardClientService.enrollClient(email, password, domain); res.status(201).json({ message: '회원가입성공', apiKey }); } catch (err) { @@ -197,8 +262,9 @@ export const dashboardController = { } }, - logoutClient: (req: Request, res: Response) => { + logoutClient: (req: Request, res: Response, next: NextFunction) => { if (!req.session.client) { + next(new Error('이미 로그아웃')); return; } req.session.destroy(() => { diff --git a/src/middleware/errorHandle.ts b/src/middleware/errorHandle.ts index 184695b..804ceb1 100644 --- a/src/middleware/errorHandle.ts +++ b/src/middleware/errorHandle.ts @@ -5,7 +5,12 @@ export const errorHandle = (err: Error, req: Request, res: Response, next: NextF if (res.headersSent) { return next(err); } - if (err.message === '로그인 에러' || err.message === 'apiKey 인증실패') { + if ( + err.message === '로그인 에러' || + err.message === 'apiKey 인증실패' || + err.message === '로그인 필요' || + err.message === '이미 로그아웃' + ) { res.status(401).json({ message: err.message }); } diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index 72bf13e..fa8e740 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -3,42 +3,24 @@ import { dashboardController } from '../controllers/dashboardController'; export const dashboardRouter = express.Router(); -dashboardRouter.get('/userConnection/onlineUsersCount', dashboardController.getOnlineUsersCount); -dashboardRouter.get('/userDevice/browsersStats', dashboardController.getBrowserStats); -dashboardRouter.get('/userDevice/osStats', dashboardController.getOsStats); -dashboardRouter.get('/userDevice/deviceStats', dashboardController.getDeviceStats); -dashboardRouter.get('/userDevice/resolutionStats', dashboardController.getResolutionStats); +dashboardRouter.get('/dashboard/onlineUsersCount', dashboardController.getOnlineUsersCount); +dashboardRouter.get('/dashboard/browsersStats', dashboardController.getBrowserStats); +dashboardRouter.get('/dashboard/osStats', dashboardController.getOsStats); +dashboardRouter.get('/dashboard/deviceStats', dashboardController.getDeviceStats); +dashboardRouter.get('/dashboard/resolutionStats', dashboardController.getResolutionStats); +dashboardRouter.get('/dashboard/languageStats', dashboardController.getLanguageStats); +dashboardRouter.get('/dashboard/countryStats', dashboardController.getCountryStats); +dashboardRouter.get('/dashboard/visitedRate', dashboardController.getVisitedUsersRate); +dashboardRouter.get('/dashboard/referrer', dashboardController.getReferrerStats); +dashboardRouter.get('/dashboard/loadTime', dashboardController.getAveragePageLoadTime); +dashboardRouter.get('/dashboard/visitorsPageByPeriodCount', dashboardController.getPageViewCount); dashboardRouter.get( - '/domains/:domain/userInfo/languageStats', - dashboardController.getLanguageStats -); -dashboardRouter.get('/domains/:domain/userInfo/countryStats', dashboardController.getCountryStats); -dashboardRouter.get( - '/domains/:domain/userInfo/visitedRate', - dashboardController.getVisitedUsersRate -); -dashboardRouter.get('/domains/:domain/pageInfo/referrer', dashboardController.getReferrerStats); -dashboardRouter.get( - '/domains/:domain/pageInfo/loadTime', - dashboardController.getAveragePageLoadTime -); -dashboardRouter.get( - '/domains/:domain/pageInfo/visitorsPageByPeriodCount', - dashboardController.getPageViewCount -); -dashboardRouter.get( - '/userAction/perPageAverageScrollDepth', + '/dashboard/perPageAverageScrollDepth', dashboardController.getPerPageAverageScrollDepth ); -dashboardRouter.get('/userAction/bounceRate', dashboardController.getPerPageBounceRate); -dashboardRouter.get( - `/domains/:domain/pageInfo/visitorsByPeriodCount`, - dashboardController.getVisitorsByPeriod -); -dashboardRouter.get( - `/domains/:domain/pageInfo/totalVisitorsCount`, - dashboardController.getTotalVisitors -); -dashboardRouter.post('/enrollClient', dashboardController.enrollClient); -dashboardRouter.post('/loginClient', dashboardController.loginClient); -dashboardRouter.post('/logoutClient', dashboardController.logoutClient); +dashboardRouter.get('/dashboard/bounceRate', dashboardController.getPerPageBounceRate); +dashboardRouter.get(`/dashboard/visitorsByPeriodCount`, dashboardController.getVisitorsByPeriod); +dashboardRouter.get(`/dashboard/totalVisitorsCount`, dashboardController.getTotalVisitors); +dashboardRouter.post('/dashboard/enrollClient', dashboardController.enrollClient); +dashboardRouter.post('/dashboard/loginClient', dashboardController.loginClient); +dashboardRouter.post('/dashboard/logoutClient', dashboardController.logoutClient); From 416f11ed5efb356a5753b5ea9350c0242d520dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Tue, 11 Mar 2025 19:39:39 +0900 Subject: [PATCH 12/14] =?UTF-8?q?refactor:=20hasDashboardDomain=20?= =?UTF-8?q?=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/dashboardController.ts | 99 +++++--------------------- src/middleware/errorHandle.ts | 2 +- src/middleware/hasDashboardDomain.ts | 10 +++ src/routes/dashBoardRoutes.ts | 82 +++++++++++++++++---- 4 files changed, 95 insertions(+), 98 deletions(-) create mode 100644 src/middleware/hasDashboardDomain.ts diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index ca359c3..cdaac3b 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -9,11 +9,7 @@ import { userPageInfoService } from '../services/userPageInfoService'; export const dashboardController = { getOnlineUsersCount: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const onlineUsersCount = await userConnectionService.getOnlineUsersCount(domain); res.status(200).json({ onlineUsersCount }); } catch (err) { @@ -23,11 +19,7 @@ export const dashboardController = { getPerPageAverageScrollDepth: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const perPageAverageScrollDepth = await userActionService.getPerPageAverageScrollDepth(domain); res.status(200).json(perPageAverageScrollDepth); @@ -38,11 +30,7 @@ export const dashboardController = { getPerPageBounceRate: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const bounceRate = await userActionService.getPerPageBounceRate(domain); res.status(200).json(bounceRate); } catch (err) { @@ -52,11 +40,7 @@ export const dashboardController = { getBrowserStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const userBrowserStats = await userDeviceService.getBrowserStats(domain); res.status(200).json(userBrowserStats); } catch (err) { @@ -66,11 +50,7 @@ export const dashboardController = { getOsStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const userOsStats = await userDeviceService.getOsStats(domain); res.status(200).json(userOsStats); } catch (err) { @@ -80,11 +60,7 @@ export const dashboardController = { getDeviceStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const userDeviceStats = await userDeviceService.getDeviceStats(domain); res.status(200).json(userDeviceStats); } catch (err) { @@ -94,11 +70,7 @@ export const dashboardController = { getResolutionStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const userResolutionStats = await userDeviceService.getResolutionStats(domain); res.status(200).json(userResolutionStats); } catch (err) { @@ -108,11 +80,7 @@ export const dashboardController = { getLanguageStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const languageStats = await userInfoService.getLanguageStats(domain); res.status(200).json(languageStats); } catch (err) { @@ -122,11 +90,7 @@ export const dashboardController = { getCountryStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const countryStats = await userInfoService.getCountryStats(domain); res.status(200).json(countryStats); } catch (err) { @@ -136,11 +100,7 @@ export const dashboardController = { getVisitedUsersRate: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const getVisitedUsersRate = await userInfoService.getVisitedUsersRate(domain); res.status(200).json(getVisitedUsersRate); } catch (err) { @@ -150,11 +110,7 @@ export const dashboardController = { getReferrerStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const referrerStatus = await userPageInfoService.getReferrerStats(domain); res.status(200).json(referrerStatus); } catch (err) { @@ -164,11 +120,7 @@ export const dashboardController = { getAveragePageLoadTime: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const avgLoadTime = await userPageInfoService.getAveragePageLoadTime(domain); res.status(200).json(avgLoadTime); } catch (err) { @@ -178,11 +130,7 @@ export const dashboardController = { getPageViewCount: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: '시작 날짜와 종료날짜 올바르게 입력하세요' }); @@ -201,11 +149,7 @@ export const dashboardController = { getVisitorsByPeriod: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: 'startDate와 endDate는 필수입니다.' }); @@ -224,11 +168,7 @@ export const dashboardController = { getTotalVisitors: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const domain = res.locals.dashboardDomain; const totalVisitorsData = await userPageInfoService.getTotalVisitors(domain); res.status(200).json(totalVisitorsData); } catch (err) { @@ -238,14 +178,9 @@ export const dashboardController = { enrollClient: async (req: Request, res: Response, next: NextFunction) => { try { - const { email, password } = req.body; - const domain = req.session.client?.domain; - if (!domain) { - next(new Error('로그인 필요')); - return; - } + const { email, password, domain } = req.body; const apiKey = await dashboardClientService.enrollClient(email, password, domain); - res.status(201).json({ message: '회원가입성공', apiKey }); + res.status(201).json({ message: '회원가입 성공', apiKey }); } catch (err) { next(err); } diff --git a/src/middleware/errorHandle.ts b/src/middleware/errorHandle.ts index 804ceb1..683d465 100644 --- a/src/middleware/errorHandle.ts +++ b/src/middleware/errorHandle.ts @@ -6,7 +6,7 @@ export const errorHandle = (err: Error, req: Request, res: Response, next: NextF return next(err); } if ( - err.message === '로그인 에러' || + err.message === '도메인 에러' || err.message === 'apiKey 인증실패' || err.message === '로그인 필요' || err.message === '이미 로그아웃' diff --git a/src/middleware/hasDashboardDomain.ts b/src/middleware/hasDashboardDomain.ts new file mode 100644 index 0000000..09c961b --- /dev/null +++ b/src/middleware/hasDashboardDomain.ts @@ -0,0 +1,10 @@ +import { NextFunction, Request, Response } from 'express'; + +export const hasDashboardDomain = (req: Request, res: Response, next: NextFunction) => { + const domain = req.session.client?.domain; + if (!domain) { + return next(new Error('도메인 에러')); + } + res.locals.dashboardDomain = domain; + next(); +}; diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index fa8e740..8235782 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -1,26 +1,78 @@ import express from 'express'; import { dashboardController } from '../controllers/dashboardController'; - +import { hasDashboardDomain } from '../middleware/hasDashboardDomain'; export const dashboardRouter = express.Router(); -dashboardRouter.get('/dashboard/onlineUsersCount', dashboardController.getOnlineUsersCount); -dashboardRouter.get('/dashboard/browsersStats', dashboardController.getBrowserStats); -dashboardRouter.get('/dashboard/osStats', dashboardController.getOsStats); -dashboardRouter.get('/dashboard/deviceStats', dashboardController.getDeviceStats); -dashboardRouter.get('/dashboard/resolutionStats', dashboardController.getResolutionStats); -dashboardRouter.get('/dashboard/languageStats', dashboardController.getLanguageStats); -dashboardRouter.get('/dashboard/countryStats', dashboardController.getCountryStats); -dashboardRouter.get('/dashboard/visitedRate', dashboardController.getVisitedUsersRate); -dashboardRouter.get('/dashboard/referrer', dashboardController.getReferrerStats); -dashboardRouter.get('/dashboard/loadTime', dashboardController.getAveragePageLoadTime); -dashboardRouter.get('/dashboard/visitorsPageByPeriodCount', dashboardController.getPageViewCount); +dashboardRouter.get( + '/dashboard/onlineUsersCount', + hasDashboardDomain, + dashboardController.getOnlineUsersCount +); +dashboardRouter.get( + '/dashboard/browsersStats', + hasDashboardDomain, + dashboardController.getBrowserStats +); +dashboardRouter.get('/dashboard/osStats', hasDashboardDomain, dashboardController.getOsStats); +dashboardRouter.get( + '/dashboard/deviceStats', + hasDashboardDomain, + dashboardController.getDeviceStats +); +dashboardRouter.get( + '/dashboard/resolutionStats', + hasDashboardDomain, + dashboardController.getResolutionStats +); +dashboardRouter.get( + '/dashboard/languageStats', + hasDashboardDomain, + dashboardController.getLanguageStats +); +dashboardRouter.get( + '/dashboard/countryStats', + hasDashboardDomain, + dashboardController.getCountryStats +); +dashboardRouter.get( + '/dashboard/visitedRate', + hasDashboardDomain, + dashboardController.getVisitedUsersRate +); +dashboardRouter.get( + '/dashboard/referrer', + hasDashboardDomain, + dashboardController.getReferrerStats +); +dashboardRouter.get( + '/dashboard/loadTime', + hasDashboardDomain, + dashboardController.getAveragePageLoadTime +); +dashboardRouter.get( + '/dashboard/visitorsPageByPeriodCount', + hasDashboardDomain, + dashboardController.getPageViewCount +); dashboardRouter.get( '/dashboard/perPageAverageScrollDepth', dashboardController.getPerPageAverageScrollDepth ); -dashboardRouter.get('/dashboard/bounceRate', dashboardController.getPerPageBounceRate); -dashboardRouter.get(`/dashboard/visitorsByPeriodCount`, dashboardController.getVisitorsByPeriod); -dashboardRouter.get(`/dashboard/totalVisitorsCount`, dashboardController.getTotalVisitors); +dashboardRouter.get( + '/dashboard/bounceRate', + hasDashboardDomain, + dashboardController.getPerPageBounceRate +); +dashboardRouter.get( + '/dashboard/visitorsByPeriodCount', + hasDashboardDomain, + dashboardController.getVisitorsByPeriod +); +dashboardRouter.get( + '/dashboard/totalVisitorsCount', + hasDashboardDomain, + dashboardController.getTotalVisitors +); dashboardRouter.post('/dashboard/enrollClient', dashboardController.enrollClient); dashboardRouter.post('/dashboard/loginClient', dashboardController.loginClient); dashboardRouter.post('/dashboard/logoutClient', dashboardController.logoutClient); From e0b19b422d96a23174e83aa7a72ccb4b6080357a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Wed, 12 Mar 2025 01:14:27 +0900 Subject: [PATCH 13/14] =?UTF-8?q?refactor:=20=EB=AF=B8=EB=93=A4=EC=9B=A8?= =?UTF-8?q?=EC=96=B4=20ensureLogin=EC=B6=94=EA=B0=80(=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=96=88=EB=8A=94=EC=A7=80=20=ED=8C=90=EB=B3=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/dashboardController.ts | 30 ++++++------- src/middleware/ensureLogin.ts | 8 ++++ src/middleware/errorHandle.ts | 1 - src/middleware/hasDashboardDomain.ts | 10 ----- src/routes/dashBoardRoutes.ts | 62 +++++++------------------- 5 files changed, 38 insertions(+), 73 deletions(-) create mode 100644 src/middleware/ensureLogin.ts delete mode 100644 src/middleware/hasDashboardDomain.ts diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index cdaac3b..d36ea93 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -9,7 +9,7 @@ import { userPageInfoService } from '../services/userPageInfoService'; export const dashboardController = { getOnlineUsersCount: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const onlineUsersCount = await userConnectionService.getOnlineUsersCount(domain); res.status(200).json({ onlineUsersCount }); } catch (err) { @@ -19,7 +19,7 @@ export const dashboardController = { getPerPageAverageScrollDepth: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const perPageAverageScrollDepth = await userActionService.getPerPageAverageScrollDepth(domain); res.status(200).json(perPageAverageScrollDepth); @@ -30,7 +30,7 @@ export const dashboardController = { getPerPageBounceRate: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const bounceRate = await userActionService.getPerPageBounceRate(domain); res.status(200).json(bounceRate); } catch (err) { @@ -40,7 +40,7 @@ export const dashboardController = { getBrowserStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const userBrowserStats = await userDeviceService.getBrowserStats(domain); res.status(200).json(userBrowserStats); } catch (err) { @@ -50,7 +50,7 @@ export const dashboardController = { getOsStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const userOsStats = await userDeviceService.getOsStats(domain); res.status(200).json(userOsStats); } catch (err) { @@ -60,7 +60,7 @@ export const dashboardController = { getDeviceStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const userDeviceStats = await userDeviceService.getDeviceStats(domain); res.status(200).json(userDeviceStats); } catch (err) { @@ -70,7 +70,7 @@ export const dashboardController = { getResolutionStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const userResolutionStats = await userDeviceService.getResolutionStats(domain); res.status(200).json(userResolutionStats); } catch (err) { @@ -80,7 +80,7 @@ export const dashboardController = { getLanguageStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const languageStats = await userInfoService.getLanguageStats(domain); res.status(200).json(languageStats); } catch (err) { @@ -90,7 +90,7 @@ export const dashboardController = { getCountryStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const countryStats = await userInfoService.getCountryStats(domain); res.status(200).json(countryStats); } catch (err) { @@ -100,7 +100,7 @@ export const dashboardController = { getVisitedUsersRate: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const getVisitedUsersRate = await userInfoService.getVisitedUsersRate(domain); res.status(200).json(getVisitedUsersRate); } catch (err) { @@ -110,7 +110,7 @@ export const dashboardController = { getReferrerStats: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const referrerStatus = await userPageInfoService.getReferrerStats(domain); res.status(200).json(referrerStatus); } catch (err) { @@ -120,7 +120,7 @@ export const dashboardController = { getAveragePageLoadTime: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const avgLoadTime = await userPageInfoService.getAveragePageLoadTime(domain); res.status(200).json(avgLoadTime); } catch (err) { @@ -130,7 +130,7 @@ export const dashboardController = { getPageViewCount: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: '시작 날짜와 종료날짜 올바르게 입력하세요' }); @@ -149,7 +149,7 @@ export const dashboardController = { getVisitorsByPeriod: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: 'startDate와 endDate는 필수입니다.' }); @@ -168,7 +168,7 @@ export const dashboardController = { getTotalVisitors: async (req: Request, res: Response, next: NextFunction) => { try { - const domain = res.locals.dashboardDomain; + const domain = req.session.client!.domain; const totalVisitorsData = await userPageInfoService.getTotalVisitors(domain); res.status(200).json(totalVisitorsData); } catch (err) { diff --git a/src/middleware/ensureLogin.ts b/src/middleware/ensureLogin.ts new file mode 100644 index 0000000..21c2fdc --- /dev/null +++ b/src/middleware/ensureLogin.ts @@ -0,0 +1,8 @@ +import { NextFunction, Request, Response } from 'express'; + +export const ensureLogin = (req: Request, res: Response, next: NextFunction) => { + if (!req.session.client) { + next(new Error('로그인 필요')); + } + next(); +}; diff --git a/src/middleware/errorHandle.ts b/src/middleware/errorHandle.ts index 683d465..c9ff810 100644 --- a/src/middleware/errorHandle.ts +++ b/src/middleware/errorHandle.ts @@ -6,7 +6,6 @@ export const errorHandle = (err: Error, req: Request, res: Response, next: NextF return next(err); } if ( - err.message === '도메인 에러' || err.message === 'apiKey 인증실패' || err.message === '로그인 필요' || err.message === '이미 로그아웃' diff --git a/src/middleware/hasDashboardDomain.ts b/src/middleware/hasDashboardDomain.ts deleted file mode 100644 index 09c961b..0000000 --- a/src/middleware/hasDashboardDomain.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NextFunction, Request, Response } from 'express'; - -export const hasDashboardDomain = (req: Request, res: Response, next: NextFunction) => { - const domain = req.session.client?.domain; - if (!domain) { - return next(new Error('도메인 에러')); - } - res.locals.dashboardDomain = domain; - next(); -}; diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index 8235782..05af25a 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -1,76 +1,44 @@ import express from 'express'; import { dashboardController } from '../controllers/dashboardController'; -import { hasDashboardDomain } from '../middleware/hasDashboardDomain'; +import { ensureLogin } from '../middleware/ensureLogin'; export const dashboardRouter = express.Router(); dashboardRouter.get( '/dashboard/onlineUsersCount', - hasDashboardDomain, + ensureLogin, dashboardController.getOnlineUsersCount ); -dashboardRouter.get( - '/dashboard/browsersStats', - hasDashboardDomain, - dashboardController.getBrowserStats -); -dashboardRouter.get('/dashboard/osStats', hasDashboardDomain, dashboardController.getOsStats); -dashboardRouter.get( - '/dashboard/deviceStats', - hasDashboardDomain, - dashboardController.getDeviceStats -); +dashboardRouter.get('/dashboard/browsersStats', ensureLogin, dashboardController.getBrowserStats); +dashboardRouter.get('/dashboard/osStats', ensureLogin, dashboardController.getOsStats); +dashboardRouter.get('/dashboard/deviceStats', ensureLogin, dashboardController.getDeviceStats); dashboardRouter.get( '/dashboard/resolutionStats', - hasDashboardDomain, + ensureLogin, dashboardController.getResolutionStats ); -dashboardRouter.get( - '/dashboard/languageStats', - hasDashboardDomain, - dashboardController.getLanguageStats -); -dashboardRouter.get( - '/dashboard/countryStats', - hasDashboardDomain, - dashboardController.getCountryStats -); -dashboardRouter.get( - '/dashboard/visitedRate', - hasDashboardDomain, - dashboardController.getVisitedUsersRate -); -dashboardRouter.get( - '/dashboard/referrer', - hasDashboardDomain, - dashboardController.getReferrerStats -); -dashboardRouter.get( - '/dashboard/loadTime', - hasDashboardDomain, - dashboardController.getAveragePageLoadTime -); +dashboardRouter.get('/dashboard/languageStats', ensureLogin, dashboardController.getLanguageStats); +dashboardRouter.get('/dashboard/countryStats', ensureLogin, dashboardController.getCountryStats); +dashboardRouter.get('/dashboard/visitedRate', ensureLogin, dashboardController.getVisitedUsersRate); +dashboardRouter.get('/dashboard/referrer', ensureLogin, dashboardController.getReferrerStats); +dashboardRouter.get('/dashboard/loadTime', ensureLogin, dashboardController.getAveragePageLoadTime); dashboardRouter.get( '/dashboard/visitorsPageByPeriodCount', - hasDashboardDomain, + ensureLogin, dashboardController.getPageViewCount ); dashboardRouter.get( '/dashboard/perPageAverageScrollDepth', dashboardController.getPerPageAverageScrollDepth ); -dashboardRouter.get( - '/dashboard/bounceRate', - hasDashboardDomain, - dashboardController.getPerPageBounceRate -); +dashboardRouter.get('/dashboard/bounceRate', ensureLogin, dashboardController.getPerPageBounceRate); dashboardRouter.get( '/dashboard/visitorsByPeriodCount', - hasDashboardDomain, + ensureLogin, dashboardController.getVisitorsByPeriod ); dashboardRouter.get( '/dashboard/totalVisitorsCount', - hasDashboardDomain, + ensureLogin, dashboardController.getTotalVisitors ); dashboardRouter.post('/dashboard/enrollClient', dashboardController.enrollClient); From 83c7f54aae1587f068639b658afb7723f87524e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=86=B7=E1=84=8C=E1=85=A2=E1=84=8B?= =?UTF-8?q?=E1=85=AF=E1=86=AB?= Date: Thu, 13 Mar 2025 16:59:43 +0900 Subject: [PATCH 14/14] =?UTF-8?q?refactor:=20sessionType=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20,=20Controller(Request=ED=83=80=EC=9E=85=20Authenti?= =?UTF-8?q?catedRequest=20=ED=83=80=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 6 +- src/controllers/dashboardController.ts | 65 ++++++++++-------- src/routes/dashBoardRoutes.ts | 68 ++++++++++++++----- .../{global.d.ts => sessionDataType.d.ts} | 0 src/types/sessionType.d.ts | 13 ++++ 5 files changed, 101 insertions(+), 51 deletions(-) rename src/types/{global.d.ts => sessionDataType.d.ts} (100%) create mode 100644 src/types/sessionType.d.ts diff --git a/.eslintrc.js b/.eslintrc.js index 5e53d47..e096fcc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,7 @@ export default { parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint', 'prettier'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier', - ], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], env: { node: true, es2022: true, diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index d36ea93..b5537a0 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -5,11 +5,12 @@ import { userConnectionService } from '../services/userConnectionService'; import { userDeviceService } from '../services/userDeviceService'; import { userInfoService } from '../services/userInfoService'; import { userPageInfoService } from '../services/userPageInfoService'; +import { AuthenticatedRequest } from '../types/sessionType'; export const dashboardController = { - getOnlineUsersCount: async (req: Request, res: Response, next: NextFunction) => { + getOnlineUsersCount: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const onlineUsersCount = await userConnectionService.getOnlineUsersCount(domain); res.status(200).json({ onlineUsersCount }); } catch (err) { @@ -17,9 +18,13 @@ export const dashboardController = { } }, - getPerPageAverageScrollDepth: async (req: Request, res: Response, next: NextFunction) => { + getPerPageAverageScrollDepth: async ( + req: AuthenticatedRequest, + res: Response, + next: NextFunction + ) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const perPageAverageScrollDepth = await userActionService.getPerPageAverageScrollDepth(domain); res.status(200).json(perPageAverageScrollDepth); @@ -28,9 +33,9 @@ export const dashboardController = { } }, - getPerPageBounceRate: async (req: Request, res: Response, next: NextFunction) => { + getPerPageBounceRate: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const bounceRate = await userActionService.getPerPageBounceRate(domain); res.status(200).json(bounceRate); } catch (err) { @@ -38,9 +43,9 @@ export const dashboardController = { } }, - getBrowserStats: async (req: Request, res: Response, next: NextFunction) => { + getBrowserStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const userBrowserStats = await userDeviceService.getBrowserStats(domain); res.status(200).json(userBrowserStats); } catch (err) { @@ -48,9 +53,9 @@ export const dashboardController = { } }, - getOsStats: async (req: Request, res: Response, next: NextFunction) => { + getOsStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const userOsStats = await userDeviceService.getOsStats(domain); res.status(200).json(userOsStats); } catch (err) { @@ -58,9 +63,9 @@ export const dashboardController = { } }, - getDeviceStats: async (req: Request, res: Response, next: NextFunction) => { + getDeviceStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const userDeviceStats = await userDeviceService.getDeviceStats(domain); res.status(200).json(userDeviceStats); } catch (err) { @@ -68,9 +73,9 @@ export const dashboardController = { } }, - getResolutionStats: async (req: Request, res: Response, next: NextFunction) => { + getResolutionStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const userResolutionStats = await userDeviceService.getResolutionStats(domain); res.status(200).json(userResolutionStats); } catch (err) { @@ -78,9 +83,9 @@ export const dashboardController = { } }, - getLanguageStats: async (req: Request, res: Response, next: NextFunction) => { + getLanguageStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const languageStats = await userInfoService.getLanguageStats(domain); res.status(200).json(languageStats); } catch (err) { @@ -88,9 +93,9 @@ export const dashboardController = { } }, - getCountryStats: async (req: Request, res: Response, next: NextFunction) => { + getCountryStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const countryStats = await userInfoService.getCountryStats(domain); res.status(200).json(countryStats); } catch (err) { @@ -98,9 +103,9 @@ export const dashboardController = { } }, - getVisitedUsersRate: async (req: Request, res: Response, next: NextFunction) => { + getVisitedUsersRate: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const getVisitedUsersRate = await userInfoService.getVisitedUsersRate(domain); res.status(200).json(getVisitedUsersRate); } catch (err) { @@ -108,9 +113,9 @@ export const dashboardController = { } }, - getReferrerStats: async (req: Request, res: Response, next: NextFunction) => { + getReferrerStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const referrerStatus = await userPageInfoService.getReferrerStats(domain); res.status(200).json(referrerStatus); } catch (err) { @@ -118,9 +123,9 @@ export const dashboardController = { } }, - getAveragePageLoadTime: async (req: Request, res: Response, next: NextFunction) => { + getAveragePageLoadTime: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const avgLoadTime = await userPageInfoService.getAveragePageLoadTime(domain); res.status(200).json(avgLoadTime); } catch (err) { @@ -128,9 +133,9 @@ export const dashboardController = { } }, - getPageViewCount: async (req: Request, res: Response, next: NextFunction) => { + getPageViewCount: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: '시작 날짜와 종료날짜 올바르게 입력하세요' }); @@ -147,9 +152,9 @@ export const dashboardController = { } }, - getVisitorsByPeriod: async (req: Request, res: Response, next: NextFunction) => { + getVisitorsByPeriod: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: 'startDate와 endDate는 필수입니다.' }); @@ -166,9 +171,9 @@ export const dashboardController = { } }, - getTotalVisitors: async (req: Request, res: Response, next: NextFunction) => { + getTotalVisitors: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const domain = req.session.client!.domain; + const domain = req.session.client.domain; const totalVisitorsData = await userPageInfoService.getTotalVisitors(domain); res.status(200).json(totalVisitorsData); } catch (err) { diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index 05af25a..b4b987a 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -1,4 +1,4 @@ -import express from 'express'; +import express, { RequestHandler } from 'express'; import { dashboardController } from '../controllers/dashboardController'; import { ensureLogin } from '../middleware/ensureLogin'; export const dashboardRouter = express.Router(); @@ -6,40 +6,76 @@ export const dashboardRouter = express.Router(); dashboardRouter.get( '/dashboard/onlineUsersCount', ensureLogin, - dashboardController.getOnlineUsersCount + dashboardController.getOnlineUsersCount as RequestHandler +); +dashboardRouter.get( + '/dashboard/browsersStats', + ensureLogin, + dashboardController.getBrowserStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/osStats', + ensureLogin, + dashboardController.getOsStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/deviceStats', + ensureLogin, + dashboardController.getDeviceStats as RequestHandler ); -dashboardRouter.get('/dashboard/browsersStats', ensureLogin, dashboardController.getBrowserStats); -dashboardRouter.get('/dashboard/osStats', ensureLogin, dashboardController.getOsStats); -dashboardRouter.get('/dashboard/deviceStats', ensureLogin, dashboardController.getDeviceStats); dashboardRouter.get( '/dashboard/resolutionStats', ensureLogin, - dashboardController.getResolutionStats + dashboardController.getResolutionStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/languageStats', + ensureLogin, + dashboardController.getLanguageStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/countryStats', + ensureLogin, + dashboardController.getCountryStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/visitedRate', + ensureLogin, + dashboardController.getVisitedUsersRate as RequestHandler +); +dashboardRouter.get( + '/dashboard/referrer', + ensureLogin, + dashboardController.getReferrerStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/loadTime', + ensureLogin, + dashboardController.getAveragePageLoadTime as RequestHandler ); -dashboardRouter.get('/dashboard/languageStats', ensureLogin, dashboardController.getLanguageStats); -dashboardRouter.get('/dashboard/countryStats', ensureLogin, dashboardController.getCountryStats); -dashboardRouter.get('/dashboard/visitedRate', ensureLogin, dashboardController.getVisitedUsersRate); -dashboardRouter.get('/dashboard/referrer', ensureLogin, dashboardController.getReferrerStats); -dashboardRouter.get('/dashboard/loadTime', ensureLogin, dashboardController.getAveragePageLoadTime); dashboardRouter.get( '/dashboard/visitorsPageByPeriodCount', ensureLogin, - dashboardController.getPageViewCount + dashboardController.getPageViewCount as RequestHandler ); dashboardRouter.get( '/dashboard/perPageAverageScrollDepth', - dashboardController.getPerPageAverageScrollDepth + dashboardController.getPerPageAverageScrollDepth as RequestHandler +); +dashboardRouter.get( + '/dashboard/bounceRate', + ensureLogin, + dashboardController.getPerPageBounceRate as RequestHandler ); -dashboardRouter.get('/dashboard/bounceRate', ensureLogin, dashboardController.getPerPageBounceRate); dashboardRouter.get( '/dashboard/visitorsByPeriodCount', ensureLogin, - dashboardController.getVisitorsByPeriod + dashboardController.getVisitorsByPeriod as RequestHandler ); dashboardRouter.get( '/dashboard/totalVisitorsCount', ensureLogin, - dashboardController.getTotalVisitors + dashboardController.getTotalVisitors as RequestHandler ); dashboardRouter.post('/dashboard/enrollClient', dashboardController.enrollClient); dashboardRouter.post('/dashboard/loginClient', dashboardController.loginClient); diff --git a/src/types/global.d.ts b/src/types/sessionDataType.d.ts similarity index 100% rename from src/types/global.d.ts rename to src/types/sessionDataType.d.ts diff --git a/src/types/sessionType.d.ts b/src/types/sessionType.d.ts new file mode 100644 index 0000000..fb324f9 --- /dev/null +++ b/src/types/sessionType.d.ts @@ -0,0 +1,13 @@ +import { Request } from 'express'; +import { Session } from 'express-session'; +import { ParsedQs } from 'qs'; + +export interface AuthenticatedRequest + extends Request { + session: Session & { + client: { + email: string; + domain: string; + }; + }; +}