From 8db69a459486a39367769008de57fde684c98324 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 30 Apr 2026 20:01:35 +0900 Subject: [PATCH 01/20] =?UTF-8?q?chore:=20=EC=B4=88=EA=B8=B0=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/.gitignore | 6 + week5/package-lock.json | 1996 +++++++++++++++++++++++++++++++++++++++ week5/package.json | 18 + week5/tsconfig.json | 25 + 4 files changed, 2045 insertions(+) create mode 100644 week5/.gitignore create mode 100644 week5/package-lock.json create mode 100644 week5/package.json create mode 100644 week5/tsconfig.json diff --git a/week5/.gitignore b/week5/.gitignore new file mode 100644 index 0000000..7cde7e8 --- /dev/null +++ b/week5/.gitignore @@ -0,0 +1,6 @@ +# dependency directories +node_modules/ + +# dotenv environment variable files +.env +.env.* \ No newline at end of file diff --git a/week5/package-lock.json b/week5/package-lock.json new file mode 100644 index 0000000..c5919eb --- /dev/null +++ b/week5/package-lock.json @@ -0,0 +1,1996 @@ +{ + "name": "week5", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "http-status-codes": "^2.3.0", + "mysql2": "^3.22.3" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/dotenv": "^6.1.1", + "@types/express": "^5.0.6", + "@types/node": "^25.6.0", + "nodemon": "^3.1.14", + "tsx": "^4.21.0", + "typescript": "^6.0.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/dotenv": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", + "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.3.tgz", + "integrity": "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/week5/package.json b/week5/package.json new file mode 100644 index 0000000..01dd51a --- /dev/null +++ b/week5/package.json @@ -0,0 +1,18 @@ +{ + "dependencies": { + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "http-status-codes": "^2.3.0", + "mysql2": "^3.22.3" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/dotenv": "^6.1.1", + "@types/express": "^5.0.6", + "@types/node": "^25.6.0", + "nodemon": "^3.1.14", + "tsx": "^4.21.0", + "typescript": "^6.0.3" + } +} diff --git a/week5/tsconfig.json b/week5/tsconfig.json new file mode 100644 index 0000000..476a7fc --- /dev/null +++ b/week5/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + /* 프로젝트 구조 설정 */ + "rootDir": "./src", // 소스 코드(.ts)가 모여있는 폴더예요. + "outDir": "./dist", // 컴파일된 결과물(.js)이 저장될 폴더예요. + + /* 언어 및 모듈 설정 (4주차에서 배운 ESM 방식을 유지해요!) */ + "target": "ESNext", + "module": "NodeNext", // 최신 Node.js의 모듈 시스템(ESM)을 사용합니다. + "moduleResolution": "NodeNext", + "lib": ["ESNext"], // 최신 자바스크립트 표준 기능을 사용할 수 있게 해줘요. + + /* 엄격한 검사 옵션 (에러를 미리 잡는 핵심!) */ + "strict": true, + "noUncheckedIndexedAccess": true, + + /* 기타 추천 옵션 */ + "skipLibCheck": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] // 빌드 결과물(dist)도 검사에서 제외하는 게 좋아요! +} From 6082c6752bd00ae97e0086fa6bdc7009409cd850 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 30 Apr 2026 20:47:20 +0900 Subject: [PATCH 02/20] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=EC=84=B8?= =?UTF-8?q?=ED=8C=852=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- package-lock.json | 6 ++ week5/package.json | 6 ++ week5/src/db.config.ts | 18 ++++ week5/src/index.ts | 27 +++++ .../users/controllers/user.controller.ts | 15 +++ week5/src/modules/users/db.config.ts | 14 +++ week5/src/modules/users/dtos/user.dto.ts | 64 ++++++++++++ .../users/repositories/user.repository.ts | 98 +++++++++++++++++++ .../modules/users/services/user.service.ts | 32 ++++++ 9 files changed, 280 insertions(+) create mode 100644 package-lock.json create mode 100644 week5/src/db.config.ts create mode 100644 week5/src/index.ts create mode 100644 week5/src/modules/users/controllers/user.controller.ts create mode 100644 week5/src/modules/users/db.config.ts create mode 100644 week5/src/modules/users/dtos/user.dto.ts create mode 100644 week5/src/modules/users/repositories/user.repository.ts create mode 100644 week5/src/modules/users/services/user.service.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9e97780 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "10th_Node", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/week5/package.json b/week5/package.json index 01dd51a..a2396fa 100644 --- a/week5/package.json +++ b/week5/package.json @@ -1,4 +1,10 @@ { + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "tsx src/index.ts", + "dev": "nodemon --exec tsx src/index.ts" + }, "dependencies": { "cors": "^2.8.6", "dotenv": "^17.4.2", diff --git a/week5/src/db.config.ts b/week5/src/db.config.ts new file mode 100644 index 0000000..a620246 --- /dev/null +++ b/week5/src/db.config.ts @@ -0,0 +1,18 @@ +import mysql from "mysql2/promise"; +import dotenv from "dotenv"; + +dotenv.config(); + +export const pool = mysql.createPool({ + host: process.env.DB_HOST || "localhost", // mysql의 hostname + user: process.env.DB_USER || "root", // user 이름 + port: parseInt(process.env.DB_PORT || "3306"), // 포트 번호 + // 환경 변수는 기본적으로 문자열이에요. 숫자가 필요한 port 필드를 위해 parseInt로 형변환을 해줍니다! + database: process.env.DB_NAME || "umc_10th", // 데이터베이스 이름 + password: process.env.DB_PASSWORD || "password", // 비밀번호 + waitForConnections: true, + // Pool에 획득할 수 있는 connection이 없을 때, + // true면 요청을 queue에 넣고 connection을 사용할 수 있게 되면 요청을 실행하며, false이면 즉시 오류를 내보내고 다시 요청 + connectionLimit: 10, // 몇 개의 커넥션을 가지게끔 할 것인지 + queueLimit: 0, // getConnection에서 오류가 발생하기 전에 Pool에 대기할 요청의 개수 한도 +}); \ No newline at end of file diff --git a/week5/src/index.ts b/week5/src/index.ts new file mode 100644 index 0000000..cd14f81 --- /dev/null +++ b/week5/src/index.ts @@ -0,0 +1,27 @@ +import dotenv from "dotenv"; +dotenv.config(); // 환경 변수를 가장 먼저 로드해야 DB pool 생성 전에 적용됩니다. + +import express, { Express, Request, Response } from "express"; +import cors from "cors"; +import { handleUserSignUp } from "./modules/users/controllers/user.controller.js"; + +const app: Express = express(); +const port = process.env.PORT || 3000; + +// 2. 미들웨어 설정 +app.use(cors()); // cors 방식 허용 +app.use(express.static('public')); // 정적 파일 접근 +app.use(express.json()); // request의 본문을 json으로 해석할 수 있도록 함(JSON 형태의 요청 body를 파싱하기 위함) +app.use(express.urlencoded({ extended: false })); // 단순 객체 문자열 형태로 본문 데이터 해석 + +// 3. 기본 라우트 +app.get("/", (req: Request, res: Response) => { + res.send("Hello World! This is TypeScript Server!"); +}); + +app.post("/api/v1/users/signup", handleUserSignUp); + +// 4. 서버 시작 +app.listen(port, () => { + console.log(`[server]: Server is running at :${port}`); +}); \ No newline at end of file diff --git a/week5/src/modules/users/controllers/user.controller.ts b/week5/src/modules/users/controllers/user.controller.ts new file mode 100644 index 0000000..b076e95 --- /dev/null +++ b/week5/src/modules/users/controllers/user.controller.ts @@ -0,0 +1,15 @@ +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; +import { bodyToUser, UserSignUpRequest } from "../dtos/user.dto.js"; +import { userSignUp } from "../services/user.service.js"; + +export const handleUserSignUp = async (req: Request, res: Response, next: NextFunction ) => { + console.log("회원가입을 요청했습니다!"); + console.log("body:", req.body); // 값이 잘 들어오나 확인하기 위한 테스트용 + + //서비스 로직 호출 + const user = await userSignUp(bodyToUser(req.body as UserSignUpRequest)); + + //성공 응답 보내기 + res.status(StatusCodes.OK).json({ result: user }); +}; \ No newline at end of file diff --git a/week5/src/modules/users/db.config.ts b/week5/src/modules/users/db.config.ts new file mode 100644 index 0000000..8138bac --- /dev/null +++ b/week5/src/modules/users/db.config.ts @@ -0,0 +1,14 @@ +import dotenv from "dotenv"; +import mysql from "mysql2/promise"; + +dotenv.config(); + +export const pool = mysql.createPool({ + host: process.env.DB_HOST || "localhost", + port: Number(process.env.DB_PORT) || 3306, + user: process.env.DB_USER || "root", + password: process.env.DB_PASSWORD || "", + database: process.env.DB_NAME || "", + waitForConnections: true, + connectionLimit: 10, +}); diff --git a/week5/src/modules/users/dtos/user.dto.ts b/week5/src/modules/users/dtos/user.dto.ts new file mode 100644 index 0000000..e27f405 --- /dev/null +++ b/week5/src/modules/users/dtos/user.dto.ts @@ -0,0 +1,64 @@ +// 1. 회원가입 요청 데이터의 설계도를 만듭니다. +export interface UserSignUpRequest { + email: string; + name: string; + gender: string; + birth: string; + address?: string; // ?가 붙으면 '없을 수도 있음(선택)'이라는 뜻이에요! + detailAddress?: string; + phoneNumber: string; + preferences: number[]; +} + +// 2. bodyToUser가 반환하는 내부 데이터 타입입니다. +export interface UserData { + email: string; + name: string; + gender: string; + birth: Date; + address: string; + detailAddress: string; + phoneNumber: string; + preferences: number[]; +} + +// 3. DB에서 조회한 데이터를 클라이언트 응답 형태로 변환해주는 함수입니다. +export const responseFromUser = ({ + user, + preferences, +}: { + user: any; + preferences: any[]; +}) => { + return { + id: user.id, + email: user.email, + name: user.name, + gender: user.gender, + birth: user.birth, + address: user.address, + detailAddress: user.detail_address, + phoneNumber: user.phone_number, + preferences: preferences.map((p) => ({ + id: p.id, + foodCategoryId: p.food_category_id, + name: p.name, + })), + }; +}; + +// 4. 요청받은 데이터를 우리 시스템에 맞는 데이터로 변환해주는 함수입니다. +export const bodyToUser = (body: UserSignUpRequest): UserData => { + const birth = new Date(body.birth); //날짜 변환 + + return { + email: body.email, //필수 + name: body.name, // 필수 + gender: body.gender, // 필수 + birth, // 필수 + address: body.address || "", //선택 + detailAddress: body.detailAddress || "", //선택 + phoneNumber: body.phoneNumber,//필수 + preferences: body.preferences,// 필수 + }; +}; \ No newline at end of file diff --git a/week5/src/modules/users/repositories/user.repository.ts b/week5/src/modules/users/repositories/user.repository.ts new file mode 100644 index 0000000..f45d325 --- /dev/null +++ b/week5/src/modules/users/repositories/user.repository.ts @@ -0,0 +1,98 @@ +import { ResultSetHeader, RowDataPacket } from "mysql2"; +import { pool } from "../db.config.js"; + +// 1. User 데이터 삽입 +export const addUser = async (data: any): Promise => { + const conn = await pool.getConnection(); + + try { + // [confirm] 뒤에 타입을 명시해 줍니다. (조회 결과는 배열 형태예요) + const [confirm] = await pool.query( + `SELECT EXISTS(SELECT 1 FROM user WHERE email = ?) as isExistEmail;`, + [data.email] + ); + + // 이제 confirm[0] 뒤에 점을 찍어도 에러가 나지 않아요! + if (confirm[0]?.isExistEmail) { + return null; + } + + // 삽입 결과는 ResultSetHeader 타입을 사용합니다. + const [result] = await pool.query( + `INSERT INTO user (email, name, gender, birth, address, detail_address, phone_number) VALUES (?, ?, ?, ?, ?, ?, ?);`, + [ + data.email, + data.name, + data.gender, + data.birth, + data.address, + data.detailAddress, + data.phoneNumber, + ] + ); + + return result.insertId; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 2. 사용자 정보 얻기 +export const getUser = async (userId: number): Promise => { + const conn = await pool.getConnection(); + + try { + const [user] = await pool.query( + `SELECT * FROM user WHERE id = ?;`, + [userId] + ); + + if (user.length === 0) { + return null; + } + + return user[0]; // 배열의 첫 번째 요소(유저 정보)를 반환합니다. + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 3. 음식 선호 카테고리 매핑 +export const setPreference = async (userId: number, foodCategoryId: number): Promise => { + const conn = await pool.getConnection(); + + try { + await pool.query( + `INSERT INTO user_favor_category (food_category_id, user_id) VALUES (?, ?);`, + [foodCategoryId, userId] + ); + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 4. 사용자 선호 카테고리 반환 +export const getUserPreferencesByUserId = async (userId: number): Promise => { + const conn = await pool.getConnection(); + + try { + const [preferences] = await pool.query( + "SELECT ufc.id, ufc.food_category_id, ufc.user_id, fcl.name " + + "FROM user_favor_category ufc JOIN food_category fcl on ufc.food_category_id = fcl.id " + + "WHERE ufc.user_id = ? ORDER BY ufc.food_category_id ASC;", + [userId] + ); + + return preferences as any[]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; \ No newline at end of file diff --git a/week5/src/modules/users/services/user.service.ts b/week5/src/modules/users/services/user.service.ts new file mode 100644 index 0000000..a4f0948 --- /dev/null +++ b/week5/src/modules/users/services/user.service.ts @@ -0,0 +1,32 @@ +import { UserData, responseFromUser } from "../dtos/user.dto.js"; +import { + addUser, + getUser, + getUserPreferencesByUserId, + setPreference, +} from "../repositories/user.repository.js"; + +export const userSignUp = async (data: UserData) => { + const joinUserId = await addUser({ + email: data.email, + name: data.name, + gender: data.gender, + birth: data.birth, // 이미 Date 객체입니다. + address: data.address, + detailAddress: data.detailAddress, + phoneNumber: data.phoneNumber, + }); + + if (joinUserId === null) { + throw new Error("이미 존재하는 이메일입니다."); + } + + for (const preference of data.preferences) { + await setPreference(joinUserId, preference); + } + + const user = await getUser(joinUserId); + const preferences = await getUserPreferencesByUserId(joinUserId); + + return responseFromUser({ user, preferences }); +}; \ No newline at end of file From bd9456af5377d63564143e474a66e8e65c7c0384 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 30 Apr 2026 20:58:55 +0900 Subject: [PATCH 03/20] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=EC=A7=80?= =?UTF-8?q?=EC=97=AD=EC=97=90=20=EA=B0=80=EA=B2=8C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- week5/src/index.ts | 2 + .../stores/controllers/store.controller.ts | 10 ++++ week5/src/modules/stores/dtos/store.dto.ts | 26 +++++++++ .../stores/repositories/store.repository.ts | 56 +++++++++++++++++++ .../modules/stores/services/store.service.ts | 19 +++++++ 5 files changed, 113 insertions(+) create mode 100644 week5/src/modules/stores/controllers/store.controller.ts create mode 100644 week5/src/modules/stores/dtos/store.dto.ts create mode 100644 week5/src/modules/stores/repositories/store.repository.ts create mode 100644 week5/src/modules/stores/services/store.service.ts diff --git a/week5/src/index.ts b/week5/src/index.ts index cd14f81..ef7a8a7 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -4,6 +4,7 @@ dotenv.config(); // 환경 변수를 가장 먼저 로드해야 DB pool 생성 import express, { Express, Request, Response } from "express"; import cors from "cors"; import { handleUserSignUp } from "./modules/users/controllers/user.controller.js"; +import { handleStoreAdd } from "./modules/stores/controllers/store.controller.js"; const app: Express = express(); const port = process.env.PORT || 3000; @@ -20,6 +21,7 @@ app.get("/", (req: Request, res: Response) => { }); app.post("/api/v1/users/signup", handleUserSignUp); +app.post("/api/v1/stores", handleStoreAdd); // 1-1. 특정 지역에 가게 추가 // 4. 서버 시작 app.listen(port, () => { diff --git a/week5/src/modules/stores/controllers/store.controller.ts b/week5/src/modules/stores/controllers/store.controller.ts new file mode 100644 index 0000000..a74947e --- /dev/null +++ b/week5/src/modules/stores/controllers/store.controller.ts @@ -0,0 +1,10 @@ +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; +import { bodyToStore, StoreAddRequest } from "../dtos/store.dto.js"; +import { storeAdd } from "../services/store.service.js"; + +// 특정 지역에 가게 추가하기 +export const handleStoreAdd = async (req: Request, res: Response, next: NextFunction) => { + const store = await storeAdd(bodyToStore(req.body as StoreAddRequest)); + res.status(StatusCodes.CREATED).json({ result: store }); +}; diff --git a/week5/src/modules/stores/dtos/store.dto.ts b/week5/src/modules/stores/dtos/store.dto.ts new file mode 100644 index 0000000..2088232 --- /dev/null +++ b/week5/src/modules/stores/dtos/store.dto.ts @@ -0,0 +1,26 @@ +// 가게 추가 요청 타입 +export interface StoreAddRequest { + regionId: number; + name: string; + address?: string; +} + +// bodyToStore: 요청 데이터를 내부 형식으로 변환 +export const bodyToStore = (body: StoreAddRequest) => { + return { + regionId: body.regionId, + name: body.name, + address: body.address || "", + }; +}; + +// responseFromStore: DB 결과를 응답 형식으로 변환 +export const responseFromStore = (store: any) => { + return { + id: store.id, + regionId: store.region_id, + name: store.name, + address: store.address, + score: store.score, + }; +}; diff --git a/week5/src/modules/stores/repositories/store.repository.ts b/week5/src/modules/stores/repositories/store.repository.ts new file mode 100644 index 0000000..2fd6238 --- /dev/null +++ b/week5/src/modules/stores/repositories/store.repository.ts @@ -0,0 +1,56 @@ +import { ResultSetHeader, RowDataPacket } from "mysql2"; +import { pool } from "../../../db.config.js"; + +// 지역 존재 여부 확인 +export const getRegion = async (regionId: number): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM region WHERE id = ?;`, + [regionId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 가게 추가 +export const addStore = async (data: { + regionId: number; + name: string; + address: string; +}): Promise => { + const conn = await pool.getConnection(); + try { + const [result] = await pool.query( + `INSERT INTO store (region_id, name, address) VALUES (?, ?, ?);`, + [data.regionId, data.name, data.address] + ); + return result.insertId; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 가게 정보 조회 +export const getStore = async (storeId: number): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM store WHERE id = ?;`, + [storeId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; diff --git a/week5/src/modules/stores/services/store.service.ts b/week5/src/modules/stores/services/store.service.ts new file mode 100644 index 0000000..3256dd2 --- /dev/null +++ b/week5/src/modules/stores/services/store.service.ts @@ -0,0 +1,19 @@ +import { bodyToStore, responseFromStore, StoreAddRequest } from "../dtos/store.dto.js"; +import { addStore, getRegion, getStore } from "../repositories/store.repository.js"; + +export const storeAdd = async (data: ReturnType) => { + // 지역 존재 여부 확인 + const region = await getRegion(data.regionId); + if (!region) { + throw new Error("존재하지 않는 지역입니다."); + } + + const storeId = await addStore({ + regionId: data.regionId, + name: data.name, + address: data.address, + }); + + const store = await getStore(storeId); + return responseFromStore(store); +}; From a4f030568e772c14108bda23b41de17dcf4f2e8a Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 30 Apr 2026 21:01:58 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat:=20=EA=B0=80=EA=B2=8C=EC=97=90=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- week5/src/index.ts | 2 + .../reviews/controllers/review.controller.ts | 10 ++++ week5/src/modules/reviews/dtos/review.dto.ts | 29 ++++++++++ .../reviews/repositories/review.repository.ts | 57 +++++++++++++++++++ .../reviews/services/review.service.ts | 20 +++++++ 5 files changed, 118 insertions(+) create mode 100644 week5/src/modules/reviews/controllers/review.controller.ts create mode 100644 week5/src/modules/reviews/dtos/review.dto.ts create mode 100644 week5/src/modules/reviews/repositories/review.repository.ts create mode 100644 week5/src/modules/reviews/services/review.service.ts diff --git a/week5/src/index.ts b/week5/src/index.ts index ef7a8a7..730d413 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -5,6 +5,7 @@ import express, { Express, Request, Response } from "express"; import cors from "cors"; import { handleUserSignUp } from "./modules/users/controllers/user.controller.js"; import { handleStoreAdd } from "./modules/stores/controllers/store.controller.js"; +import { handleReviewAdd } from "./modules/reviews/controllers/review.controller.js"; const app: Express = express(); const port = process.env.PORT || 3000; @@ -22,6 +23,7 @@ app.get("/", (req: Request, res: Response) => { app.post("/api/v1/users/signup", handleUserSignUp); app.post("/api/v1/stores", handleStoreAdd); // 1-1. 특정 지역에 가게 추가 +app.post("/api/v1/reviews", handleReviewAdd); // 1-2. 가게에 리뷰 추가 // 4. 서버 시작 app.listen(port, () => { diff --git a/week5/src/modules/reviews/controllers/review.controller.ts b/week5/src/modules/reviews/controllers/review.controller.ts new file mode 100644 index 0000000..3f9ff8a --- /dev/null +++ b/week5/src/modules/reviews/controllers/review.controller.ts @@ -0,0 +1,10 @@ +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; +import { bodyToReview, ReviewAddRequest } from "../dtos/review.dto.js"; +import { reviewAdd } from "../services/review.service.js"; + +// 가게에 리뷰 추가하기 +export const handleReviewAdd = async (req: Request, res: Response, next: NextFunction) => { + const review = await reviewAdd(bodyToReview(req.body as ReviewAddRequest)); + res.status(StatusCodes.CREATED).json({ result: review }); +}; diff --git a/week5/src/modules/reviews/dtos/review.dto.ts b/week5/src/modules/reviews/dtos/review.dto.ts new file mode 100644 index 0000000..9e44c32 --- /dev/null +++ b/week5/src/modules/reviews/dtos/review.dto.ts @@ -0,0 +1,29 @@ +// 리뷰 추가 요청 타입 +export interface ReviewAddRequest { + userId: number; + storeId: number; + body: string; + score: number; +} + +// bodyToReview: 요청 데이터를 내부 형식으로 변환 +export const bodyToReview = (body: ReviewAddRequest) => { + return { + userId: body.userId, + storeId: body.storeId, + body: body.body, + score: body.score, + }; +}; + +// responseFromReview: DB 결과를 응답 형식으로 변환 +export const responseFromReview = (review: any) => { + return { + id: review.id, + userId: review.user_id, + storeId: review.store_id, + body: review.body, + score: review.score, + createdAt: review.created_at, + }; +}; diff --git a/week5/src/modules/reviews/repositories/review.repository.ts b/week5/src/modules/reviews/repositories/review.repository.ts new file mode 100644 index 0000000..2801a6c --- /dev/null +++ b/week5/src/modules/reviews/repositories/review.repository.ts @@ -0,0 +1,57 @@ +import { ResultSetHeader, RowDataPacket } from "mysql2"; +import { pool } from "../../../db.config.js"; + +// 가게 존재 여부 확인 +export const getStoreById = async (storeId: number): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM store WHERE id = ?;`, + [storeId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 리뷰 추가 +export const addReview = async (data: { + userId: number; + storeId: number; + body: string; + score: number; +}): Promise => { + const conn = await pool.getConnection(); + try { + const [result] = await pool.query( + `INSERT INTO review (user_id, store_id, body, score) VALUES (?, ?, ?, ?);`, + [data.userId, data.storeId, data.body, data.score] + ); + return result.insertId; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 리뷰 조회 +export const getReview = async (reviewId: number): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM review WHERE id = ?;`, + [reviewId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; diff --git a/week5/src/modules/reviews/services/review.service.ts b/week5/src/modules/reviews/services/review.service.ts new file mode 100644 index 0000000..8f25f4b --- /dev/null +++ b/week5/src/modules/reviews/services/review.service.ts @@ -0,0 +1,20 @@ +import { bodyToReview, responseFromReview } from "../dtos/review.dto.js"; +import { addReview, getReview, getStoreById } from "../repositories/review.repository.js"; + +export const reviewAdd = async (data: ReturnType) => { + // 가게 존재 여부 확인 + const store = await getStoreById(data.storeId); + if (!store) { + throw new Error("존재하지 않는 가게입니다."); + } + + const reviewId = await addReview({ + userId: data.userId, + storeId: data.storeId, + body: data.body, + score: data.score, + }); + + const review = await getReview(reviewId); + return responseFromReview(review); +}; From 9d469f4a301514dd33fb4f95268e6c750788efeb Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 30 Apr 2026 21:04:21 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20=EA=B0=80=EA=B2=8C=EC=97=90=20?= =?UTF-8?q?=EB=AF=B8=EC=85=98=20=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/index.ts | 2 + .../controllers/mission.controller.ts | 10 ++++ .../src/modules/missions/dtos/mission.dto.ts | 28 +++++++++ .../repositories/mission.repository.ts | 57 +++++++++++++++++++ .../missions/services/mission.service.ts | 20 +++++++ 5 files changed, 117 insertions(+) create mode 100644 week5/src/modules/missions/controllers/mission.controller.ts create mode 100644 week5/src/modules/missions/dtos/mission.dto.ts create mode 100644 week5/src/modules/missions/repositories/mission.repository.ts create mode 100644 week5/src/modules/missions/services/mission.service.ts diff --git a/week5/src/index.ts b/week5/src/index.ts index 730d413..c08e588 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -6,6 +6,7 @@ import cors from "cors"; import { handleUserSignUp } from "./modules/users/controllers/user.controller.js"; import { handleStoreAdd } from "./modules/stores/controllers/store.controller.js"; import { handleReviewAdd } from "./modules/reviews/controllers/review.controller.js"; +import { handleMissionAdd } from "./modules/missions/controllers/mission.controller.js"; const app: Express = express(); const port = process.env.PORT || 3000; @@ -24,6 +25,7 @@ app.get("/", (req: Request, res: Response) => { app.post("/api/v1/users/signup", handleUserSignUp); app.post("/api/v1/stores", handleStoreAdd); // 1-1. 특정 지역에 가게 추가 app.post("/api/v1/reviews", handleReviewAdd); // 1-2. 가게에 리뷰 추가 +app.post("/api/v1/missions", handleMissionAdd); // 1-3. 가게에 미션 추가 // 4. 서버 시작 app.listen(port, () => { diff --git a/week5/src/modules/missions/controllers/mission.controller.ts b/week5/src/modules/missions/controllers/mission.controller.ts new file mode 100644 index 0000000..394dee6 --- /dev/null +++ b/week5/src/modules/missions/controllers/mission.controller.ts @@ -0,0 +1,10 @@ +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; +import { bodyToMission, MissionAddRequest } from "../dtos/mission.dto.js"; +import { missionAdd } from "../services/mission.service.js"; + +// 가게에 미션 추가하기 +export const handleMissionAdd = async (req: Request, res: Response, next: NextFunction) => { + const mission = await missionAdd(bodyToMission(req.body as MissionAddRequest)); + res.status(StatusCodes.CREATED).json({ result: mission }); +}; diff --git a/week5/src/modules/missions/dtos/mission.dto.ts b/week5/src/modules/missions/dtos/mission.dto.ts new file mode 100644 index 0000000..c9149f7 --- /dev/null +++ b/week5/src/modules/missions/dtos/mission.dto.ts @@ -0,0 +1,28 @@ +// 미션 추가 요청 타입 +export interface MissionAddRequest { + storeId: number; + reward: number; + deadline: string; + missionSpec: string; +} + +// bodyToMission: 요청 데이터를 내부 형식으로 변환 +export const bodyToMission = (body: MissionAddRequest) => { + return { + storeId: body.storeId, + reward: body.reward, + deadline: new Date(body.deadline), + missionSpec: body.missionSpec, + }; +}; + +// responseFromMission: DB 결과를 응답 형식으로 변환 +export const responseFromMission = (mission: any) => { + return { + id: mission.id, + storeId: mission.store_id, + reward: mission.reward, + deadline: mission.deadline, + missionSpec: mission.mission_spec, + }; +}; diff --git a/week5/src/modules/missions/repositories/mission.repository.ts b/week5/src/modules/missions/repositories/mission.repository.ts new file mode 100644 index 0000000..398c099 --- /dev/null +++ b/week5/src/modules/missions/repositories/mission.repository.ts @@ -0,0 +1,57 @@ +import { ResultSetHeader, RowDataPacket } from "mysql2"; +import { pool } from "../../../db.config.js"; + +// 가게 존재 여부 확인 +export const getStoreById = async (storeId: number): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM store WHERE id = ?;`, + [storeId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 미션 추가 +export const addMission = async (data: { + storeId: number; + reward: number; + deadline: Date; + missionSpec: string; +}): Promise => { + const conn = await pool.getConnection(); + try { + const [result] = await pool.query( + `INSERT INTO mission (store_id, reward, deadline, mission_spec) VALUES (?, ?, ?, ?);`, + [data.storeId, data.reward, data.deadline, data.missionSpec] + ); + return result.insertId; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 미션 조회 +export const getMission = async (missionId: number): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM mission WHERE id = ?;`, + [missionId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; diff --git a/week5/src/modules/missions/services/mission.service.ts b/week5/src/modules/missions/services/mission.service.ts new file mode 100644 index 0000000..a8ad098 --- /dev/null +++ b/week5/src/modules/missions/services/mission.service.ts @@ -0,0 +1,20 @@ +import { bodyToMission, responseFromMission } from "../dtos/mission.dto.js"; +import { addMission, getMission, getStoreById } from "../repositories/mission.repository.js"; + +export const missionAdd = async (data: ReturnType) => { + // 가게 존재 여부 확인 + const store = await getStoreById(data.storeId); + if (!store) { + throw new Error("존재하지 않는 가게입니다."); + } + + const missionId = await addMission({ + storeId: data.storeId, + reward: data.reward, + deadline: data.deadline, + missionSpec: data.missionSpec, + }); + + const mission = await getMission(missionId); + return responseFromMission(mission); +}; From ab0f44c4ab0d68754fe9d05fe902ddc0806b215e Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 30 Apr 2026 21:07:06 +0900 Subject: [PATCH 06/20] =?UTF-8?q?feat:=20=EB=AF=B8=EC=85=98=20=EB=8F=84?= =?UTF-8?q?=EC=A0=84=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/index.ts | 5 +- .../controllers/mission.controller.ts | 8 +++ .../modules/missions/dtos/user-mission.dto.ts | 24 ++++++++ .../repositories/mission.repository.ts | 56 +++++++++++++++++++ .../missions/services/user-mission.service.ts | 29 ++++++++++ 5 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 week5/src/modules/missions/dtos/user-mission.dto.ts create mode 100644 week5/src/modules/missions/services/user-mission.service.ts diff --git a/week5/src/index.ts b/week5/src/index.ts index c08e588..a897a97 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -6,7 +6,7 @@ import cors from "cors"; import { handleUserSignUp } from "./modules/users/controllers/user.controller.js"; import { handleStoreAdd } from "./modules/stores/controllers/store.controller.js"; import { handleReviewAdd } from "./modules/reviews/controllers/review.controller.js"; -import { handleMissionAdd } from "./modules/missions/controllers/mission.controller.js"; +import { handleMissionAdd, handleUserMissionAdd } from "./modules/missions/controllers/mission.controller.js"; const app: Express = express(); const port = process.env.PORT || 3000; @@ -25,7 +25,8 @@ app.get("/", (req: Request, res: Response) => { app.post("/api/v1/users/signup", handleUserSignUp); app.post("/api/v1/stores", handleStoreAdd); // 1-1. 특정 지역에 가게 추가 app.post("/api/v1/reviews", handleReviewAdd); // 1-2. 가게에 리뷰 추가 -app.post("/api/v1/missions", handleMissionAdd); // 1-3. 가게에 미션 추가 +app.post("/api/v1/missions", handleMissionAdd); // 1-3. 가게에 미션 추가 +app.post("/api/v1/user-missions", handleUserMissionAdd); // 1-4. 미션 도전하기 // 4. 서버 시작 app.listen(port, () => { diff --git a/week5/src/modules/missions/controllers/mission.controller.ts b/week5/src/modules/missions/controllers/mission.controller.ts index 394dee6..a7e5944 100644 --- a/week5/src/modules/missions/controllers/mission.controller.ts +++ b/week5/src/modules/missions/controllers/mission.controller.ts @@ -2,9 +2,17 @@ import { Request, Response, NextFunction } from "express"; import { StatusCodes } from "http-status-codes"; import { bodyToMission, MissionAddRequest } from "../dtos/mission.dto.js"; import { missionAdd } from "../services/mission.service.js"; +import { bodyToUserMission, UserMissionAddRequest } from "../dtos/user-mission.dto.js"; +import { userMissionAdd } from "../services/user-mission.service.js"; // 가게에 미션 추가하기 export const handleMissionAdd = async (req: Request, res: Response, next: NextFunction) => { const mission = await missionAdd(bodyToMission(req.body as MissionAddRequest)); res.status(StatusCodes.CREATED).json({ result: mission }); }; + +// 미션 도전하기 +export const handleUserMissionAdd = async (req: Request, res: Response, next: NextFunction) => { + const userMission = await userMissionAdd(bodyToUserMission(req.body as UserMissionAddRequest)); + res.status(StatusCodes.CREATED).json({ result: userMission }); +}; diff --git a/week5/src/modules/missions/dtos/user-mission.dto.ts b/week5/src/modules/missions/dtos/user-mission.dto.ts new file mode 100644 index 0000000..45f449e --- /dev/null +++ b/week5/src/modules/missions/dtos/user-mission.dto.ts @@ -0,0 +1,24 @@ +// 미션 도전 요청 타입 +export interface UserMissionAddRequest { + userId: number; + missionId: number; +} + +// bodyToUserMission: 요청 데이터를 내부 형식으로 변환 +export const bodyToUserMission = (body: UserMissionAddRequest) => { + return { + userId: body.userId, + missionId: body.missionId, + }; +}; + +// responseFromUserMission: DB 결과를 응답 형식으로 변환 +export const responseFromUserMission = (userMission: any) => { + return { + id: userMission.id, + userId: userMission.user_id, + missionId: userMission.mission_id, + status: userMission.status, + createdAt: userMission.created_at, + }; +}; diff --git a/week5/src/modules/missions/repositories/mission.repository.ts b/week5/src/modules/missions/repositories/mission.repository.ts index 398c099..5cd3a95 100644 --- a/week5/src/modules/missions/repositories/mission.repository.ts +++ b/week5/src/modules/missions/repositories/mission.repository.ts @@ -55,3 +55,59 @@ export const getMission = async (missionId: number): Promise => { conn.release(); } }; + +// 이미 도전 중인 미션인지 확인 +export const getUserMissionByUserAndMission = async ( + userId: number, + missionId: number +): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM user_mission WHERE user_id = ? AND mission_id = ? AND status = '도전중';`, + [userId, missionId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// 미션 도전 추가 +export const addUserMission = async (data: { + userId: number; + missionId: number; +}): Promise => { + const conn = await pool.getConnection(); + try { + const [result] = await pool.query( + `INSERT INTO user_mission (user_id, mission_id) VALUES (?, ?);`, + [data.userId, data.missionId] + ); + return result.insertId; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; + +// user_mission 조회 +export const getUserMission = async (userMissionId: number): Promise => { + const conn = await pool.getConnection(); + try { + const [rows] = await pool.query( + `SELECT * FROM user_mission WHERE id = ?;`, + [userMissionId] + ); + if (rows.length === 0) return null; + return rows[0]; + } catch (err) { + throw new Error(`오류가 발생했어요: ${err}`); + } finally { + conn.release(); + } +}; diff --git a/week5/src/modules/missions/services/user-mission.service.ts b/week5/src/modules/missions/services/user-mission.service.ts new file mode 100644 index 0000000..859d185 --- /dev/null +++ b/week5/src/modules/missions/services/user-mission.service.ts @@ -0,0 +1,29 @@ +import { bodyToUserMission, responseFromUserMission } from "../dtos/user-mission.dto.js"; +import { + getMission, + getUserMissionByUserAndMission, + addUserMission, + getUserMission, +} from "../repositories/mission.repository.js"; + +export const userMissionAdd = async (data: ReturnType) => { + // 미션 존재 여부 확인 + const mission = await getMission(data.missionId); + if (!mission) { + throw new Error("존재하지 않는 미션입니다."); + } + + // 이미 도전 중인지 확인 + const existing = await getUserMissionByUserAndMission(data.userId, data.missionId); + if (existing) { + throw new Error("이미 도전 중인 미션입니다."); + } + + const userMissionId = await addUserMission({ + userId: data.userId, + missionId: data.missionId, + }); + + const userMission = await getUserMission(userMissionId); + return responseFromUserMission(userMission); +}; From d3972646a0b47b4b098d496d0d60f9dd6ed26520 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 10:33:27 +0900 Subject: [PATCH 07/20] =?UTF-8?q?chore:=20TSOA=20=EC=84=A4=EC=B9=98=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B8=B0=EB=B3=B8=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/package-lock.json | 1685 ++++++++++++++++++++++++++++++++++++++- week5/package.json | 9 +- week5/tsconfig.json | 4 + week5/tsoa.json | 14 + 4 files changed, 1699 insertions(+), 13 deletions(-) create mode 100644 week5/tsoa.json diff --git a/week5/package-lock.json b/week5/package-lock.json index c5919eb..36ae324 100644 --- a/week5/package-lock.json +++ b/week5/package-lock.json @@ -9,7 +9,9 @@ "dotenv": "^17.4.2", "express": "^5.2.1", "http-status-codes": "^2.3.0", - "mysql2": "^3.22.3" + "mysql2": "^3.22.3", + "reflect-metadata": "^0.2.2", + "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { "@types/cors": "^2.8.19", @@ -463,11 +465,727 @@ "node": ">=18" } }, + "node_modules/@hapi/accept": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz", + "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/ammo": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz", + "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/b64": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz", + "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/bounce": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.2.tgz", + "integrity": "sha512-d0XmlTi3H9HFDHhQLjg4F4auL1EY3Wqj7j7/hGDhFFe6xAbnm3qiGrXeT93zZnPH8gH+SKAFYiRzu26xkXcH3g==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/call": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz", + "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/catbox": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz", + "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/podium": "^5.0.0", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/catbox-memory": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.2.tgz", + "integrity": "sha512-H1l4ugoFW/ZRkqeFrIo8p1rWN0PA4MDTfu4JmcoNDvnY975o29mqoZblqFTotxNHlEkMPpIiIBJTV+Mbi+aF0g==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/content": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.2.tgz", + "integrity": "sha512-OKyCOTjNR1hftwSjk9ueyAQTw8AwapvzBrPIWMGn39vhR5PmqLdYFmLc35bsSBye7gSMnlkXfc679bUdMIcRyQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.0" + } + }, + "node_modules/@hapi/cryptiles": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.3.tgz", + "integrity": "sha512-r6VKalpbMHz4ci3gFjFysBmhwCg70RpYZy6OkjEpdXzAYnYFX5XsW7n4YMJvuIYpnMwLxGUjK/cBhA7X3JDvXw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/file": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz", + "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hapi": { + "version": "21.4.9", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.4.9.tgz", + "integrity": "sha512-YnecZOVx2AD08VvPl0ZaFS0MjEHqg+InGRmBRli731ct+VwI++dpu3BIYA1Z4SMr6HUAnpyvbQ1aq5woe3fBWg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/accept": "^6.0.3", + "@hapi/ammo": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.2", + "@hapi/call": "^9.0.1", + "@hapi/catbox": "^12.1.1", + "@hapi/catbox-memory": "^6.0.2", + "@hapi/heavy": "^8.0.1", + "@hapi/hoek": "^11.0.7", + "@hapi/mimos": "^7.0.1", + "@hapi/podium": "^5.0.2", + "@hapi/shot": "^6.0.2", + "@hapi/somever": "^4.1.1", + "@hapi/statehood": "^8.2.1", + "@hapi/subtext": "^8.1.3", + "@hapi/teamwork": "^6.0.1", + "@hapi/topo": "^6.0.2", + "@hapi/validate": "^2.0.1" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@hapi/heavy": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz", + "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/iron": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz", + "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/mimos": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz", + "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "mime-db": "^1.52.0" + } + }, + "node_modules/@hapi/nigel": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz", + "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/vise": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/pez": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.1.tgz", + "integrity": "sha512-yg2OS1tC0S1sHXvhUtWsfRn6lrKl9jKtRhZ+EI0woOW/gqX5vM2PZ1459ypCvCYDRLJ9nIyueeEH5MJV1ZDqIg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/content": "^6.0.1", + "@hapi/hoek": "^11.0.7", + "@hapi/nigel": "^5.0.1" + } + }, + "node_modules/@hapi/podium": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.2.tgz", + "integrity": "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/teamwork": "^6.0.0", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/shot": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.2.tgz", + "integrity": "sha512-WKK1ShfJTrL1oXC0skoIZQYzvLsyMDEF8lfcWuQBjpjCN29qivr9U36ld1z0nt6edvzv28etNMOqUF4klnHryw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/somever": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz", + "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/bounce": "^3.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/statehood": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.2.1.tgz", + "integrity": "sha512-xf72TG/QINW26jUu+uL5H+crE1o8GplIgfPWwPZhnAGJzetIVAQEQYvzq+C0aEVHg5/lMMtQ+L9UryuSa5Yjkg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/iron": "^7.0.1", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/subtext": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.3.tgz", + "integrity": "sha512-WTpEZQjBP3UJ3gGunNl3w5Ao1EOJsuu2vttZ2KEcG+csSLxc0dI6VIkl2md2jDlHiQ2ARAoqdSUScy05A/NHtA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/content": "^6.0.2", + "@hapi/file": "^3.0.0", + "@hapi/hoek": "^11.0.7", + "@hapi/pez": "^6.1.1", + "@hapi/wreck": "^18.1.1" + } + }, + "node_modules/@hapi/teamwork": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.1.tgz", + "integrity": "sha512-52OXRslUfYwXAOG8k58f2h2ngXYQGP0x5RPOo+eWA/FtyLgHjGMrE3+e9LSXP/0q2YfHAK5wj9aA9DTy1K+kyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/vise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz", + "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/wreck": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.1.tgz", + "integrity": "sha512-UwTeGBfAnB/1mkw4gD6IQGI/bgMu7iGmqgT8K+xxye3z4ZHhCZlmS2wuHBJmENhBJSKqvoYzJ71ds3Xfq4gofQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@tsoa/cli": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tsoa/cli/-/cli-7.0.0-alpha.0.tgz", + "integrity": "sha512-fCBWv6F20qrpwFh5X+vaZs38Bh/5cVwKPhvG/uArR+crZgEowyNl76unLCmYdvycES9Y6g/TKFLagG8qojSNuQ==", + "license": "MIT", + "dependencies": { + "@tsoa/runtime": "^7.0.0-alpha.0", + "@types/multer": "^1.4.12", + "fs-extra": "^11.2.0", + "glob": "^10.3.10", + "handlebars": "^4.7.8", + "merge-anything": "^5.1.7", + "minimatch": "^9.0.1", + "ts-deepmerge": "^7.0.2", + "typescript": "^5.7.2", + "validator": "^13.12.0", + "yaml": "^2.6.1", + "yargs": "^17.7.1" + }, + "bin": { + "tsoa": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, + "node_modules/@tsoa/cli/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==", + "license": "MIT" + }, + "node_modules/@tsoa/cli/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tsoa/cli/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tsoa/cli/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@tsoa/runtime": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tsoa/runtime/-/runtime-7.0.0-alpha.0.tgz", + "integrity": "sha512-zlWYz2bLfaN6WtFoIbLBEAyVhKG4IQKJ9QPzeRFFKeAsh5zYN4/ocnd14XyWs4ehY9TdtTnma2drW89aYNSRYw==", + "license": "MIT", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hapi": "^21.3.12", + "@types/koa": "^2.15.0", + "@types/multer": "^1.4.12", + "express": "^4.21.2", + "reflect-metadata": "^0.2.2", + "validator": "^13.12.0" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, + "node_modules/@tsoa/runtime/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@tsoa/runtime/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/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==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@tsoa/runtime/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@tsoa/runtime/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tsoa/runtime/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@tsoa/runtime/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/@tsoa/runtime/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@tsoa/runtime/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@tsoa/runtime/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -478,12 +1196,29 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/content-disposition": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz", + "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", + "license": "MIT" + }, + "node_modules/@types/cookies": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz", + "integrity": "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -508,7 +1243,6 @@ "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -520,7 +1254,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -529,13 +1262,58 @@ "@types/send": "*" } }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, "license": "MIT" }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "license": "MIT" + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "license": "MIT", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.9.tgz", + "integrity": "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==", + "license": "MIT", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "25.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", @@ -549,21 +1327,18 @@ "version": "6.15.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -573,7 +1348,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -593,6 +1367,30 @@ "node": ">= 0.6" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -607,6 +1405,12 @@ "node": ">= 8" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -752,6 +1556,111 @@ "fsevents": "~2.3.2" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -809,6 +1718,20 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -844,6 +1767,16 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/dotenv": { "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", @@ -870,12 +1803,24 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -957,6 +1902,15 @@ "@esbuild/win32-x64": "0.27.7" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1049,6 +2003,22 @@ "url": "https://opencollective.com/express" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1067,6 +2037,20 @@ "node": ">= 0.8" } }, + "node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1100,6 +2084,15 @@ "is-property": "^1.0.2" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1150,6 +2143,27 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1163,6 +2177,36 @@ "node": ">= 6" } }, + "node_modules/glob/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==", + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1175,6 +2219,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1296,6 +2367,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1331,12 +2411,63 @@ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "license": "MIT" }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/lru.min": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", @@ -1370,6 +2501,21 @@ "node": ">= 0.8" } }, + "node_modules/merge-anything": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", + "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -1382,6 +2528,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -1423,6 +2590,24 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1472,6 +2657,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/nodemon": { "version": "3.1.14", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", @@ -1553,6 +2744,12 @@ "wrappy": "1" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1562,6 +2759,31 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -1657,6 +2879,21 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -1683,6 +2920,26 @@ "node": ">= 18" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1753,6 +3010,27 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -1825,6 +3103,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -1838,6 +3128,15 @@ "node": ">=10" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sql-escaper": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", @@ -1862,6 +3161,102 @@ "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1907,6 +3302,32 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/ts-deepmerge": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz", + "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/tsoa": { + "version": "7.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/tsoa/-/tsoa-7.0.0-alpha.0.tgz", + "integrity": "sha512-o5h2DD1IKa2GF728BHYDL2uSX57a44sX8BLFScR4KbD0xlOmgsSDECneJmouDxf9MJdjHHWc+I1S3sGK82B6kQ==", + "license": "MIT", + "dependencies": { + "@tsoa/cli": "^7.0.0-alpha.0", + "@tsoa/runtime": "^7.0.0-alpha.0" + }, + "bin": { + "tsoa": "dist/cli.js" + }, + "engines": { + "node": ">=18.0.0", + "yarn": ">=1.9.4" + } + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -1955,6 +3376,19 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -1968,6 +3402,15 @@ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1977,6 +3420,24 @@ "node": ">= 0.8" } }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1986,11 +3447,215 @@ "node": ">= 0.8" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/week5/package.json b/week5/package.json index a2396fa..26ddbba 100644 --- a/week5/package.json +++ b/week5/package.json @@ -2,15 +2,18 @@ "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "tsx src/index.ts", - "dev": "nodemon --exec tsx src/index.ts" + "tsoa": "tsoa spec-and-routes", + "start": "npm run tsoa && tsx src/index.ts", + "dev": "npm run tsoa && nodemon --watch 'src/**/*.ts' --ignore 'src/routes.ts' --exec 'npm run tsoa && tsx src/index.ts'" }, "dependencies": { "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", "http-status-codes": "^2.3.0", - "mysql2": "^3.22.3" + "mysql2": "^3.22.3", + "reflect-metadata": "^0.2.2", + "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/week5/tsconfig.json b/week5/tsconfig.json index 476a7fc..5c3fb26 100644 --- a/week5/tsconfig.json +++ b/week5/tsconfig.json @@ -10,6 +10,10 @@ "moduleResolution": "NodeNext", "lib": ["ESNext"], // 최신 자바스크립트 표준 기능을 사용할 수 있게 해줘요. + /* 데코레이터 설정 (TSOA 필수!) */ + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + /* 엄격한 검사 옵션 (에러를 미리 잡는 핵심!) */ "strict": true, "noUncheckedIndexedAccess": true, diff --git a/week5/tsoa.json b/week5/tsoa.json new file mode 100644 index 0000000..53eaeaf --- /dev/null +++ b/week5/tsoa.json @@ -0,0 +1,14 @@ +{ + "entryFile": "src/index.ts", + "noImplicitAdditionalProperties": "throw-on-extras", + "controllerPathGlobs": ["src/**/*.controller.ts"], + "spec": { + "outputDirectory": "src", + "specVersion": 3, + "basePath": "/api/v1" + }, + "routes": { + "routesDir": "src", + "esm": true + } +} From 0f99ec4939e2dc8521cf6f2de8e4f365531b1b9e Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 10:34:35 +0900 Subject: [PATCH 08/20] =?UTF-8?q?refactor:=20controller=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20TSOA=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/mission.controller.ts | 28 +++++++++---------- .../controllers/user-mission.controller.ts | 16 +++++++++++ .../reviews/controllers/review.controller.ts | 20 ++++++++----- .../stores/controllers/store.controller.ts | 20 ++++++++----- .../users/controllers/user.controller.ts | 25 +++++++++-------- 5 files changed, 68 insertions(+), 41 deletions(-) create mode 100644 week5/src/modules/missions/controllers/user-mission.controller.ts diff --git a/week5/src/modules/missions/controllers/mission.controller.ts b/week5/src/modules/missions/controllers/mission.controller.ts index a7e5944..5ea722f 100644 --- a/week5/src/modules/missions/controllers/mission.controller.ts +++ b/week5/src/modules/missions/controllers/mission.controller.ts @@ -1,18 +1,16 @@ -import { Request, Response, NextFunction } from "express"; -import { StatusCodes } from "http-status-codes"; +import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; import { bodyToMission, MissionAddRequest } from "../dtos/mission.dto.js"; import { missionAdd } from "../services/mission.service.js"; -import { bodyToUserMission, UserMissionAddRequest } from "../dtos/user-mission.dto.js"; -import { userMissionAdd } from "../services/user-mission.service.js"; -// 가게에 미션 추가하기 -export const handleMissionAdd = async (req: Request, res: Response, next: NextFunction) => { - const mission = await missionAdd(bodyToMission(req.body as MissionAddRequest)); - res.status(StatusCodes.CREATED).json({ result: mission }); -}; - -// 미션 도전하기 -export const handleUserMissionAdd = async (req: Request, res: Response, next: NextFunction) => { - const userMission = await userMissionAdd(bodyToUserMission(req.body as UserMissionAddRequest)); - res.status(StatusCodes.CREATED).json({ result: userMission }); -}; +@Route("missions") +@Tags("Missions") +export class MissionController extends Controller { + // 가게에 미션 추가하기 + @Post() + @SuccessResponse("201", "Created") + public async addMission(@Body() body: MissionAddRequest): Promise<{ result: unknown }> { + this.setStatus(201); + const mission = await missionAdd(bodyToMission(body)); + return { result: mission }; + } +} diff --git a/week5/src/modules/missions/controllers/user-mission.controller.ts b/week5/src/modules/missions/controllers/user-mission.controller.ts new file mode 100644 index 0000000..0e7815a --- /dev/null +++ b/week5/src/modules/missions/controllers/user-mission.controller.ts @@ -0,0 +1,16 @@ +import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; +import { bodyToUserMission, UserMissionAddRequest } from "../dtos/user-mission.dto.js"; +import { userMissionAdd } from "../services/user-mission.service.js"; + +@Route("user-missions") +@Tags("UserMissions") +export class UserMissionController extends Controller { + // 미션 도전하기 + @Post() + @SuccessResponse("201", "Created") + public async addUserMission(@Body() body: UserMissionAddRequest): Promise<{ result: unknown }> { + this.setStatus(201); + const userMission = await userMissionAdd(bodyToUserMission(body)); + return { result: userMission }; + } +} diff --git a/week5/src/modules/reviews/controllers/review.controller.ts b/week5/src/modules/reviews/controllers/review.controller.ts index 3f9ff8a..ccd7e9c 100644 --- a/week5/src/modules/reviews/controllers/review.controller.ts +++ b/week5/src/modules/reviews/controllers/review.controller.ts @@ -1,10 +1,16 @@ -import { Request, Response, NextFunction } from "express"; -import { StatusCodes } from "http-status-codes"; +import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; import { bodyToReview, ReviewAddRequest } from "../dtos/review.dto.js"; import { reviewAdd } from "../services/review.service.js"; -// 가게에 리뷰 추가하기 -export const handleReviewAdd = async (req: Request, res: Response, next: NextFunction) => { - const review = await reviewAdd(bodyToReview(req.body as ReviewAddRequest)); - res.status(StatusCodes.CREATED).json({ result: review }); -}; +@Route("reviews") +@Tags("Reviews") +export class ReviewController extends Controller { + // 가게에 리뷰 추가하기 + @Post() + @SuccessResponse("201", "Created") + public async addReview(@Body() body: ReviewAddRequest): Promise<{ result: unknown }> { + this.setStatus(201); + const review = await reviewAdd(bodyToReview(body)); + return { result: review }; + } +} diff --git a/week5/src/modules/stores/controllers/store.controller.ts b/week5/src/modules/stores/controllers/store.controller.ts index a74947e..1e8f121 100644 --- a/week5/src/modules/stores/controllers/store.controller.ts +++ b/week5/src/modules/stores/controllers/store.controller.ts @@ -1,10 +1,16 @@ -import { Request, Response, NextFunction } from "express"; -import { StatusCodes } from "http-status-codes"; +import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; import { bodyToStore, StoreAddRequest } from "../dtos/store.dto.js"; import { storeAdd } from "../services/store.service.js"; -// 특정 지역에 가게 추가하기 -export const handleStoreAdd = async (req: Request, res: Response, next: NextFunction) => { - const store = await storeAdd(bodyToStore(req.body as StoreAddRequest)); - res.status(StatusCodes.CREATED).json({ result: store }); -}; +@Route("stores") +@Tags("Stores") +export class StoreController extends Controller { + // 특정 지역에 가게 추가하기 + @Post() + @SuccessResponse("201", "Created") + public async addStore(@Body() body: StoreAddRequest): Promise<{ result: unknown }> { + this.setStatus(201); + const store = await storeAdd(bodyToStore(body)); + return { result: store }; + } +} diff --git a/week5/src/modules/users/controllers/user.controller.ts b/week5/src/modules/users/controllers/user.controller.ts index b076e95..f47bbcc 100644 --- a/week5/src/modules/users/controllers/user.controller.ts +++ b/week5/src/modules/users/controllers/user.controller.ts @@ -1,15 +1,16 @@ -import { Request, Response, NextFunction } from "express"; -import { StatusCodes } from "http-status-codes"; +import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; import { bodyToUser, UserSignUpRequest } from "../dtos/user.dto.js"; import { userSignUp } from "../services/user.service.js"; -export const handleUserSignUp = async (req: Request, res: Response, next: NextFunction ) => { - console.log("회원가입을 요청했습니다!"); - console.log("body:", req.body); // 값이 잘 들어오나 확인하기 위한 테스트용 - - //서비스 로직 호출 - const user = await userSignUp(bodyToUser(req.body as UserSignUpRequest)); - - //성공 응답 보내기 - res.status(StatusCodes.OK).json({ result: user }); -}; \ No newline at end of file +@Route("users") +@Tags("Users") +export class UserController extends Controller { + // 회원가입 + @Post("signup") + @SuccessResponse("200", "OK") + public async signUp(@Body() body: UserSignUpRequest): Promise<{ result: unknown }> { + console.log("회원가입을 요청했습니다!"); + const user = await userSignUp(bodyToUser(body)); + return { result: user }; + } +} \ No newline at end of file From 751801bff34f33b62c3a9c9e8fe2ab36988ab8b4 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 10:36:32 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20TSOA=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=90=EB=8F=99=20=EC=83=9D=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20index.ts=20=EB=9D=BC=EC=9A=B0=ED=84=B0=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/index.ts | 33 ++-- week5/src/routes.ts | 253 ++++++++++++++++++++++++++++++ week5/src/swagger.json | 347 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 615 insertions(+), 18 deletions(-) create mode 100644 week5/src/routes.ts create mode 100644 week5/src/swagger.json diff --git a/week5/src/index.ts b/week5/src/index.ts index a897a97..d2f0586 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -1,34 +1,31 @@ +import "reflect-metadata"; import dotenv from "dotenv"; -dotenv.config(); // 환경 변수를 가장 먼저 로드해야 DB pool 생성 전에 적용됩니다. +dotenv.config(); import express, { Express, Request, Response } from "express"; import cors from "cors"; -import { handleUserSignUp } from "./modules/users/controllers/user.controller.js"; -import { handleStoreAdd } from "./modules/stores/controllers/store.controller.js"; -import { handleReviewAdd } from "./modules/reviews/controllers/review.controller.js"; -import { handleMissionAdd, handleUserMissionAdd } from "./modules/missions/controllers/mission.controller.js"; +import { RegisterRoutes } from "./routes.js"; const app: Express = express(); const port = process.env.PORT || 3000; -// 2. 미들웨어 설정 -app.use(cors()); // cors 방식 허용 -app.use(express.static('public')); // 정적 파일 접근 -app.use(express.json()); // request의 본문을 json으로 해석할 수 있도록 함(JSON 형태의 요청 body를 파싱하기 위함) -app.use(express.urlencoded({ extended: false })); // 단순 객체 문자열 형태로 본문 데이터 해석 +// 미들웨어 설정 +app.use(cors()); +app.use(express.static('public')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); -// 3. 기본 라우트 +// 기본 라우트 app.get("/", (req: Request, res: Response) => { res.send("Hello World! This is TypeScript Server!"); }); -app.post("/api/v1/users/signup", handleUserSignUp); -app.post("/api/v1/stores", handleStoreAdd); // 1-1. 특정 지역에 가게 추가 -app.post("/api/v1/reviews", handleReviewAdd); // 1-2. 가게에 리뷰 추가 -app.post("/api/v1/missions", handleMissionAdd); // 1-3. 가게에 미션 추가 -app.post("/api/v1/user-missions", handleUserMissionAdd); // 1-4. 미션 도전하기 +// Express.js에 생성한 엔드 포인트들을 register +const router = express.Router(); +RegisterRoutes(router); +app.use("/api/v1", router); -// 4. 서버 시작 +// 서버 시작 app.listen(port, () => { - console.log(`[server]: Server is running at :${port}`); + console.log(`[server]: Server is running at http://localhost:${port}`); }); \ No newline at end of file diff --git a/week5/src/routes.ts b/week5/src/routes.ts new file mode 100644 index 0000000..0abdab2 --- /dev/null +++ b/week5/src/routes.ts @@ -0,0 +1,253 @@ +/* tslint:disable */ +/* eslint-disable */ +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import type { TsoaRoute } from '@tsoa/runtime'; +import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { UserController } from './modules/users/controllers/user.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { StoreController } from './modules/stores/controllers/store.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { ReviewController } from './modules/reviews/controllers/review.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { UserMissionController } from './modules/missions/controllers/user-mission.controller.js'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { MissionController } from './modules/missions/controllers/mission.controller.js'; +import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express'; + + + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +const models: TsoaRoute.Models = { + "UserSignUpRequest": { + "dataType": "refObject", + "properties": { + "email": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "gender": {"dataType":"string","required":true}, + "birth": {"dataType":"string","required":true}, + "address": {"dataType":"string"}, + "detailAddress": {"dataType":"string"}, + "phoneNumber": {"dataType":"string","required":true}, + "preferences": {"dataType":"array","array":{"dataType":"double"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "StoreAddRequest": { + "dataType": "refObject", + "properties": { + "regionId": {"dataType":"double","required":true}, + "name": {"dataType":"string","required":true}, + "address": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ReviewAddRequest": { + "dataType": "refObject", + "properties": { + "userId": {"dataType":"double","required":true}, + "storeId": {"dataType":"double","required":true}, + "body": {"dataType":"string","required":true}, + "score": {"dataType":"double","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UserMissionAddRequest": { + "dataType": "refObject", + "properties": { + "userId": {"dataType":"double","required":true}, + "missionId": {"dataType":"double","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MissionAddRequest": { + "dataType": "refObject", + "properties": { + "storeId": {"dataType":"double","required":true}, + "reward": {"dataType":"double","required":true}, + "deadline": {"dataType":"string","required":true}, + "missionSpec": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +}; +const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true}); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + + +export function RegisterRoutes(app: Router) { + + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + + + + const argsUserController_signUp: Record = { + body: {"in":"body","name":"body","required":true,"ref":"UserSignUpRequest"}, + }; + app.post('/users/signup', + ...(fetchMiddlewares(UserController)), + ...(fetchMiddlewares(UserController.prototype.signUp)), + + async function UserController_signUp(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_signUp, request, response }); + + const controller = new UserController(); + + await templateService.apiHandler({ + methodName: 'signUp', + controller, + response, + next, + validatedArgs, + successStatus: 200, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsStoreController_addStore: Record = { + body: {"in":"body","name":"body","required":true,"ref":"StoreAddRequest"}, + }; + app.post('/stores', + ...(fetchMiddlewares(StoreController)), + ...(fetchMiddlewares(StoreController.prototype.addStore)), + + async function StoreController_addStore(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_addStore, request, response }); + + const controller = new StoreController(); + + await templateService.apiHandler({ + methodName: 'addStore', + controller, + response, + next, + validatedArgs, + successStatus: 201, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsReviewController_addReview: Record = { + body: {"in":"body","name":"body","required":true,"ref":"ReviewAddRequest"}, + }; + app.post('/reviews', + ...(fetchMiddlewares(ReviewController)), + ...(fetchMiddlewares(ReviewController.prototype.addReview)), + + async function ReviewController_addReview(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsReviewController_addReview, request, response }); + + const controller = new ReviewController(); + + await templateService.apiHandler({ + methodName: 'addReview', + controller, + response, + next, + validatedArgs, + successStatus: 201, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsUserMissionController_addUserMission: Record = { + body: {"in":"body","name":"body","required":true,"ref":"UserMissionAddRequest"}, + }; + app.post('/user-missions', + ...(fetchMiddlewares(UserMissionController)), + ...(fetchMiddlewares(UserMissionController.prototype.addUserMission)), + + async function UserMissionController_addUserMission(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsUserMissionController_addUserMission, request, response }); + + const controller = new UserMissionController(); + + await templateService.apiHandler({ + methodName: 'addUserMission', + controller, + response, + next, + validatedArgs, + successStatus: 201, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + const argsMissionController_addMission: Record = { + body: {"in":"body","name":"body","required":true,"ref":"MissionAddRequest"}, + }; + app.post('/missions', + ...(fetchMiddlewares(MissionController)), + ...(fetchMiddlewares(MissionController.prototype.addMission)), + + async function MissionController_addMission(request: ExRequest, response: ExResponse, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ args: argsMissionController_addMission, request, response }); + + const controller = new MissionController(); + + await templateService.apiHandler({ + methodName: 'addMission', + controller, + response, + next, + validatedArgs, + successStatus: 201, + }); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +} + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/week5/src/swagger.json b/week5/src/swagger.json new file mode 100644 index 0000000..d1eb234 --- /dev/null +++ b/week5/src/swagger.json @@ -0,0 +1,347 @@ +{ + "openapi": "3.0.0", + "components": { + "examples": {}, + "headers": {}, + "parameters": {}, + "requestBodies": {}, + "responses": {}, + "schemas": { + "UserSignUpRequest": { + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "birth": { + "type": "string" + }, + "address": { + "type": "string" + }, + "detailAddress": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "preferences": { + "items": { + "type": "number", + "format": "double" + }, + "type": "array" + } + }, + "required": [ + "email", + "name", + "gender", + "birth", + "phoneNumber", + "preferences" + ], + "type": "object", + "additionalProperties": false + }, + "StoreAddRequest": { + "properties": { + "regionId": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + }, + "address": { + "type": "string" + } + }, + "required": [ + "regionId", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "ReviewAddRequest": { + "properties": { + "userId": { + "type": "number", + "format": "double" + }, + "storeId": { + "type": "number", + "format": "double" + }, + "body": { + "type": "string" + }, + "score": { + "type": "number", + "format": "double" + } + }, + "required": [ + "userId", + "storeId", + "body", + "score" + ], + "type": "object", + "additionalProperties": false + }, + "UserMissionAddRequest": { + "properties": { + "userId": { + "type": "number", + "format": "double" + }, + "missionId": { + "type": "number", + "format": "double" + } + }, + "required": [ + "userId", + "missionId" + ], + "type": "object", + "additionalProperties": false + }, + "MissionAddRequest": { + "properties": { + "storeId": { + "type": "number", + "format": "double" + }, + "reward": { + "type": "number", + "format": "double" + }, + "deadline": { + "type": "string" + }, + "missionSpec": { + "type": "string" + } + }, + "required": [ + "storeId", + "reward", + "deadline", + "missionSpec" + ], + "type": "object", + "additionalProperties": false + } + }, + "securitySchemes": {} + }, + "info": { + "title": "", + "contact": {} + }, + "paths": { + "/users/signup": { + "post": { + "operationId": "SignUp", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "properties": { + "result": {} + }, + "required": [ + "result" + ], + "type": "object" + } + } + } + } + }, + "tags": [ + "Users" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSignUpRequest" + } + } + } + } + } + }, + "/stores": { + "post": { + "operationId": "AddStore", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "properties": { + "result": {} + }, + "required": [ + "result" + ], + "type": "object" + } + } + } + } + }, + "tags": [ + "Stores" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StoreAddRequest" + } + } + } + } + } + }, + "/reviews": { + "post": { + "operationId": "AddReview", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "properties": { + "result": {} + }, + "required": [ + "result" + ], + "type": "object" + } + } + } + } + }, + "tags": [ + "Reviews" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewAddRequest" + } + } + } + } + } + }, + "/user-missions": { + "post": { + "operationId": "AddUserMission", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "properties": { + "result": {} + }, + "required": [ + "result" + ], + "type": "object" + } + } + } + } + }, + "tags": [ + "UserMissions" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserMissionAddRequest" + } + } + } + } + } + }, + "/missions": { + "post": { + "operationId": "AddMission", + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "properties": { + "result": {} + }, + "required": [ + "result" + ], + "type": "object" + } + } + } + } + }, + "tags": [ + "Missions" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MissionAddRequest" + } + } + } + } + } + } + }, + "servers": [ + { + "url": "/api/v1" + } + ] +} \ No newline at end of file From f4c9feb05467f922d39ab2ed38d143ff64376676 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 10:45:06 +0900 Subject: [PATCH 10/20] =?UTF-8?q?feat:=20=EA=B0=81=20dto=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/modules/missions/dtos/mission.dto.ts | 11 ++++++++++- .../modules/missions/dtos/user-mission.dto.ts | 11 ++++++++++- week5/src/modules/reviews/dtos/review.dto.ts | 12 +++++++++++- week5/src/modules/stores/dtos/store.dto.ts | 11 ++++++++++- week5/src/modules/users/dtos/user.dto.ts | 17 +++++++++++++++-- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/week5/src/modules/missions/dtos/mission.dto.ts b/week5/src/modules/missions/dtos/mission.dto.ts index c9149f7..1fc34c4 100644 --- a/week5/src/modules/missions/dtos/mission.dto.ts +++ b/week5/src/modules/missions/dtos/mission.dto.ts @@ -16,8 +16,17 @@ export const bodyToMission = (body: MissionAddRequest) => { }; }; + +export interface MissionAddResponse { + id: number; + storeId: number; + reward: number; + deadline: Date; + missionSpec: string; +} + // responseFromMission: DB 결과를 응답 형식으로 변환 -export const responseFromMission = (mission: any) => { +export const responseFromMission = (mission: any): MissionAddResponse => { return { id: mission.id, storeId: mission.store_id, diff --git a/week5/src/modules/missions/dtos/user-mission.dto.ts b/week5/src/modules/missions/dtos/user-mission.dto.ts index 45f449e..eb99ff1 100644 --- a/week5/src/modules/missions/dtos/user-mission.dto.ts +++ b/week5/src/modules/missions/dtos/user-mission.dto.ts @@ -12,8 +12,17 @@ export const bodyToUserMission = (body: UserMissionAddRequest) => { }; }; +// 미션 도전 응답 타입 +export interface UserMissionAddResponse { + id: number; + userId: number; + missionId: number; + status: string; + createdAt: Date; +} + // responseFromUserMission: DB 결과를 응답 형식으로 변환 -export const responseFromUserMission = (userMission: any) => { +export const responseFromUserMission = (userMission: any): UserMissionAddResponse => { return { id: userMission.id, userId: userMission.user_id, diff --git a/week5/src/modules/reviews/dtos/review.dto.ts b/week5/src/modules/reviews/dtos/review.dto.ts index 9e44c32..7518f20 100644 --- a/week5/src/modules/reviews/dtos/review.dto.ts +++ b/week5/src/modules/reviews/dtos/review.dto.ts @@ -16,8 +16,18 @@ export const bodyToReview = (body: ReviewAddRequest) => { }; }; +// 리뷰 추가 응답 타입 +export interface ReviewAddResponse { + id: number; + userId: number; + storeId: number; + body: string; + score: number; + createdAt: Date; +} + // responseFromReview: DB 결과를 응답 형식으로 변환 -export const responseFromReview = (review: any) => { +export const responseFromReview = (review: any): ReviewAddResponse => { return { id: review.id, userId: review.user_id, diff --git a/week5/src/modules/stores/dtos/store.dto.ts b/week5/src/modules/stores/dtos/store.dto.ts index 2088232..0e0c5f1 100644 --- a/week5/src/modules/stores/dtos/store.dto.ts +++ b/week5/src/modules/stores/dtos/store.dto.ts @@ -14,8 +14,17 @@ export const bodyToStore = (body: StoreAddRequest) => { }; }; +// 가게 추가 응답 타입 +export interface StoreAddResponse { + id: number; + regionId: number; + name: string; + address: string; + score: number; +} + // responseFromStore: DB 결과를 응답 형식으로 변환 -export const responseFromStore = (store: any) => { +export const responseFromStore = (store: any): StoreAddResponse => { return { id: store.id, regionId: store.region_id, diff --git a/week5/src/modules/users/dtos/user.dto.ts b/week5/src/modules/users/dtos/user.dto.ts index e27f405..501fb0d 100644 --- a/week5/src/modules/users/dtos/user.dto.ts +++ b/week5/src/modules/users/dtos/user.dto.ts @@ -29,7 +29,7 @@ export const responseFromUser = ({ }: { user: any; preferences: any[]; -}) => { +}): UserSignUpResponse => { return { id: user.id, email: user.email, @@ -47,7 +47,20 @@ export const responseFromUser = ({ }; }; -// 4. 요청받은 데이터를 우리 시스템에 맞는 데이터로 변환해주는 함수입니다. +// 4. 회원가입 응답 데이터 타입입니다. +export interface UserSignUpResponse { + id: number; + email: string; + name: string; + gender: string; + birth: Date; + address: string; + detailAddress: string; + phoneNumber: string; + preferences: { id: number; foodCategoryId: number; name: string }[]; +} + +// 5. 요청받은 데이터를 우리 시스템에 맞는 데이터로 변환해주는 함수입니다. export const bodyToUser = (body: UserSignUpRequest): UserData => { const birth = new Date(body.birth); //날짜 변환 From ca56484141abfe028a2321e0c0c55d012d4957ea Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 10:46:21 +0900 Subject: [PATCH 11/20] =?UTF-8?q?feat:=20=EC=84=B1=EA=B3=B5=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=ED=86=B5=EC=9D=BC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/common/responses/response.ts | 10 ++++++++++ .../missions/controllers/mission.controller.ts | 7 ++++--- .../controllers/user-mission.controller.ts | 7 ++++--- .../reviews/controllers/review.controller.ts | 7 ++++--- .../stores/controllers/store.controller.ts | 7 ++++--- .../modules/users/controllers/user.controller.ts | 15 +++++++++------ week5/src/modules/users/services/user.service.ts | 10 +++++----- 7 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 week5/src/common/responses/response.ts diff --git a/week5/src/common/responses/response.ts b/week5/src/common/responses/response.ts new file mode 100644 index 0000000..3a4d66f --- /dev/null +++ b/week5/src/common/responses/response.ts @@ -0,0 +1,10 @@ +export interface ApiResponse { + resultType: "SUCCESS"; + error: null; + data: T; +} +export const success = (data: T): ApiResponse => ({ + resultType: "SUCCESS", + error: null, + data, +}); \ No newline at end of file diff --git a/week5/src/modules/missions/controllers/mission.controller.ts b/week5/src/modules/missions/controllers/mission.controller.ts index 5ea722f..93df369 100644 --- a/week5/src/modules/missions/controllers/mission.controller.ts +++ b/week5/src/modules/missions/controllers/mission.controller.ts @@ -1,6 +1,7 @@ import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; -import { bodyToMission, MissionAddRequest } from "../dtos/mission.dto.js"; +import { bodyToMission, MissionAddRequest, MissionAddResponse } from "../dtos/mission.dto.js"; import { missionAdd } from "../services/mission.service.js"; +import { ApiResponse, success } from "../../../common/responses/response.js"; @Route("missions") @Tags("Missions") @@ -8,9 +9,9 @@ export class MissionController extends Controller { // 가게에 미션 추가하기 @Post() @SuccessResponse("201", "Created") - public async addMission(@Body() body: MissionAddRequest): Promise<{ result: unknown }> { + public async addMission(@Body() body: MissionAddRequest): Promise> { this.setStatus(201); const mission = await missionAdd(bodyToMission(body)); - return { result: mission }; + return success(mission); } } diff --git a/week5/src/modules/missions/controllers/user-mission.controller.ts b/week5/src/modules/missions/controllers/user-mission.controller.ts index 0e7815a..95eda6e 100644 --- a/week5/src/modules/missions/controllers/user-mission.controller.ts +++ b/week5/src/modules/missions/controllers/user-mission.controller.ts @@ -1,6 +1,7 @@ import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; -import { bodyToUserMission, UserMissionAddRequest } from "../dtos/user-mission.dto.js"; +import { bodyToUserMission, UserMissionAddRequest, UserMissionAddResponse } from "../dtos/user-mission.dto.js"; import { userMissionAdd } from "../services/user-mission.service.js"; +import { ApiResponse, success } from "../../../common/responses/response.js"; @Route("user-missions") @Tags("UserMissions") @@ -8,9 +9,9 @@ export class UserMissionController extends Controller { // 미션 도전하기 @Post() @SuccessResponse("201", "Created") - public async addUserMission(@Body() body: UserMissionAddRequest): Promise<{ result: unknown }> { + public async addUserMission(@Body() body: UserMissionAddRequest): Promise> { this.setStatus(201); const userMission = await userMissionAdd(bodyToUserMission(body)); - return { result: userMission }; + return success(userMission); } } diff --git a/week5/src/modules/reviews/controllers/review.controller.ts b/week5/src/modules/reviews/controllers/review.controller.ts index ccd7e9c..b016af3 100644 --- a/week5/src/modules/reviews/controllers/review.controller.ts +++ b/week5/src/modules/reviews/controllers/review.controller.ts @@ -1,6 +1,7 @@ import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; -import { bodyToReview, ReviewAddRequest } from "../dtos/review.dto.js"; +import { bodyToReview, ReviewAddRequest, ReviewAddResponse } from "../dtos/review.dto.js"; import { reviewAdd } from "../services/review.service.js"; +import { ApiResponse, success } from "../../../common/responses/response.js"; @Route("reviews") @Tags("Reviews") @@ -8,9 +9,9 @@ export class ReviewController extends Controller { // 가게에 리뷰 추가하기 @Post() @SuccessResponse("201", "Created") - public async addReview(@Body() body: ReviewAddRequest): Promise<{ result: unknown }> { + public async addReview(@Body() body: ReviewAddRequest): Promise> { this.setStatus(201); const review = await reviewAdd(bodyToReview(body)); - return { result: review }; + return success(review); } } diff --git a/week5/src/modules/stores/controllers/store.controller.ts b/week5/src/modules/stores/controllers/store.controller.ts index 1e8f121..f9439b6 100644 --- a/week5/src/modules/stores/controllers/store.controller.ts +++ b/week5/src/modules/stores/controllers/store.controller.ts @@ -1,6 +1,7 @@ import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; -import { bodyToStore, StoreAddRequest } from "../dtos/store.dto.js"; +import { bodyToStore, StoreAddRequest, StoreAddResponse } from "../dtos/store.dto.js"; import { storeAdd } from "../services/store.service.js"; +import { ApiResponse, success } from "../../../common/responses/response.js"; @Route("stores") @Tags("Stores") @@ -8,9 +9,9 @@ export class StoreController extends Controller { // 특정 지역에 가게 추가하기 @Post() @SuccessResponse("201", "Created") - public async addStore(@Body() body: StoreAddRequest): Promise<{ result: unknown }> { + public async addStore(@Body() body: StoreAddRequest): Promise> { this.setStatus(201); const store = await storeAdd(bodyToStore(body)); - return { result: store }; + return success(store); } } diff --git a/week5/src/modules/users/controllers/user.controller.ts b/week5/src/modules/users/controllers/user.controller.ts index f47bbcc..f482227 100644 --- a/week5/src/modules/users/controllers/user.controller.ts +++ b/week5/src/modules/users/controllers/user.controller.ts @@ -1,16 +1,19 @@ import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; -import { bodyToUser, UserSignUpRequest } from "../dtos/user.dto.js"; +import { UserSignUpRequest, UserSignUpResponse } from "../dtos/user.dto.js"; import { userSignUp } from "../services/user.service.js"; +import { ApiResponse, success } from "../../../common/responses/response.js"; @Route("users") @Tags("Users") export class UserController extends Controller { - // 회원가입 - @Post("signup") + @Post("signup") // 엔드포인트 정의 @SuccessResponse("200", "OK") - public async signUp(@Body() body: UserSignUpRequest): Promise<{ result: unknown }> { + public async handleUserSignUp( + @Body() body: UserSignUpRequest, + ): Promise> { console.log("회원가입을 요청했습니다!"); - const user = await userSignUp(bodyToUser(body)); - return { result: user }; + console.log("body:", body); + const user = await userSignUp(body); //서비스 로직 호웍 + return success(user); //성공 응답 보내기 } } \ No newline at end of file diff --git a/week5/src/modules/users/services/user.service.ts b/week5/src/modules/users/services/user.service.ts index a4f0948..4c30090 100644 --- a/week5/src/modules/users/services/user.service.ts +++ b/week5/src/modules/users/services/user.service.ts @@ -1,4 +1,4 @@ -import { UserData, responseFromUser } from "../dtos/user.dto.js"; +import { UserSignUpRequest, responseFromUser } from "../dtos/user.dto.js"; import { addUser, getUser, @@ -6,14 +6,14 @@ import { setPreference, } from "../repositories/user.repository.js"; -export const userSignUp = async (data: UserData) => { +export const userSignUp = async (data: UserSignUpRequest) => { const joinUserId = await addUser({ email: data.email, name: data.name, gender: data.gender, - birth: data.birth, // 이미 Date 객체입니다. - address: data.address, - detailAddress: data.detailAddress, + birth: new Date(data.birth), + address: data.address || "", + detailAddress: data.detailAddress || "", phoneNumber: data.phoneNumber, }); From 56b5cd5942c76027bce038d04c27ca203af642de Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 11:06:17 +0900 Subject: [PATCH 12/20] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/common/errors/app.error.ts | 17 +++++++++++++++++ week5/src/common/errors/error.ts | 12 ++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 week5/src/common/errors/app.error.ts create mode 100644 week5/src/common/errors/error.ts diff --git a/week5/src/common/errors/app.error.ts b/week5/src/common/errors/app.error.ts new file mode 100644 index 0000000..c53d95a --- /dev/null +++ b/week5/src/common/errors/app.error.ts @@ -0,0 +1,17 @@ +export class AppError extends Error { + public readonly errorCode: string; + public readonly statusCode: number; + public readonly data?: any; + + constructor(params?: { + errorCode: string; + message: string; + statusCode: number; + data?: any; + }) { + super(params?.message); + this.errorCode = params?.errorCode ?? "UNKNOWN"; + this.statusCode = params?.statusCode ?? 500; + this.data = params?.data ?? null; + } +} diff --git a/week5/src/common/errors/error.ts b/week5/src/common/errors/error.ts new file mode 100644 index 0000000..5a3823c --- /dev/null +++ b/week5/src/common/errors/error.ts @@ -0,0 +1,12 @@ +import { AppError } from "./app.error.js"; + +export class DuplicateUserEmailError extends AppError { + constructor(message: string, data?: unknown) { + super({ + errorCode: "U001", + statusCode: 409, + message, + data, + }); + } +} From 7d5343396e0789a0e9a7639ccec60c61ddebaf98 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 11:07:00 +0900 Subject: [PATCH 13/20] =?UTF-8?q?feat:=20=EC=A0=84=EC=97=AD=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=AF=B8=EB=93=A4?= =?UTF-8?q?=EC=9B=A8=EC=96=B4=20=EB=B0=8F=20=EC=9C=A0=EC=A0=80=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=97=90=EB=9F=AC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/index.ts | 43 ++++++++++++++++++- .../modules/users/services/user.service.ts | 15 ++++--- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/week5/src/index.ts b/week5/src/index.ts index d2f0586..3d54dd9 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -2,13 +2,39 @@ import "reflect-metadata"; import dotenv from "dotenv"; dotenv.config(); -import express, { Express, Request, Response } from "express"; +import express, { Express, NextFunction, Request, Response } from "express"; import cors from "cors"; import { RegisterRoutes } from "./routes.js"; +import { AppError } from "./common/errors/app.error.js"; + +// res.error 메서드 타입 확장 +declare global { + namespace Express { + interface Response { + error: (params: { + errorCode?: string | null; + message?: string | null; + data?: unknown; + }) => Response; + } + } +} const app: Express = express(); const port = process.env.PORT || 3000; +// res.error 메서드 주입 +app.use((req: Request, res: Response, next: NextFunction) => { + res.error = function ({ errorCode = null, message = null, data = null }) { + return this.json({ + resultType: "FAILED", + error: { errorCode, message, data }, + data: null, + }); + }; + next(); +}); + // 미들웨어 설정 app.use(cors()); app.use(express.static('public')); @@ -25,6 +51,21 @@ const router = express.Router(); RegisterRoutes(router); app.use("/api/v1", router); +/** + * 전역 오류를 처리하기 위한 미들웨어 + */ +app.use((err: AppError, req: Request, res: Response, next: NextFunction) => { + if (res.headersSent) { + return next(err); + } + + res.status(err.statusCode || 500).error({ + errorCode: err.errorCode || "unknown", + message: err.message || null, + data: err.data || null, + }); +}); + // 서버 시작 app.listen(port, () => { console.log(`[server]: Server is running at http://localhost:${port}`); diff --git a/week5/src/modules/users/services/user.service.ts b/week5/src/modules/users/services/user.service.ts index 4c30090..bcb1c36 100644 --- a/week5/src/modules/users/services/user.service.ts +++ b/week5/src/modules/users/services/user.service.ts @@ -1,24 +1,27 @@ -import { UserSignUpRequest, responseFromUser } from "../dtos/user.dto.js"; +import { UserSignUpRequest, UserSignUpResponse, responseFromUser } from "../dtos/user.dto.js"; import { addUser, getUser, getUserPreferencesByUserId, setPreference, } from "../repositories/user.repository.js"; +import { DuplicateUserEmailError } from "../../../common/errors/error.js"; -export const userSignUp = async (data: UserSignUpRequest) => { +export const userSignUp = async ( + data: UserSignUpRequest, +): Promise => { const joinUserId = await addUser({ email: data.email, name: data.name, gender: data.gender, - birth: new Date(data.birth), - address: data.address || "", - detailAddress: data.detailAddress || "", + birth: new Date(data.birth), // 문자열을 Date 객체로 변환해서 넘겨줍니다. + address: data.address, + detailAddress: data.detailAddress, phoneNumber: data.phoneNumber, }); if (joinUserId === null) { - throw new Error("이미 존재하는 이메일입니다."); + throw new DuplicateUserEmailError("이미 존재하는 이메일입니다.", data); } for (const preference of data.preferences) { From 53af9c64f2149679afca22c33dbd3a5b7e5b1fd7 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 11:07:18 +0900 Subject: [PATCH 14/20] =?UTF-8?q?chore:=20TSOA=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=8F=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=8A=A4?= =?UTF-8?q?=ED=8E=99=20=EC=9E=AC=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/routes.ts | 125 +++++++++++++- week5/src/swagger.json | 381 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 465 insertions(+), 41 deletions(-) diff --git a/week5/src/routes.ts b/week5/src/routes.ts index 0abdab2..8ae7cba 100644 --- a/week5/src/routes.ts +++ b/week5/src/routes.ts @@ -20,6 +20,32 @@ import type { Request as ExRequest, Response as ExResponse, RequestHandler, Rout // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa const models: TsoaRoute.Models = { + "UserSignUpResponse": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"double","required":true}, + "email": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "gender": {"dataType":"string","required":true}, + "birth": {"dataType":"datetime","required":true}, + "address": {"dataType":"string","required":true}, + "detailAddress": {"dataType":"string","required":true}, + "phoneNumber": {"dataType":"string","required":true}, + "preferences": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true},"foodCategoryId":{"dataType":"double","required":true},"id":{"dataType":"double","required":true}}},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResponse_UserSignUpResponse_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"enum","enums":["SUCCESS"],"required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "data": {"ref":"UserSignUpResponse","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "UserSignUpRequest": { "dataType": "refObject", "properties": { @@ -35,6 +61,28 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "StoreAddResponse": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"double","required":true}, + "regionId": {"dataType":"double","required":true}, + "name": {"dataType":"string","required":true}, + "address": {"dataType":"string","required":true}, + "score": {"dataType":"double","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResponse_StoreAddResponse_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"enum","enums":["SUCCESS"],"required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "data": {"ref":"StoreAddResponse","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "StoreAddRequest": { "dataType": "refObject", "properties": { @@ -45,6 +93,29 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ReviewAddResponse": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"double","required":true}, + "userId": {"dataType":"double","required":true}, + "storeId": {"dataType":"double","required":true}, + "body": {"dataType":"string","required":true}, + "score": {"dataType":"double","required":true}, + "createdAt": {"dataType":"datetime","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResponse_ReviewAddResponse_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"enum","enums":["SUCCESS"],"required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "data": {"ref":"ReviewAddResponse","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ReviewAddRequest": { "dataType": "refObject", "properties": { @@ -56,6 +127,28 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UserMissionAddResponse": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"double","required":true}, + "userId": {"dataType":"double","required":true}, + "missionId": {"dataType":"double","required":true}, + "status": {"dataType":"string","required":true}, + "createdAt": {"dataType":"datetime","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResponse_UserMissionAddResponse_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"enum","enums":["SUCCESS"],"required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "data": {"ref":"UserMissionAddResponse","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "UserMissionAddRequest": { "dataType": "refObject", "properties": { @@ -65,6 +158,28 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "MissionAddResponse": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"double","required":true}, + "storeId": {"dataType":"double","required":true}, + "reward": {"dataType":"double","required":true}, + "deadline": {"dataType":"datetime","required":true}, + "missionSpec": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiResponse_MissionAddResponse_": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"enum","enums":["SUCCESS"],"required":true}, + "error": {"dataType":"enum","enums":[null],"required":true}, + "data": {"ref":"MissionAddResponse","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "MissionAddRequest": { "dataType": "refObject", "properties": { @@ -93,25 +208,25 @@ export function RegisterRoutes(app: Router) { - const argsUserController_signUp: Record = { + const argsUserController_handleUserSignUp: Record = { body: {"in":"body","name":"body","required":true,"ref":"UserSignUpRequest"}, }; app.post('/users/signup', ...(fetchMiddlewares(UserController)), - ...(fetchMiddlewares(UserController.prototype.signUp)), + ...(fetchMiddlewares(UserController.prototype.handleUserSignUp)), - async function UserController_signUp(request: ExRequest, response: ExResponse, next: any) { + async function UserController_handleUserSignUp(request: ExRequest, response: ExResponse, next: any) { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa let validatedArgs: any[] = []; try { - validatedArgs = templateService.getValidatedArgs({ args: argsUserController_signUp, request, response }); + validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleUserSignUp, request, response }); const controller = new UserController(); await templateService.apiHandler({ - methodName: 'signUp', + methodName: 'handleUserSignUp', controller, response, next, diff --git a/week5/src/swagger.json b/week5/src/swagger.json index d1eb234..e8ca37b 100644 --- a/week5/src/swagger.json +++ b/week5/src/swagger.json @@ -7,6 +7,101 @@ "requestBodies": {}, "responses": {}, "schemas": { + "UserSignUpResponse": { + "properties": { + "id": { + "type": "number", + "format": "double" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "birth": { + "type": "string", + "format": "date-time" + }, + "address": { + "type": "string" + }, + "detailAddress": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "preferences": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "foodCategoryId": { + "type": "number", + "format": "double" + }, + "id": { + "type": "number", + "format": "double" + } + }, + "required": [ + "name", + "foodCategoryId", + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "id", + "email", + "name", + "gender", + "birth", + "address", + "detailAddress", + "phoneNumber", + "preferences" + ], + "type": "object", + "additionalProperties": false + }, + "ApiResponse_UserSignUpResponse_": { + "properties": { + "resultType": { + "type": "string", + "enum": [ + "SUCCESS" + ], + "nullable": false + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "data": { + "$ref": "#/components/schemas/UserSignUpResponse" + } + }, + "required": [ + "resultType", + "error", + "data" + ], + "type": "object", + "additionalProperties": false + }, "UserSignUpRequest": { "properties": { "email": { @@ -49,6 +144,65 @@ "type": "object", "additionalProperties": false }, + "StoreAddResponse": { + "properties": { + "id": { + "type": "number", + "format": "double" + }, + "regionId": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + }, + "address": { + "type": "string" + }, + "score": { + "type": "number", + "format": "double" + } + }, + "required": [ + "id", + "regionId", + "name", + "address", + "score" + ], + "type": "object", + "additionalProperties": false + }, + "ApiResponse_StoreAddResponse_": { + "properties": { + "resultType": { + "type": "string", + "enum": [ + "SUCCESS" + ], + "nullable": false + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "data": { + "$ref": "#/components/schemas/StoreAddResponse" + } + }, + "required": [ + "resultType", + "error", + "data" + ], + "type": "object", + "additionalProperties": false + }, "StoreAddRequest": { "properties": { "regionId": { @@ -69,6 +223,71 @@ "type": "object", "additionalProperties": false }, + "ReviewAddResponse": { + "properties": { + "id": { + "type": "number", + "format": "double" + }, + "userId": { + "type": "number", + "format": "double" + }, + "storeId": { + "type": "number", + "format": "double" + }, + "body": { + "type": "string" + }, + "score": { + "type": "number", + "format": "double" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "userId", + "storeId", + "body", + "score", + "createdAt" + ], + "type": "object", + "additionalProperties": false + }, + "ApiResponse_ReviewAddResponse_": { + "properties": { + "resultType": { + "type": "string", + "enum": [ + "SUCCESS" + ], + "nullable": false + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "data": { + "$ref": "#/components/schemas/ReviewAddResponse" + } + }, + "required": [ + "resultType", + "error", + "data" + ], + "type": "object", + "additionalProperties": false + }, "ReviewAddRequest": { "properties": { "userId": { @@ -96,6 +315,66 @@ "type": "object", "additionalProperties": false }, + "UserMissionAddResponse": { + "properties": { + "id": { + "type": "number", + "format": "double" + }, + "userId": { + "type": "number", + "format": "double" + }, + "missionId": { + "type": "number", + "format": "double" + }, + "status": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "userId", + "missionId", + "status", + "createdAt" + ], + "type": "object", + "additionalProperties": false + }, + "ApiResponse_UserMissionAddResponse_": { + "properties": { + "resultType": { + "type": "string", + "enum": [ + "SUCCESS" + ], + "nullable": false + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "data": { + "$ref": "#/components/schemas/UserMissionAddResponse" + } + }, + "required": [ + "resultType", + "error", + "data" + ], + "type": "object", + "additionalProperties": false + }, "UserMissionAddRequest": { "properties": { "userId": { @@ -114,6 +393,66 @@ "type": "object", "additionalProperties": false }, + "MissionAddResponse": { + "properties": { + "id": { + "type": "number", + "format": "double" + }, + "storeId": { + "type": "number", + "format": "double" + }, + "reward": { + "type": "number", + "format": "double" + }, + "deadline": { + "type": "string", + "format": "date-time" + }, + "missionSpec": { + "type": "string" + } + }, + "required": [ + "id", + "storeId", + "reward", + "deadline", + "missionSpec" + ], + "type": "object", + "additionalProperties": false + }, + "ApiResponse_MissionAddResponse_": { + "properties": { + "resultType": { + "type": "string", + "enum": [ + "SUCCESS" + ], + "nullable": false + }, + "error": { + "type": "number", + "enum": [ + null + ], + "nullable": true + }, + "data": { + "$ref": "#/components/schemas/MissionAddResponse" + } + }, + "required": [ + "resultType", + "error", + "data" + ], + "type": "object", + "additionalProperties": false + }, "MissionAddRequest": { "properties": { "storeId": { @@ -150,20 +489,14 @@ "paths": { "/users/signup": { "post": { - "operationId": "SignUp", + "operationId": "HandleUserSignUp", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { - "properties": { - "result": {} - }, - "required": [ - "result" - ], - "type": "object" + "$ref": "#/components/schemas/ApiResponse_UserSignUpResponse_" } } } @@ -195,13 +528,7 @@ "content": { "application/json": { "schema": { - "properties": { - "result": {} - }, - "required": [ - "result" - ], - "type": "object" + "$ref": "#/components/schemas/ApiResponse_StoreAddResponse_" } } } @@ -233,13 +560,7 @@ "content": { "application/json": { "schema": { - "properties": { - "result": {} - }, - "required": [ - "result" - ], - "type": "object" + "$ref": "#/components/schemas/ApiResponse_ReviewAddResponse_" } } } @@ -271,13 +592,7 @@ "content": { "application/json": { "schema": { - "properties": { - "result": {} - }, - "required": [ - "result" - ], - "type": "object" + "$ref": "#/components/schemas/ApiResponse_UserMissionAddResponse_" } } } @@ -309,13 +624,7 @@ "content": { "application/json": { "schema": { - "properties": { - "result": {} - }, - "required": [ - "result" - ], - "type": "object" + "$ref": "#/components/schemas/ApiResponse_MissionAddResponse_" } } } From 09c1e44d2d87be55aeec1900aa9131bd5c196dd8 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 11:09:00 +0900 Subject: [PATCH 15/20] =?UTF-8?q?chore:=20morgan=20=EB=AF=B8=EB=93=A4?= =?UTF-8?q?=EC=9B=A8=EC=96=B4=20=EC=84=A4=EC=B9=98=20=EB=B0=8F=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/package-lock.json | 82 +++++++++++++++++++++++++++++++++++++++++ week5/package.json | 2 + week5/src/index.ts | 2 + 3 files changed, 86 insertions(+) diff --git a/week5/package-lock.json b/week5/package-lock.json index 36ae324..ef5c28a 100644 --- a/week5/package-lock.json +++ b/week5/package-lock.json @@ -9,6 +9,7 @@ "dotenv": "^17.4.2", "express": "^5.2.1", "http-status-codes": "^2.3.0", + "morgan": "^1.10.1", "mysql2": "^3.22.3", "reflect-metadata": "^0.2.2", "tsoa": "^7.0.0-alpha.0" @@ -17,6 +18,7 @@ "@types/cors": "^2.8.19", "@types/dotenv": "^6.1.1", "@types/express": "^5.0.6", + "@types/morgan": "^1.9.10", "@types/node": "^25.6.0", "nodemon": "^3.1.14", "tsx": "^4.21.0", @@ -1305,6 +1307,16 @@ "@types/koa": "*" } }, + "node_modules/@types/morgan": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/multer": { "version": "1.4.13", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", @@ -1430,6 +1442,24 @@ "node": "18 || 20 || >=22" } }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2608,6 +2638,49 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2735,6 +2808,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/week5/package.json b/week5/package.json index 26ddbba..029b9b4 100644 --- a/week5/package.json +++ b/week5/package.json @@ -11,6 +11,7 @@ "dotenv": "^17.4.2", "express": "^5.2.1", "http-status-codes": "^2.3.0", + "morgan": "^1.10.1", "mysql2": "^3.22.3", "reflect-metadata": "^0.2.2", "tsoa": "^7.0.0-alpha.0" @@ -19,6 +20,7 @@ "@types/cors": "^2.8.19", "@types/dotenv": "^6.1.1", "@types/express": "^5.0.6", + "@types/morgan": "^1.9.10", "@types/node": "^25.6.0", "nodemon": "^3.1.14", "tsx": "^4.21.0", diff --git a/week5/src/index.ts b/week5/src/index.ts index 3d54dd9..adf6269 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -4,6 +4,7 @@ dotenv.config(); import express, { Express, NextFunction, Request, Response } from "express"; import cors from "cors"; +import morgan from "morgan"; import { RegisterRoutes } from "./routes.js"; import { AppError } from "./common/errors/app.error.js"; @@ -36,6 +37,7 @@ app.use((req: Request, res: Response, next: NextFunction) => { }); // 미들웨어 설정 +app.use(morgan("dev")); app.use(cors()); app.use(express.static('public')); app.use(express.json()); From 160de34a7e79745e5187d2df013a54cc7ab1d409 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 14 May 2026 11:10:15 +0900 Subject: [PATCH 16/20] =?UTF-8?q?chore:=20cookie-parser=20=EB=AF=B8?= =?UTF-8?q?=EB=93=A4=EC=9B=A8=EC=96=B4=20=EC=84=A4=EC=B9=98=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/package-lock.json | 31 +++++++++++++++++++++++++++++++ week5/package.json | 2 ++ week5/src/index.ts | 2 ++ 3 files changed, 35 insertions(+) diff --git a/week5/package-lock.json b/week5/package-lock.json index ef5c28a..8b30a6e 100644 --- a/week5/package-lock.json +++ b/week5/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "cookie-parser": "^1.4.7", "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", @@ -15,6 +16,7 @@ "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { + "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/dotenv": "^6.1.1", "@types/express": "^5.0.6", @@ -1209,6 +1211,16 @@ "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==", "license": "MIT" }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cookies": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz", @@ -1722,6 +1734,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", diff --git a/week5/package.json b/week5/package.json index 029b9b4..5a2a066 100644 --- a/week5/package.json +++ b/week5/package.json @@ -7,6 +7,7 @@ "dev": "npm run tsoa && nodemon --watch 'src/**/*.ts' --ignore 'src/routes.ts' --exec 'npm run tsoa && tsx src/index.ts'" }, "dependencies": { + "cookie-parser": "^1.4.7", "cors": "^2.8.6", "dotenv": "^17.4.2", "express": "^5.2.1", @@ -17,6 +18,7 @@ "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { + "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/dotenv": "^6.1.1", "@types/express": "^5.0.6", diff --git a/week5/src/index.ts b/week5/src/index.ts index adf6269..a3373d6 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -5,6 +5,7 @@ dotenv.config(); import express, { Express, NextFunction, Request, Response } from "express"; import cors from "cors"; import morgan from "morgan"; +import cookieParser from "cookie-parser"; import { RegisterRoutes } from "./routes.js"; import { AppError } from "./common/errors/app.error.js"; @@ -39,6 +40,7 @@ app.use((req: Request, res: Response, next: NextFunction) => { // 미들웨어 설정 app.use(morgan("dev")); app.use(cors()); +app.use(cookieParser()); app.use(express.static('public')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); From ac0d86bdd7bcf4b607a63ad73a3961fc4c823f54 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 21 May 2026 10:34:52 +0900 Subject: [PATCH 17/20] =?UTF-8?q?feat:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EA=B4=80=EB=A0=A8=20UI=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/package-lock.json | 44 +++++++++++++++++++++++++++++++++++++++++ week5/package.json | 2 ++ 2 files changed, 46 insertions(+) diff --git a/week5/package-lock.json b/week5/package-lock.json index 8b30a6e..4a25676 100644 --- a/week5/package-lock.json +++ b/week5/package-lock.json @@ -13,6 +13,7 @@ "morgan": "^1.10.1", "mysql2": "^3.22.3", "reflect-metadata": "^0.2.2", + "swagger-ui-express": "^5.0.1", "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { @@ -22,6 +23,7 @@ "@types/express": "^5.0.6", "@types/morgan": "^1.9.10", "@types/node": "^25.6.0", + "@types/swagger-ui-express": "^4.1.8", "nodemon": "^3.1.14", "tsx": "^4.21.0", "typescript": "^6.0.3" @@ -812,6 +814,13 @@ "node": ">=14" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@tsoa/cli": { "version": "7.0.0-alpha.0", "resolved": "https://registry.npmjs.org/@tsoa/cli/-/cli-7.0.0-alpha.0.tgz", @@ -1378,6 +1387,17 @@ "@types/node": "*" } }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -3383,6 +3403,30 @@ "node": ">=4" } }, + "node_modules/swagger-ui-dist": { + "version": "5.32.6", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz", + "integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/week5/package.json b/week5/package.json index 5a2a066..ffbf1af 100644 --- a/week5/package.json +++ b/week5/package.json @@ -15,6 +15,7 @@ "morgan": "^1.10.1", "mysql2": "^3.22.3", "reflect-metadata": "^0.2.2", + "swagger-ui-express": "^5.0.1", "tsoa": "^7.0.0-alpha.0" }, "devDependencies": { @@ -24,6 +25,7 @@ "@types/express": "^5.0.6", "@types/morgan": "^1.9.10", "@types/node": "^25.6.0", + "@types/swagger-ui-express": "^4.1.8", "nodemon": "^3.1.14", "tsx": "^4.21.0", "typescript": "^6.0.3" From 74b668ae08ab9e2a3f83e34cf635c7d47095ce61 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 21 May 2026 10:35:20 +0900 Subject: [PATCH 18/20] =?UTF-8?q?feat:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EA=B8=B0=EB=B3=B8=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?index.ts=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/week5/src/index.ts b/week5/src/index.ts index a3373d6..a9df1de 100644 --- a/week5/src/index.ts +++ b/week5/src/index.ts @@ -6,9 +6,17 @@ import express, { Express, NextFunction, Request, Response } from "express"; import cors from "cors"; import morgan from "morgan"; import cookieParser from "cookie-parser"; +import swaggerUi from "swagger-ui-express"; +import path from "path"; +import fs from "fs"; import { RegisterRoutes } from "./routes.js"; import { AppError } from "./common/errors/app.error.js"; +// 1. TSOA가 생성한 swagger.json 읽어오기 +const swaggerDocument = JSON.parse( + fs.readFileSync(path.resolve("src/swagger.json"), "utf8") +); + // res.error 메서드 타입 확장 declare global { namespace Express { @@ -50,6 +58,9 @@ app.get("/", (req: Request, res: Response) => { res.send("Hello World! This is TypeScript Server!"); }); +// 2. Swagger UI 연결 +app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + // Express.js에 생성한 엔드 포인트들을 register const router = express.Router(); RegisterRoutes(router); From 4e59079472dcbd098a503d6c62fa0a7a126dc212 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 21 May 2026 10:35:48 +0900 Subject: [PATCH 19/20] =?UTF-8?q?feat:=20=EC=8B=A4=ED=8C=A8=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20JSDoc=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week5/src/common/responses/response.ts | 11 ++ .../controllers/mission.controller.ts | 11 +- .../controllers/user-mission.controller.ts | 11 +- .../reviews/controllers/review.controller.ts | 11 +- .../stores/controllers/store.controller.ts | 11 +- .../users/controllers/user.controller.ts | 13 +- week5/src/routes.ts | 10 ++ week5/src/swagger.json | 164 ++++++++++++++++++ 8 files changed, 227 insertions(+), 15 deletions(-) diff --git a/week5/src/common/responses/response.ts b/week5/src/common/responses/response.ts index 3a4d66f..803780a 100644 --- a/week5/src/common/responses/response.ts +++ b/week5/src/common/responses/response.ts @@ -3,6 +3,17 @@ export interface ApiResponse { error: null; data: T; } + +export interface ApiErrorResponse { + resultType: "FAILED"; + error: { + errorCode: string | null; + message: string | null; + data: unknown; + }; + data: null; +} + export const success = (data: T): ApiResponse => ({ resultType: "SUCCESS", error: null, diff --git a/week5/src/modules/missions/controllers/mission.controller.ts b/week5/src/modules/missions/controllers/mission.controller.ts index 93df369..3f2f28d 100644 --- a/week5/src/modules/missions/controllers/mission.controller.ts +++ b/week5/src/modules/missions/controllers/mission.controller.ts @@ -1,14 +1,19 @@ -import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; +import { Controller, Route, Post, Body, Tags, SuccessResponse, Response } from "tsoa"; import { bodyToMission, MissionAddRequest, MissionAddResponse } from "../dtos/mission.dto.js"; import { missionAdd } from "../services/mission.service.js"; -import { ApiResponse, success } from "../../../common/responses/response.js"; +import { ApiResponse, ApiErrorResponse, success } from "../../../common/responses/response.js"; @Route("missions") @Tags("Missions") export class MissionController extends Controller { - // 가게에 미션 추가하기 + /** + * 미션 추가 API + * @summary 특정 가게에 미션을 추가합니다. + */ @Post() @SuccessResponse("201", "Created") + @Response(400, "Bad Request") + @Response(500, "Internal Server Error") public async addMission(@Body() body: MissionAddRequest): Promise> { this.setStatus(201); const mission = await missionAdd(bodyToMission(body)); diff --git a/week5/src/modules/missions/controllers/user-mission.controller.ts b/week5/src/modules/missions/controllers/user-mission.controller.ts index 95eda6e..8b1f4b1 100644 --- a/week5/src/modules/missions/controllers/user-mission.controller.ts +++ b/week5/src/modules/missions/controllers/user-mission.controller.ts @@ -1,14 +1,19 @@ -import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; +import { Controller, Route, Post, Body, Tags, SuccessResponse, Response } from "tsoa"; import { bodyToUserMission, UserMissionAddRequest, UserMissionAddResponse } from "../dtos/user-mission.dto.js"; import { userMissionAdd } from "../services/user-mission.service.js"; -import { ApiResponse, success } from "../../../common/responses/response.js"; +import { ApiResponse, ApiErrorResponse, success } from "../../../common/responses/response.js"; @Route("user-missions") @Tags("UserMissions") export class UserMissionController extends Controller { - // 미션 도전하기 + /** + * 미션 도전 API + * @summary 사용자가 특정 미션에 도전합니다. + */ @Post() @SuccessResponse("201", "Created") + @Response(400, "Bad Request") + @Response(500, "Internal Server Error") public async addUserMission(@Body() body: UserMissionAddRequest): Promise> { this.setStatus(201); const userMission = await userMissionAdd(bodyToUserMission(body)); diff --git a/week5/src/modules/reviews/controllers/review.controller.ts b/week5/src/modules/reviews/controllers/review.controller.ts index b016af3..c2b5327 100644 --- a/week5/src/modules/reviews/controllers/review.controller.ts +++ b/week5/src/modules/reviews/controllers/review.controller.ts @@ -1,14 +1,19 @@ -import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; +import { Controller, Route, Post, Body, Tags, SuccessResponse, Response } from "tsoa"; import { bodyToReview, ReviewAddRequest, ReviewAddResponse } from "../dtos/review.dto.js"; import { reviewAdd } from "../services/review.service.js"; -import { ApiResponse, success } from "../../../common/responses/response.js"; +import { ApiResponse, ApiErrorResponse, success } from "../../../common/responses/response.js"; @Route("reviews") @Tags("Reviews") export class ReviewController extends Controller { - // 가게에 리뷰 추가하기 + /** + * 리뷰 추가 API + * @summary 특정 가게에 리뷰를 추가합니다. + */ @Post() @SuccessResponse("201", "Created") + @Response(400, "Bad Request") + @Response(500, "Internal Server Error") public async addReview(@Body() body: ReviewAddRequest): Promise> { this.setStatus(201); const review = await reviewAdd(bodyToReview(body)); diff --git a/week5/src/modules/stores/controllers/store.controller.ts b/week5/src/modules/stores/controllers/store.controller.ts index f9439b6..7cd653a 100644 --- a/week5/src/modules/stores/controllers/store.controller.ts +++ b/week5/src/modules/stores/controllers/store.controller.ts @@ -1,14 +1,19 @@ -import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; +import { Controller, Route, Post, Body, Tags, SuccessResponse, Response } from "tsoa"; import { bodyToStore, StoreAddRequest, StoreAddResponse } from "../dtos/store.dto.js"; import { storeAdd } from "../services/store.service.js"; -import { ApiResponse, success } from "../../../common/responses/response.js"; +import { ApiResponse, ApiErrorResponse, success } from "../../../common/responses/response.js"; @Route("stores") @Tags("Stores") export class StoreController extends Controller { - // 특정 지역에 가게 추가하기 + /** + * 가게 추가 API + * @summary 특정 지역에 새로운 가게를 추가합니다. + */ @Post() @SuccessResponse("201", "Created") + @Response(400, "Bad Request") + @Response(500, "Internal Server Error") public async addStore(@Body() body: StoreAddRequest): Promise> { this.setStatus(201); const store = await storeAdd(bodyToStore(body)); diff --git a/week5/src/modules/users/controllers/user.controller.ts b/week5/src/modules/users/controllers/user.controller.ts index f482227..8d901bf 100644 --- a/week5/src/modules/users/controllers/user.controller.ts +++ b/week5/src/modules/users/controllers/user.controller.ts @@ -1,13 +1,20 @@ -import { Controller, Route, Post, Body, Tags, SuccessResponse } from "tsoa"; +import { Controller, Route, Post, Body, Tags, SuccessResponse, Response } from "tsoa"; import { UserSignUpRequest, UserSignUpResponse } from "../dtos/user.dto.js"; import { userSignUp } from "../services/user.service.js"; -import { ApiResponse, success } from "../../../common/responses/response.js"; +import { ApiResponse, ApiErrorResponse, success } from "../../../common/responses/response.js"; @Route("users") @Tags("Users") export class UserController extends Controller { - @Post("signup") // 엔드포인트 정의 + /** + * 회원가입 API + * @summary 회원가입을 처리하는 엔드포인트입니다. + */ + @Post("signup") @SuccessResponse("200", "OK") + @Response(400, "Bad Request") + @Response(409, "Conflict - 이미 존재하는 이메일") + @Response(500, "Internal Server Error") public async handleUserSignUp( @Body() body: UserSignUpRequest, ): Promise> { diff --git a/week5/src/routes.ts b/week5/src/routes.ts index 8ae7cba..5d1f7fb 100644 --- a/week5/src/routes.ts +++ b/week5/src/routes.ts @@ -46,6 +46,16 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ApiErrorResponse": { + "dataType": "refObject", + "properties": { + "resultType": {"dataType":"enum","enums":["FAILED"],"required":true}, + "error": {"dataType":"nestedObjectLiteral","nestedProperties":{"data":{"dataType":"any","required":true},"message":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},"errorCode":{"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true}, + "data": {"dataType":"enum","enums":[null],"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "UserSignUpRequest": { "dataType": "refObject", "properties": { diff --git a/week5/src/swagger.json b/week5/src/swagger.json index e8ca37b..bcc2cb0 100644 --- a/week5/src/swagger.json +++ b/week5/src/swagger.json @@ -102,6 +102,50 @@ "type": "object", "additionalProperties": false }, + "ApiErrorResponse": { + "properties": { + "resultType": { + "type": "string", + "enum": [ + "FAILED" + ], + "nullable": false + }, + "error": { + "properties": { + "data": {}, + "message": { + "type": "string", + "nullable": true + }, + "errorCode": { + "type": "string", + "nullable": true + } + }, + "required": [ + "data", + "message", + "errorCode" + ], + "type": "object" + }, + "data": { + "type": "number", + "enum": [ + null + ], + "nullable": true + } + }, + "required": [ + "resultType", + "error", + "data" + ], + "type": "object", + "additionalProperties": false + }, "UserSignUpRequest": { "properties": { "email": { @@ -500,8 +544,40 @@ } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict - 이미 존재하는 이메일", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } } }, + "description": "회원가입 API", + "summary": "회원가입을 처리하는 엔드포인트입니다.", "tags": [ "Users" ], @@ -532,8 +608,30 @@ } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } } }, + "description": "가게 추가 API", + "summary": "특정 지역에 새로운 가게를 추가합니다.", "tags": [ "Stores" ], @@ -564,8 +662,30 @@ } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } } }, + "description": "리뷰 추가 API", + "summary": "특정 가게에 리뷰를 추가합니다.", "tags": [ "Reviews" ], @@ -596,8 +716,30 @@ } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } } }, + "description": "미션 도전 API", + "summary": "사용자가 특정 미션에 도전합니다.", "tags": [ "UserMissions" ], @@ -628,8 +770,30 @@ } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiErrorResponse" + } + } + } } }, + "description": "미션 추가 API", + "summary": "특정 가게에 미션을 추가합니다.", "tags": [ "Missions" ], From 542910c01e7402dc4a99575c562581418ac4aaf6 Mon Sep 17 00:00:00 2001 From: Yewon Date: Thu, 21 May 2026 10:41:43 +0900 Subject: [PATCH 20/20] =?UTF-8?q?feat:=20=ED=95=84=EB=93=9C=20=EB=82=B4=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modules/missions/dtos/mission.dto.ts | 9 ++ .../modules/missions/dtos/user-mission.dto.ts | 7 + week5/src/modules/reviews/dtos/review.dto.ts | 10 ++ week5/src/modules/stores/dtos/store.dto.ts | 8 ++ week5/src/modules/users/dtos/user.dto.ts | 10 +- week5/src/swagger.json | 126 ++++++++++++------ 6 files changed, 127 insertions(+), 43 deletions(-) diff --git a/week5/src/modules/missions/dtos/mission.dto.ts b/week5/src/modules/missions/dtos/mission.dto.ts index 1fc34c4..9db970a 100644 --- a/week5/src/modules/missions/dtos/mission.dto.ts +++ b/week5/src/modules/missions/dtos/mission.dto.ts @@ -1,8 +1,12 @@ // 미션 추가 요청 타입 export interface MissionAddRequest { + /** 미션 대상 가게 ID */ storeId: number; + /** 미션 달성 시 지급 포인트 */ reward: number; + /** 미션 마감일 (예: '2025-12-31') */ deadline: string; + /** 미션 내용 */ missionSpec: string; } @@ -18,10 +22,15 @@ export const bodyToMission = (body: MissionAddRequest) => { export interface MissionAddResponse { + /** 미션 ID */ id: number; + /** 미션 대상 가게 ID */ storeId: number; + /** 미션 달성 시 지급 포인트 */ reward: number; + /** 미션 마감일 */ deadline: Date; + /** 미션 내용 */ missionSpec: string; } diff --git a/week5/src/modules/missions/dtos/user-mission.dto.ts b/week5/src/modules/missions/dtos/user-mission.dto.ts index eb99ff1..c7870b6 100644 --- a/week5/src/modules/missions/dtos/user-mission.dto.ts +++ b/week5/src/modules/missions/dtos/user-mission.dto.ts @@ -1,6 +1,8 @@ // 미션 도전 요청 타입 export interface UserMissionAddRequest { + /** 도전하는 유저 ID */ userId: number; + /** 도전할 미션 ID */ missionId: number; } @@ -14,10 +16,15 @@ export const bodyToUserMission = (body: UserMissionAddRequest) => { // 미션 도전 응답 타입 export interface UserMissionAddResponse { + /** 유저 미션 ID */ id: number; + /** 유저 ID */ userId: number; + /** 미션 ID */ missionId: number; + /** 미션 진행 상태 (예: 'in_progress', 'complete') */ status: string; + /** 도전 시작일 */ createdAt: Date; } diff --git a/week5/src/modules/reviews/dtos/review.dto.ts b/week5/src/modules/reviews/dtos/review.dto.ts index 7518f20..c15a1c0 100644 --- a/week5/src/modules/reviews/dtos/review.dto.ts +++ b/week5/src/modules/reviews/dtos/review.dto.ts @@ -1,8 +1,12 @@ // 리뷰 추가 요청 타입 export interface ReviewAddRequest { + /** 리뷰 작성자 유저 ID */ userId: number; + /** 리뷰 대상 가게 ID */ storeId: number; + /** 리뷰 내용 */ body: string; + /** 평점 (1~5) */ score: number; } @@ -18,11 +22,17 @@ export const bodyToReview = (body: ReviewAddRequest) => { // 리뷰 추가 응답 타입 export interface ReviewAddResponse { + /** 리뷰 ID */ id: number; + /** 리뷰 작성자 유저 ID */ userId: number; + /** 리뷰 대상 가게 ID */ storeId: number; + /** 리뷰 내용 */ body: string; + /** 평점 (1~5) */ score: number; + /** 리뷰 작성일 */ createdAt: Date; } diff --git a/week5/src/modules/stores/dtos/store.dto.ts b/week5/src/modules/stores/dtos/store.dto.ts index 0e0c5f1..24827d0 100644 --- a/week5/src/modules/stores/dtos/store.dto.ts +++ b/week5/src/modules/stores/dtos/store.dto.ts @@ -1,7 +1,10 @@ // 가게 추가 요청 타입 export interface StoreAddRequest { + /** 지역 ID */ regionId: number; + /** 가게 이름 */ name: string; + /** 가게 주소 (선택) */ address?: string; } @@ -16,10 +19,15 @@ export const bodyToStore = (body: StoreAddRequest) => { // 가게 추가 응답 타입 export interface StoreAddResponse { + /** 가게 ID */ id: number; + /** 지역 ID */ regionId: number; + /** 가게 이름 */ name: string; + /** 가게 주소 */ address: string; + /** 평균 평점 */ score: number; } diff --git a/week5/src/modules/users/dtos/user.dto.ts b/week5/src/modules/users/dtos/user.dto.ts index 501fb0d..4af9d1f 100644 --- a/week5/src/modules/users/dtos/user.dto.ts +++ b/week5/src/modules/users/dtos/user.dto.ts @@ -1,12 +1,20 @@ // 1. 회원가입 요청 데이터의 설계도를 만듭니다. export interface UserSignUpRequest { + /** 유저 이메일 (로그인 시 사용) */ email: string; + /** 유저 이름 */ name: string; + /** 성별 (예: 'male', 'female') */ gender: string; + /** 생년월일 (예: '1999-01-01') */ birth: string; - address?: string; // ?가 붙으면 '없을 수도 있음(선택)'이라는 뜻이에요! + /** 주소 (선택) */ + address?: string; + /** 상세 주소 (선택) */ detailAddress?: string; + /** 전화번호 (예: '010-1234-5678') */ phoneNumber: string; + /** 선호 카테고리 ID 배열 (예: [1, 2]) */ preferences: number[]; } diff --git a/week5/src/swagger.json b/week5/src/swagger.json index bcc2cb0..d873f73 100644 --- a/week5/src/swagger.json +++ b/week5/src/swagger.json @@ -149,32 +149,40 @@ "UserSignUpRequest": { "properties": { "email": { - "type": "string" + "type": "string", + "description": "유저 이메일 (로그인 시 사용)" }, "name": { - "type": "string" + "type": "string", + "description": "유저 이름" }, "gender": { - "type": "string" + "type": "string", + "description": "성별 (예: 'male', 'female')" }, "birth": { - "type": "string" + "type": "string", + "description": "생년월일 (예: '1999-01-01')" }, "address": { - "type": "string" + "type": "string", + "description": "주소 (선택)" }, "detailAddress": { - "type": "string" + "type": "string", + "description": "상세 주소 (선택)" }, "phoneNumber": { - "type": "string" + "type": "string", + "description": "전화번호 (예: '010-1234-5678')" }, "preferences": { "items": { "type": "number", "format": "double" }, - "type": "array" + "type": "array", + "description": "선호 카테고리 ID 배열 (예: [1, 2])" } }, "required": [ @@ -192,21 +200,26 @@ "properties": { "id": { "type": "number", - "format": "double" + "format": "double", + "description": "가게 ID" }, "regionId": { "type": "number", - "format": "double" + "format": "double", + "description": "지역 ID" }, "name": { - "type": "string" + "type": "string", + "description": "가게 이름" }, "address": { - "type": "string" + "type": "string", + "description": "가게 주소" }, "score": { "type": "number", - "format": "double" + "format": "double", + "description": "평균 평점" } }, "required": [ @@ -251,13 +264,16 @@ "properties": { "regionId": { "type": "number", - "format": "double" + "format": "double", + "description": "지역 ID" }, "name": { - "type": "string" + "type": "string", + "description": "가게 이름" }, "address": { - "type": "string" + "type": "string", + "description": "가게 주소 (선택)" } }, "required": [ @@ -271,26 +287,32 @@ "properties": { "id": { "type": "number", - "format": "double" + "format": "double", + "description": "리뷰 ID" }, "userId": { "type": "number", - "format": "double" + "format": "double", + "description": "리뷰 작성자 유저 ID" }, "storeId": { "type": "number", - "format": "double" + "format": "double", + "description": "리뷰 대상 가게 ID" }, "body": { - "type": "string" + "type": "string", + "description": "리뷰 내용" }, "score": { "type": "number", - "format": "double" + "format": "double", + "description": "평점 (1~5)" }, "createdAt": { "type": "string", - "format": "date-time" + "format": "date-time", + "description": "리뷰 작성일" } }, "required": [ @@ -336,18 +358,22 @@ "properties": { "userId": { "type": "number", - "format": "double" + "format": "double", + "description": "리뷰 작성자 유저 ID" }, "storeId": { "type": "number", - "format": "double" + "format": "double", + "description": "리뷰 대상 가게 ID" }, "body": { - "type": "string" + "type": "string", + "description": "리뷰 내용" }, "score": { "type": "number", - "format": "double" + "format": "double", + "description": "평점 (1~5)" } }, "required": [ @@ -363,22 +389,27 @@ "properties": { "id": { "type": "number", - "format": "double" + "format": "double", + "description": "유저 미션 ID" }, "userId": { "type": "number", - "format": "double" + "format": "double", + "description": "유저 ID" }, "missionId": { "type": "number", - "format": "double" + "format": "double", + "description": "미션 ID" }, "status": { - "type": "string" + "type": "string", + "description": "미션 진행 상태 (예: 'in_progress', 'complete')" }, "createdAt": { "type": "string", - "format": "date-time" + "format": "date-time", + "description": "도전 시작일" } }, "required": [ @@ -423,11 +454,13 @@ "properties": { "userId": { "type": "number", - "format": "double" + "format": "double", + "description": "도전하는 유저 ID" }, "missionId": { "type": "number", - "format": "double" + "format": "double", + "description": "도전할 미션 ID" } }, "required": [ @@ -441,22 +474,27 @@ "properties": { "id": { "type": "number", - "format": "double" + "format": "double", + "description": "미션 ID" }, "storeId": { "type": "number", - "format": "double" + "format": "double", + "description": "미션 대상 가게 ID" }, "reward": { "type": "number", - "format": "double" + "format": "double", + "description": "미션 달성 시 지급 포인트" }, "deadline": { "type": "string", - "format": "date-time" + "format": "date-time", + "description": "미션 마감일" }, "missionSpec": { - "type": "string" + "type": "string", + "description": "미션 내용" } }, "required": [ @@ -501,17 +539,21 @@ "properties": { "storeId": { "type": "number", - "format": "double" + "format": "double", + "description": "미션 대상 가게 ID" }, "reward": { "type": "number", - "format": "double" + "format": "double", + "description": "미션 달성 시 지급 포인트" }, "deadline": { - "type": "string" + "type": "string", + "description": "미션 마감일 (예: '2025-12-31')" }, "missionSpec": { - "type": "string" + "type": "string", + "description": "미션 내용" } }, "required": [