diff --git a/Week10/J2H3233/mission/jsconfig.json b/Week10/J2H3233/mission/jsconfig.json new file mode 100644 index 0000000..0fa40d3 --- /dev/null +++ b/Week10/J2H3233/mission/jsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "target": "ES2024", + "jsx": "react-jsx", + "allowImportingTsExtensions": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strict": true + }, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] +} \ No newline at end of file diff --git a/Week10/J2H3233/mission/package-lock.json b/Week10/J2H3233/mission/package-lock.json new file mode 100644 index 0000000..1ccc756 --- /dev/null +++ b/Week10/J2H3233/mission/package-lock.json @@ -0,0 +1,2134 @@ +{ + "name": "mission", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mission", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@prisma/client": "^6.19.0", + "bcrypt": "^6.0.0", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.1", + "nodemon": "^3.1.10", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", + "swagger-autogen": "^2.23.7", + "swagger-ui-express": "^5.0.1" + }, + "devDependencies": { + "prisma": "^6.19.0" + } + }, + "node_modules/@prisma/client": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", + "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", + "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", + "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", + "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/fetch-engine": "6.19.0", + "@prisma/get-platform": "6.19.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", + "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", + "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/get-platform": "6.19.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", + "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.0" + } + }, + "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/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "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/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "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/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/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/compression/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/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/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-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-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/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/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/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "license": "MIT" + }, + "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/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "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/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "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/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "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/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.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/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/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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/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/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "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==", + "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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "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==", + "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==", + "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==", + "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==", + "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/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "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.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/oauth": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, + "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/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", + "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/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "license": "MIT", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "license": "MIT", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/prisma": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", + "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.19.0", + "@prisma/engines": "6.19.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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==", + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "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/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "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/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", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/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.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "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==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/swagger-autogen": { + "version": "2.23.7", + "resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz", + "integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==", + "license": "MIT", + "dependencies": { + "acorn": "^7.4.1", + "deepmerge": "^4.2.2", + "glob": "^7.1.7", + "json5": "^2.2.3" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.30.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.2.tgz", + "integrity": "sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==", + "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/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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==", + "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==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "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/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "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/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/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/Week10/J2H3233/mission/package.json b/Week10/J2H3233/mission/package.json new file mode 100644 index 0000000..9bc4254 --- /dev/null +++ b/Week10/J2H3233/mission/package.json @@ -0,0 +1,37 @@ +{ + "name": "mission", + "version": "1.0.0", + "description": "week6 mission", + "license": "ISC", + "author": "", + "type": "module", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "dev": "nodemon -e js,json,prisma --exec \"prisma generate && node src/index.js\"", + "test": "echo \"Error: no test specified\" && exit 1", + "prisma:migrate": "npx prisma migrate dev", + "prisma:push": "npx prisma db push", + "prisma:generate": "npx prisma generate" + }, + "dependencies": { + "@prisma/client": "^6.19.0", + "bcrypt": "^6.0.0", + "compression": "^1.8.1", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "dotenv": "^17.2.3", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.1", + "nodemon": "^3.1.10", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", + "swagger-autogen": "^2.23.7", + "swagger-ui-express": "^5.0.1" + }, + "devDependencies": { + "prisma": "^6.19.0" + } +} diff --git a/Week10/J2H3233/mission/prisma/migrations/20251026070047_y/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251026070047_y/migration.sql new file mode 100644 index 0000000..08383d2 --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251026070047_y/migration.sql @@ -0,0 +1,327 @@ +-- CreateTable +CREATE TABLE `region` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `sido` VARCHAR(255) NOT NULL, + `sigungu` VARCHAR(255) NOT NULL, + `eupmyeon` VARCHAR(255) NOT NULL, + `region_code` VARCHAR(255) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `category` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `category` VARCHAR(255) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `user` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `region_id` BIGINT NOT NULL, + `email` VARCHAR(255) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `gender` ENUM('male', 'female', 'other') NOT NULL, + `birth_date` DATE NOT NULL, + `address_detail` VARCHAR(255) NOT NULL, + `phone_num` VARCHAR(255) NOT NULL, + `is_phone_verified` BOOLEAN NOT NULL DEFAULT false, + `profile_image_url` VARCHAR(255) NULL, + `is_under_14` BOOLEAN NOT NULL DEFAULT false, + `is_location_agreed` BOOLEAN NOT NULL DEFAULT false, + `is_event_subscribed` BOOLEAN NOT NULL DEFAULT false, + `is_review_reply_notified` BOOLEAN NOT NULL DEFAULT true, + `is_inquiry_reply_notified` BOOLEAN NOT NULL DEFAULT true, + `is_withdrawn` BOOLEAN NOT NULL DEFAULT false, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + UNIQUE INDEX `user_email_key`(`email`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `social_login` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `provider` VARCHAR(255) NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `user_consents` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `type` ENUM('privacy', 'marketing', 'location') NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `category_user` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `category_id` BIGINT NOT NULL, + `user_id` BIGINT NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `store` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `category_id` BIGINT NOT NULL, + `region_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `store_name` VARCHAR(255) NOT NULL, + `status` ENUM('active', 'inactive', 'closed') NOT NULL, + `address_detail` VARCHAR(255) NULL, + `lat` DOUBLE NULL, + `lng` DOUBLE NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `store_image` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `store_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `image_url` VARCHAR(255) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `business_hour` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `store_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `day_of_week` ENUM('MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN') NOT NULL, + `open_time` TIME(0) NOT NULL, + `close_time` TIME(0) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `mission` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `store_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `amount` INTEGER NOT NULL, + `deadline` DATETIME(3) NOT NULL, + `point` INTEGER NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `user_mission` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NULL, + `mission_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `verification_code` INTEGER NOT NULL, + `status` ENUM('pending', 'in_progress', 'success', 'fail') NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `point_record` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `content` VARCHAR(255) NOT NULL, + `point` INTEGER NOT NULL, + `user_id` BIGINT NOT NULL, + `user_mission_id` BIGINT NOT NULL, + `id2` BIGINT NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `review` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `store_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `score` DOUBLE NOT NULL, + `content` TEXT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `review_image` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `review_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `image_url` VARCHAR(255) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `review_reply` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `review_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `content` TEXT NULL, + + UNIQUE INDEX `review_reply_review_id_key`(`review_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `inquiry` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `title` VARCHAR(255) NOT NULL, + `type` ENUM('general', 'payment', 'mission', 'bug') NOT NULL, + `content` TEXT NOT NULL, + `status` ENUM('waiting', 'answered', 'closed') NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `inquiry_reply` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `content` TEXT NULL, + `inquiry_id` BIGINT NOT NULL, + + UNIQUE INDEX `inquiry_reply_inquiry_id_key`(`inquiry_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `inquiry_image` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `inquiry_id` BIGINT NOT NULL, + `image_url` VARCHAR(255) NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `notification` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `is_read` BOOLEAN NOT NULL DEFAULT false, + `type` ENUM('system', 'reply', 'marketing') NOT NULL, + `title` VARCHAR(255) NOT NULL, + `content` TEXT NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `region_reward` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `status` BOOLEAN NOT NULL DEFAULT true, + `region_id` BIGINT NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `user` ADD CONSTRAINT `user_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `social_login` ADD CONSTRAINT `social_login_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_consents` ADD CONSTRAINT `user_consents_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `category_user` ADD CONSTRAINT `category_user_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `category_user` ADD CONSTRAINT `category_user_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `store` ADD CONSTRAINT `store_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `store` ADD CONSTRAINT `store_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `store_image` ADD CONSTRAINT `store_image_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `business_hour` ADD CONSTRAINT `business_hour_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `mission` ADD CONSTRAINT `mission_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_mission` ADD CONSTRAINT `user_mission_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_mission` ADD CONSTRAINT `user_mission_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `point_record` ADD CONSTRAINT `point_record_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `point_record` ADD CONSTRAINT `point_record_user_mission_id_fkey` FOREIGN KEY (`user_mission_id`) REFERENCES `user_mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review` ADD CONSTRAINT `review_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review` ADD CONSTRAINT `review_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review_image` ADD CONSTRAINT `review_image_review_id_fkey` FOREIGN KEY (`review_id`) REFERENCES `review`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review_reply` ADD CONSTRAINT `review_reply_review_id_fkey` FOREIGN KEY (`review_id`) REFERENCES `review`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `inquiry` ADD CONSTRAINT `inquiry_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `inquiry_reply` ADD CONSTRAINT `inquiry_reply_inquiry_id_fkey` FOREIGN KEY (`inquiry_id`) REFERENCES `inquiry`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `inquiry_image` ADD CONSTRAINT `inquiry_image_inquiry_id_fkey` FOREIGN KEY (`inquiry_id`) REFERENCES `inquiry`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `notification` ADD CONSTRAINT `notification_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `region_reward` ADD CONSTRAINT `region_reward_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251026074924_y/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251026074924_y/migration.sql new file mode 100644 index 0000000..b6e009a --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251026074924_y/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE `user_mission` MODIFY `verification_code` VARCHAR(8) NOT NULL; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251107112905/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251107112905/migration.sql new file mode 100644 index 0000000..a1cf92d --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251107112905/migration.sql @@ -0,0 +1,394 @@ +/* + Warnings: + + - The primary key for the `business_hour` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `business_hour` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `store_id` on the `business_hour` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `category` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `category` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `category_user` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `category_user` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `category_id` on the `category_user` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `category_user` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `inquiry` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `inquiry` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `inquiry` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `inquiry_image` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `inquiry_image` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `inquiry_id` on the `inquiry_image` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `inquiry_reply` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `inquiry_reply` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `inquiry_id` on the `inquiry_reply` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `mission` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `mission` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `store_id` on the `mission` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `notification` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `notification` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `notification` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `point_record` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `point_record` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `point_record` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_mission_id` on the `point_record` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `id2` on the `point_record` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `region` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `region` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `region_reward` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `region_reward` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `region_id` on the `region_reward` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `review` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `review` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `review` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `store_id` on the `review` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `review_image` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `review_image` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `review_id` on the `review_image` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `review_reply` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `review_reply` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `review_id` on the `review_reply` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `social_login` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `social_login` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `social_login` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `store` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `store` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `category_id` on the `store` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `region_id` on the `store` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `store_image` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `store_image` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `store_id` on the `store_image` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `user` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `user` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `region_id` on the `user` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `user_consents` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `user_consents` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `user_consents` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `user_mission` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `user_mission` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `user_id` on the `user_mission` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `mission_id` on the `user_mission` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + +*/ +-- DropForeignKey +ALTER TABLE `business_hour` DROP FOREIGN KEY `business_hour_store_id_fkey`; + +-- DropForeignKey +ALTER TABLE `category_user` DROP FOREIGN KEY `category_user_category_id_fkey`; + +-- DropForeignKey +ALTER TABLE `category_user` DROP FOREIGN KEY `category_user_user_id_fkey`; + +-- DropForeignKey +ALTER TABLE `inquiry` DROP FOREIGN KEY `inquiry_user_id_fkey`; + +-- DropForeignKey +ALTER TABLE `inquiry_image` DROP FOREIGN KEY `inquiry_image_inquiry_id_fkey`; + +-- DropForeignKey +ALTER TABLE `inquiry_reply` DROP FOREIGN KEY `inquiry_reply_inquiry_id_fkey`; + +-- DropForeignKey +ALTER TABLE `mission` DROP FOREIGN KEY `mission_store_id_fkey`; + +-- DropForeignKey +ALTER TABLE `notification` DROP FOREIGN KEY `notification_user_id_fkey`; + +-- DropForeignKey +ALTER TABLE `point_record` DROP FOREIGN KEY `point_record_user_id_fkey`; + +-- DropForeignKey +ALTER TABLE `point_record` DROP FOREIGN KEY `point_record_user_mission_id_fkey`; + +-- DropForeignKey +ALTER TABLE `region_reward` DROP FOREIGN KEY `region_reward_region_id_fkey`; + +-- DropForeignKey +ALTER TABLE `review` DROP FOREIGN KEY `review_store_id_fkey`; + +-- DropForeignKey +ALTER TABLE `review` DROP FOREIGN KEY `review_user_id_fkey`; + +-- DropForeignKey +ALTER TABLE `review_image` DROP FOREIGN KEY `review_image_review_id_fkey`; + +-- DropForeignKey +ALTER TABLE `review_reply` DROP FOREIGN KEY `review_reply_review_id_fkey`; + +-- DropForeignKey +ALTER TABLE `social_login` DROP FOREIGN KEY `social_login_user_id_fkey`; + +-- DropForeignKey +ALTER TABLE `store` DROP FOREIGN KEY `store_category_id_fkey`; + +-- DropForeignKey +ALTER TABLE `store` DROP FOREIGN KEY `store_region_id_fkey`; + +-- DropForeignKey +ALTER TABLE `store_image` DROP FOREIGN KEY `store_image_store_id_fkey`; + +-- DropForeignKey +ALTER TABLE `user` DROP FOREIGN KEY `user_region_id_fkey`; + +-- DropForeignKey +ALTER TABLE `user_consents` DROP FOREIGN KEY `user_consents_user_id_fkey`; + +-- DropForeignKey +ALTER TABLE `user_mission` DROP FOREIGN KEY `user_mission_mission_id_fkey`; + +-- DropForeignKey +ALTER TABLE `user_mission` DROP FOREIGN KEY `user_mission_user_id_fkey`; + +-- DropIndex +DROP INDEX `business_hour_store_id_fkey` ON `business_hour`; + +-- DropIndex +DROP INDEX `category_user_category_id_fkey` ON `category_user`; + +-- DropIndex +DROP INDEX `category_user_user_id_fkey` ON `category_user`; + +-- DropIndex +DROP INDEX `inquiry_user_id_fkey` ON `inquiry`; + +-- DropIndex +DROP INDEX `inquiry_image_inquiry_id_fkey` ON `inquiry_image`; + +-- DropIndex +DROP INDEX `mission_store_id_fkey` ON `mission`; + +-- DropIndex +DROP INDEX `notification_user_id_fkey` ON `notification`; + +-- DropIndex +DROP INDEX `point_record_user_id_fkey` ON `point_record`; + +-- DropIndex +DROP INDEX `point_record_user_mission_id_fkey` ON `point_record`; + +-- DropIndex +DROP INDEX `region_reward_region_id_fkey` ON `region_reward`; + +-- DropIndex +DROP INDEX `review_store_id_fkey` ON `review`; + +-- DropIndex +DROP INDEX `review_user_id_fkey` ON `review`; + +-- DropIndex +DROP INDEX `review_image_review_id_fkey` ON `review_image`; + +-- DropIndex +DROP INDEX `social_login_user_id_fkey` ON `social_login`; + +-- DropIndex +DROP INDEX `store_category_id_fkey` ON `store`; + +-- DropIndex +DROP INDEX `store_region_id_fkey` ON `store`; + +-- DropIndex +DROP INDEX `store_image_store_id_fkey` ON `store_image`; + +-- DropIndex +DROP INDEX `user_region_id_fkey` ON `user`; + +-- DropIndex +DROP INDEX `user_consents_user_id_fkey` ON `user_consents`; + +-- DropIndex +DROP INDEX `user_mission_mission_id_fkey` ON `user_mission`; + +-- DropIndex +DROP INDEX `user_mission_user_id_fkey` ON `user_mission`; + +-- AlterTable +ALTER TABLE `business_hour` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `store_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `category` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `category_user` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `category_id` INTEGER NOT NULL, + MODIFY `user_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `inquiry` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `user_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `inquiry_image` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `inquiry_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `inquiry_reply` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `inquiry_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `mission` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `store_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `notification` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `user_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `point_record` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `user_id` INTEGER NOT NULL, + MODIFY `user_mission_id` INTEGER NOT NULL, + MODIFY `id2` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `region` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `region_reward` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `region_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `review` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `user_id` INTEGER NOT NULL, + MODIFY `store_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `review_image` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `review_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `review_reply` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `review_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `social_login` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `user_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `store` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `category_id` INTEGER NOT NULL, + MODIFY `region_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `store_image` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `store_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `user` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `region_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `user_consents` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `user_id` INTEGER NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AlterTable +ALTER TABLE `user_mission` DROP PRIMARY KEY, + MODIFY `id` INTEGER NOT NULL AUTO_INCREMENT, + MODIFY `user_id` INTEGER NULL, + MODIFY `mission_id` INTEGER NOT NULL, + MODIFY `verification_code` VARCHAR(191) NOT NULL, + ADD PRIMARY KEY (`id`); + +-- AddForeignKey +ALTER TABLE `user` ADD CONSTRAINT `user_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `social_login` ADD CONSTRAINT `social_login_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_consents` ADD CONSTRAINT `user_consents_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `category_user` ADD CONSTRAINT `category_user_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `category_user` ADD CONSTRAINT `category_user_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `store` ADD CONSTRAINT `store_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `store` ADD CONSTRAINT `store_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `store_image` ADD CONSTRAINT `store_image_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `business_hour` ADD CONSTRAINT `business_hour_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `mission` ADD CONSTRAINT `mission_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_mission` ADD CONSTRAINT `user_mission_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user_mission` ADD CONSTRAINT `user_mission_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `point_record` ADD CONSTRAINT `point_record_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `point_record` ADD CONSTRAINT `point_record_user_mission_id_fkey` FOREIGN KEY (`user_mission_id`) REFERENCES `user_mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review` ADD CONSTRAINT `review_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review` ADD CONSTRAINT `review_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review_image` ADD CONSTRAINT `review_image_review_id_fkey` FOREIGN KEY (`review_id`) REFERENCES `review`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `review_reply` ADD CONSTRAINT `review_reply_review_id_fkey` FOREIGN KEY (`review_id`) REFERENCES `review`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `inquiry` ADD CONSTRAINT `inquiry_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `inquiry_reply` ADD CONSTRAINT `inquiry_reply_inquiry_id_fkey` FOREIGN KEY (`inquiry_id`) REFERENCES `inquiry`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `inquiry_image` ADD CONSTRAINT `inquiry_image_inquiry_id_fkey` FOREIGN KEY (`inquiry_id`) REFERENCES `inquiry`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `notification` ADD CONSTRAINT `notification_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `region_reward` ADD CONSTRAINT `region_reward_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251107152522/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251107152522/migration.sql new file mode 100644 index 0000000..600303f --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251107152522/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `id2` on the `point_record` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE `point_record` DROP COLUMN `id2`; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251121175144/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251121175144/migration.sql new file mode 100644 index 0000000..c448417 --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251121175144/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE `LocalLogin` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `user_id` INTEGER NOT NULL, + `login_id` VARCHAR(100) NOT NULL, + `password_hash` VARCHAR(255) NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + UNIQUE INDEX `LocalLogin_user_id_key`(`user_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `LocalLogin` ADD CONSTRAINT `LocalLogin_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251121182245/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251121182245/migration.sql new file mode 100644 index 0000000..32046e1 --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251121182245/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the `locallogin` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE `locallogin` DROP FOREIGN KEY `LocalLogin_user_id_fkey`; + +-- DropTable +DROP TABLE `locallogin`; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251121183730/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251121183730/migration.sql new file mode 100644 index 0000000..0c2539b --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251121183730/migration.sql @@ -0,0 +1,27 @@ +-- DropForeignKey +ALTER TABLE `user` DROP FOREIGN KEY `user_region_id_fkey`; + +-- DropIndex +DROP INDEX `user_region_id_fkey` ON `user`; + +-- AlterTable +ALTER TABLE `user` MODIFY `region_id` INTEGER NULL, + MODIFY `birth_date` DATE NULL; + +-- CreateTable +CREATE TABLE `local_login` ( + `id` INTEGER NOT NULL, + `login_id` VARCHAR(255) NOT NULL, + `password_hash` VARCHAR(255) NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + UNIQUE INDEX `local_login_login_id_key`(`login_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `local_login` ADD CONSTRAINT `local_login_id_fkey` FOREIGN KEY (`id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `user` ADD CONSTRAINT `user_region_id_fkey` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251122090728/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251122090728/migration.sql new file mode 100644 index 0000000..dc2e089 --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251122090728/migration.sql @@ -0,0 +1,36 @@ +/* + Warnings: + + - You are about to drop the `local_login` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `social_login` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE `local_login` DROP FOREIGN KEY `local_login_id_fkey`; + +-- DropForeignKey +ALTER TABLE `social_login` DROP FOREIGN KEY `social_login_user_id_fkey`; + +-- DropTable +DROP TABLE `local_login`; + +-- DropTable +DROP TABLE `social_login`; + +-- CreateTable +CREATE TABLE `login_info` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `user_id` INTEGER NOT NULL, + `sns_id` VARCHAR(255) NULL, + `password_hash` VARCHAR(255) NULL, + `refresh_token` VARCHAR(255) NULL, + `provider` ENUM('local', 'google', 'kakao') NOT NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + UNIQUE INDEX `login_info_user_id_key`(`user_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `login_info` ADD CONSTRAINT `login_info_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/Week10/J2H3233/mission/prisma/migrations/20251122095302/migration.sql b/Week10/J2H3233/mission/prisma/migrations/20251122095302/migration.sql new file mode 100644 index 0000000..c62c8d2 --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/20251122095302/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE `user` MODIFY `gender` ENUM('male', 'female', 'other') NULL, + MODIFY `address_detail` VARCHAR(255) NULL, + MODIFY `phone_num` VARCHAR(255) NULL; diff --git a/Week10/J2H3233/mission/prisma/migrations/migration_lock.toml b/Week10/J2H3233/mission/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..592fc0b --- /dev/null +++ b/Week10/J2H3233/mission/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "mysql" diff --git a/Week10/J2H3233/mission/prisma/schema.prisma b/Week10/J2H3233/mission/prisma/schema.prisma new file mode 100644 index 0000000..ef3665f --- /dev/null +++ b/Week10/J2H3233/mission/prisma/schema.prisma @@ -0,0 +1,375 @@ +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} + +model Region { + id Int @id @default(autoincrement()) + created_at DateTime @default(now()) @map("created_at") + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + sido String @db.VarChar(255) + sigungu String @db.VarChar(255) + eupmyeon String @db.VarChar(255) + region_code String @db.VarChar(255) + + users User[] + stores Store[] + regionRewards RegionReward[] + + @@map("region") +} + +model Category { + id Int @id @default(autoincrement()) + created_at DateTime @default(now()) @map("created_at") + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + category String @db.VarChar(255) + + stores Store[] + users CategoryUser[] + + @@map("category") +} + + +model User { + id Int @id @default(autoincrement()) + region_id Int? + email String @unique @db.VarChar(255) + name String @db.VarChar(255) + gender Gender? + birth_date DateTime?@db.Date + address_detail String? @db.VarChar(255) + phone_num String? @db.VarChar(255) + is_phone_verified Boolean @default(false) + profile_image_url String? @db.VarChar(255) + is_under_14 Boolean @default(false) + is_location_agreed Boolean @default(false) + is_event_subscribed Boolean @default(false) @map("is_event_subscribed") + is_review_reply_notified Boolean @default(true) + is_inquiry_reply_notified Boolean @default(true) + is_withdrawn Boolean @default(false) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt + login_info LoginInfo? + + region Region? @relation(fields: [region_id], references: [id]) + + + user_consents UserConsents[] + category_users CategoryUser[] + user_missions UserMission[] + point_records PointRecord[] + reviews Review[] + inquiries Inquiry[] + notifications Notification[] + + @@map("user") +} + +model LoginInfo { + id Int @id @default(autoincrement()) + user_id Int @unique + sns_id String? @db.VarChar(255) + password_hash String? @db.VarChar(255) + refresh_token String? @db.VarChar(255) + provider Provider + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt + + user User @relation(fields: [user_id], references: [id]) + + @@map("login_info") +} + +model UserConsents { + id Int @id @default(autoincrement()) + user_id Int + type ConsentType + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt + + user User @relation(fields: [user_id], references: [id]) + + @@map("user_consents") +} + +model CategoryUser { + id Int @id @default(autoincrement()) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + category_id Int + user_id Int + + category Category @relation(fields: [category_id], references: [id]) + user User @relation(fields: [user_id], references: [id]) + + @@map("category_user") +} + +model Store { + id Int @id @default(autoincrement()) + category_id Int + region_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + store_name String @db.VarChar(255) + status StoreStatus + address_detail String? @db.VarChar(255) + lat Float? @db.Double + lng Float? @db.Double + + category Category @relation(fields: [category_id], references: [id]) + region   Region @relation(fields: [region_id], references: [id]) + images StoreImage[] + hours BusinessHour[] + missions Mission[] + reviews Review[] + + @@map("store") +} + +model StoreImage { + id Int @id @default(autoincrement()) + store_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + image_url String? @db.VarChar(255) + + store Store @relation(fields: [store_id], references: [id]) + + @@map("store_image") +} + +model BusinessHour { + id Int @id @default(autoincrement()) + store_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + day_of_week DayOfWeek + open_time DateTime @db.Time(0) + close_time DateTime @db.Time(0) + + store Store @relation(fields: [store_id], references: [id]) + + @@map("business_hour") +} + +model Mission { + id Int @id @default(autoincrement()) + store_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + amount Int + deadline DateTime + point Int + + store Store @relation(fields: [store_id], references: [id]) + user_missions UserMission[] + + @@map("mission") +} + +model UserMission { + id Int @id @default(autoincrement()) + user_id Int? + mission_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + verification_code String + status MissionStatus + + user User? @relation(fields: [user_id], references: [id]) + mission Mission @relation(fields: [mission_id], references: [id]) + point_records PointRecord[] + + @@map("user_mission") +} + +model PointRecord { + id Int @id @default(autoincrement()) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + content String @db.VarChar(255) + point Int + user_id Int + user_mission_id Int + + user User @relation(fields: [user_id], references: [id]) + user_mission UserMission @relation(fields: [user_mission_id], references: [id]) + + @@map("point_record") +} + +model Review { + id Int @id @default(autoincrement()) + user_id Int + store_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + score Float + content String? @db.Text + + user User @relation(fields: [user_id], references: [id]) + store Store @relation(fields: [store_id], references: [id]) + images ReviewImage[] + reply ReviewReply? + + @@map("review") +} + +model ReviewImage { + id Int @id @default(autoincrement()) + review_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + image_url String? @db.VarChar(255) + + review Review @relation(fields: [review_id], references: [id]) + + @@map("review_image") +} + +model ReviewReply { + id Int @id @default(autoincrement()) + review_id Int @unique + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + content String? @db.Text + + review Review @relation(fields: [review_id], references: [id]) + + @@map("review_reply") +} + +model Inquiry { + id Int @id @default(autoincrement()) + user_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + title String @db.VarChar(255) + type InquiryType + content String @db.Text + status InquiryStatus + + user User @relation(fields: [user_id], references: [id]) + reply InquiryReply? + images InquiryImage[] + + @@map("inquiry") +} + +model InquiryReply { + id Int @id @default(autoincrement()) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + content String? @db.Text + inquiry_id Int @unique + + inquiry Inquiry @relation(fields: [inquiry_id], references: [id]) + + @@map("inquiry_reply") +} + +model InquiryImage { + id Int @id @default(autoincrement()) + inquiry_id Int + image_url String? @db.VarChar(255) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt + + inquiry Inquiry @relation(fields: [inquiry_id], references: [id]) + + @@map("inquiry_image") +} + +model Notification { + id Int @id @default(autoincrement()) + user_id Int + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt + is_read Boolean @default(false) + type NotificationType + title String @db.VarChar(255) + content String @db.Text + + user User @relation(fields: [user_id], references: [id]) + + @@map("notification") +} + +model RegionReward { + id Int @id @default(autoincrement()) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt @map("updated_at") + status Boolean @default(true) + region_id Int + + region Region @relation(fields: [region_id], references: [id]) + + @@map("region_reward") +} + +enum Gender { + male + female + other +} + +enum ConsentType { + privacy + marketing + location +} + +enum StoreStatus { + active + inactive + closed +} + +enum DayOfWeek { + MON + TUE + WED + THU + FRI + SAT + SUN +} + +enum MissionStatus { + pending + in_progress + success + fail +} + +enum InquiryType { + general + payment + mission + bug +} + +enum InquiryStatus { + waiting + answered + closed +} + +enum NotificationType { + system + reply + marketing +} + +enum Provider { + local + google + kakao +} \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/config/db.config.js b/Week10/J2H3233/mission/src/config/db.config.js new file mode 100644 index 0000000..662f06a --- /dev/null +++ b/Week10/J2H3233/mission/src/config/db.config.js @@ -0,0 +1,18 @@ +import dotenv from 'dotenv'; +import { PrismaClient } from "@prisma/client"; +dotenv.config(); + +const prisma = new PrismaClient({ + log : [ + { + emit: 'event', + level: 'query' + } + ] +}); + +prisma.$on('query', (event) => { + console.log(`[PRISMA/DB] ${event.duration}ms | SQL: ${event.query.slice(0, 100)}...`); +}); + +export { prisma }; diff --git a/Week10/J2H3233/mission/src/controllers/auth.controller.js b/Week10/J2H3233/mission/src/controllers/auth.controller.js new file mode 100644 index 0000000..6f4fe07 --- /dev/null +++ b/Week10/J2H3233/mission/src/controllers/auth.controller.js @@ -0,0 +1,128 @@ +import { CustomError, ErrorCodes } from '../error/customError.js'; +import { SuccessCodes } from '../error/responseCodes.js'; +import { LocalregisterService } from "../services/auth.service.js"; +import { LocalLoginService } from "../services/auth.service.js"; +import { refreshTokenService } from "../services/auth.service.js"; +import { clearRefreshToken, generateAccessToken, generateRefreshToken} from "../services/auth.service.js"; +import { GoogleLoginService } from '../services/auth.service.js'; + +export const hanlderLocalRegister = async (req, res, next ) => { + try { + const { + email, + name, + password + } = req.body; + + const user = await LocalregisterService( + email, + name, + password + ); + res.jsonSuccess( + 201, + SuccessCodes.Created, + '사용자 등록이 완료되었습니다.', + user + ); + + } catch (error) { + next(error); + } +} + +export const handlerLocalLogin = async (req, res, next) => { + try { + const { email, password } = req.body; + const { accessToken, refreshToken } = await LocalLoginService(email, password); + + res.cookie('refreshToken', refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 7 * 24 * 60 * 60 * 1000, + }); + + return res.jsonSuccess( + 200, + SuccessCodes.OK, + '로그인에 성공하였습니다.', + { accessToken, refreshToken } + ); + } catch (error) { + next(error); + } +} + +export const hanlderTokenRefresh = async (req, res, next) => { + try { + const clientRefreshToken = req.cookies.refreshToken; + + if (!clientRefreshToken) { + throw new CustomError(401, ErrorCodes.UNAUTHORIZED, 'Refresh token이 제공되지 않았습니다.'); + } + + const { accessToken, refreshToken } = await refreshTokenService(clientRefreshToken); + + res.cookie('refreshToken', refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 7 * 24 * 60 * 60 * 1000, + }); + + res.jsonSuccess( + 200, + SuccessCodes.OK, + '토큰이 재발급되었습니다.', + { accessToken } + ); + } catch (error) { + next(error); + } +} + +export const handlerLogout = async (req, res, next) => { + try { + res.clearCookie('refreshToken', { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + }); + + await clearRefreshToken(req.user.id ,null); + + res.jsonSuccess( + 200, + SuccessCodes.OK, + '로그아웃에 성공하였습니다.', + {} + ); + } catch (error) { + next(error); + } +} + +export const handlerGoogleCallback = async (req, res, next) => { + try { + const user = req.user; + + const { accessToken, refreshToken } = await GoogleLoginService(user.email); + + res.cookie('refreshToken', refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 7 * 24 * 60 * 60 * 1000, + }); + + return res.jsonSuccess( + 200, + SuccessCodes.OK, + '구글 로그인에 성공하였습니다.', + { accessToken, refreshToken } + ); + } catch (error) { + next(error); + } +} diff --git a/Week10/J2H3233/mission/src/controllers/mission.controller.js b/Week10/J2H3233/mission/src/controllers/mission.controller.js new file mode 100644 index 0000000..7172f69 --- /dev/null +++ b/Week10/J2H3233/mission/src/controllers/mission.controller.js @@ -0,0 +1,306 @@ +import { completeUserMission,getMissionList,createMission, addMissionToUser, getUserMissionList} from '../services/mission.service.js'; +import { getMissionListDto,createMissionDto, addMissionToUserDto } from '../dtos/mission.dto.js'; +import { CustomError, ErrorCodes } from '../error/customError.js'; +import { SuccessCodes } from '../error/responseCodes.js'; + + +export const handlerCreateMission = async (req, res, next) => { + /* + #swagger.tags = ['Mission'] + #swagger.summary = '가게 미션 추가하기' + #swagger.parameters['storeId'] = { + in: 'path', + description: '가게 ID', + required: true, + type: 'integer' + } + #swagger.parameters['mission'] = { + in: 'body', + description: '미션 정보', + required: true, + schema: { + type: 'object', + required: ['amount', 'deadline', 'point'], + properties: { + amount: { + type: 'integer', + description: '목표 금액', + example: 10000 + }, + deadline: { + type: 'string', + format: 'date', + description: '마감일', + example: '2024-12-31' + }, + point: { + type: 'integer', + description: '포인트 보상', + example: 1000 + } + } + } + } + #swagger.responses[201] = { + description: '미션 생성 성공', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: true }, + data: { + type: 'object', + properties: { + id: { type: 'integer', example: 1 }, + storeId: { type: 'integer', example: 1 }, + amount: { type: 'integer', example: 10000 }, + deadline: { type: 'string', example: '2024-12-31' }, + point: { type: 'integer', example: 1000 } + } + }, + message: { type: 'string', example: '가게 미션을 성공적으로 추가하였습니다.' } + } + } + } + #swagger.responses[400] = { + description: '잘못된 요청', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: false }, + error: { + type: 'object', + properties: { + code: { type: 'string', example: 'INVALID_INPUT' }, + message: { type: 'string', example: '유효하지 않은 가게 ID입니다.' } + } + } + } + } + } + */ + const { storeId } = req.params; + const data = req.body; + + try { + if (!storeId || isNaN(parseInt(storeId))) { + throw new CustomError( + 400, + ErrorCodes.INVALID_INPUT, + '유효하지 않은 가게 ID입니다.' + ); + } + const mission = await createMission(createMissionDto(storeId, data)); + res.jsonSuccess( + 201, + SuccessCodes.Created, + '가게 미션을 성공적으로 추가하였습니다.', + mission, + ); + } catch (error) { + next(error); + } +}; + +export const handlerAddMissionToUser = async (req, res, next) => { + /* + #swagger.tags = ['Mission'] + #swagger.summary = '가게의 미션을 도전 중인 미션에 추가' + #swagger.parameters['missionId'] = { + in: 'path', + description: '미션 ID', + required: true, + type: 'integer' + } + #swagger.parameters['userMission'] = { + in: 'body', + description: '사용자 미션 정보', + required: true, + schema: { + type: 'object', + required: ['userId'], + properties: { + userId: { + type: 'integer', + description: '사용자 ID', + example: 1 + } + } + } + } + #swagger.responses[201] = { + description: '사용자 미션 추가 성공', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: true }, + data: { + type: 'object', + properties: { + id: { type: 'integer', example: 1 }, + missionId: { type: 'integer', example: 1 }, + userId: { type: 'integer', example: 1 }, + status: { type: 'string', example: 'IN_PROGRESS' } + } + }, + message: { type: 'string', example: '미션을 사용자에게 추가하였습니다.' } + } + } + } + #swagger.responses[400] = { + description: '잘못된 요청', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: false }, + error: { + type: 'object', + properties: { + code: { type: 'string', example: 'INVALID_INPUT' }, + message: { type: 'string', example: '유효하지 않은 미션 ID입니다.' } + } + } + } + } + } + */ + const { missionId } = req.params; + const data = req.body; + try { + + if (!missionId || isNaN(parseInt(missionId))) { + throw new CustomError( + 400, + ErrorCodes.INVALID_INPUT, + '유효하지 않은 미션 ID입니다.' + ); + } + + const result = await addMissionToUser(addMissionToUserDto(missionId, data)); + res.jsonSuccess( + 201, + SuccessCodes.Created, + '미션을 사용자에게 추가하였습니다.', + result + ); + } catch (error) { + next(error); + } +}; + +export const handlerGetMissionList = async (req, res, next) => { + /* + #swagger.tags = ['Mission'] + #swagger.summary = '가게 미션 목록 조회하기' + #swagger.parameters['storeId'] = { + in: 'path', + description: '가게 ID', + required: true, + type: 'integer' + } + #swagger.responses[200] = { + description: '미션 목록 조회 성공', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: true }, + data: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer', example: 1 }, + amount: { type: 'integer', example: 10000 }, + deadline: { type: 'string', example: '2024-12-31' }, + point: { type: 'integer', example: 1000 } + } + } + }, + message: { type: 'string', example: '가게의 미션 목록 조회에 성공했습니다' } + } + } + } + #swagger.responses[400] = { + description: '잘못된 요청', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: false }, + error: { + type: 'object', + properties: { + code: { type: 'string', example: 'INVALID_INPUT' }, + message: { type: 'string', example: '유효하지 않은 가게 ID입니다.' } + } + } + } + } + } + */ + const { storeId } = req.params; + try { + if (!storeId || isNaN(parseInt(storeId))) { + throw new CustomError( + 400, + ErrorCodes.INVALID_INPUT, + '유효하지 않은 가게 ID입니다.' + ); + } + + + const missions = await getMissionList(getMissionListDto(storeId)); + res.jsonSuccess( + 200, + SuccessCodes.OK, + '미션 목록 조회에 성공했습니다.', + missions, + ); + } catch (error) { + next(error); + } +}; + +export const handlerGetUserMissionList = async (req, res, next) => { + const userId = req.user.userId; + try { + if (!userId || isNaN(parseInt(userId))) { + throw new CustomError( + 400, + ErrorCodes.INVALID_INPUT, + '유효하지 않은 사용자 ID입니다.' + ); + } + + const userMissions = await getUserMissionList(parseInt(userId)); + res.jsonSuccess( + 200, + SuccessCodes.OK, + '사용자 미션 목록 조회에 성공했습니다', + userMissions, + ); + } catch (error) { + next(error); + } +}; + +export const handlerCompleteUserMission = async (req, res, next) => { + const { userMissionId } = req.params.userMissionId; + if (!userMissionId || isNaN(parseInt(userMissionId))) { + throw new CustomError( + 400, + ErrorCodes.INVALID_INPUT, + '유효하지 않은 미션 ID입니다.' + ); + } + + try { + const result = await completeUserMission(parseInt(userMissionId)); + res.jsonSuccess( + 200, + SuccessCodes.OK, + '사용자 미션 진행 완료에 성공했습니다', + result, + ); + } catch (error) { + next(error); + } +}; diff --git a/Week10/J2H3233/mission/src/controllers/review.controller.js b/Week10/J2H3233/mission/src/controllers/review.controller.js new file mode 100644 index 0000000..db061de --- /dev/null +++ b/Week10/J2H3233/mission/src/controllers/review.controller.js @@ -0,0 +1,182 @@ +import { createReview, getReviewList } from "../services/review.service.js"; +import { createReviewDto, getReviewDto } from "../dtos/review.dto.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; +import { SuccessCodes } from '../error/responseCodes.js'; + +export const handlerCreateReview = async (req, res, next) => { + /* + #swagger.tags = ['Review'] + #swagger.summary = '가게에 리뷰 추가하기' + #swagger.parameters['storeId'] = { + in: 'path', + description: '가게 ID', + required: true, + type: 'integer' + } + #swagger.parameters['review'] = { + in: 'body', + description: '리뷰 정보', + required: true, + schema: { + type: 'object', + required: ['userId', 'score', 'content'], + properties: { + userId: { + type: 'integer', + description: '사용자 ID', + example: 1 + }, + score: { + type: 'number', + description: '평점 (1-5)', + example: 4.5 + }, + content: { + type: 'string', + description: '리뷰 내용', + example: '맛있고 친절해요!' + } + } + } + } + #swagger.responses[201] = { + description: '리뷰 생성 성공', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: true }, + data: { + type: 'object', + properties: { + id: { type: 'integer', example: 1 }, + storeId: { type: 'integer', example: 1 }, + userId: { type: 'integer', example: 1 }, + score: { type: 'number', example: 4.5 }, + content: { type: 'string', example: '맛있고 친절해요!' } + } + }, + message: { type: 'string', example: '리뷰 작성에 성공했습니다' } + } + } + } + #swagger.responses[400] = { + description: '잘못된 요청', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: false }, + error: { + type: 'object', + properties: { + code: { type: 'string', example: 'INVALID_INPUT' }, + message: { type: 'string', example: '유효하지 않은 가게 ID입니다.' } + } + } + } + } + } + */ + const { storeId } = req.params; + const data = req.body; + + + try { + + if (!storeId || isNaN(parseInt(storeId))) { + throw new CustomError( + 400, + ErrorCodes.INVALID_INPUT, + '유효하지 않은 가게 ID입니다.' + ); + } + const review = await createReview(createReviewDto(storeId, data)); + + res.jsonSuccess( + 201, + SuccessCodes.Created, + '리뷰 작성에 성공했습니다', + review, + ) + } catch (error) { + next(error); + } +} + +export const handlerGetReviewList = async (req, res, next) => { + /* + #swagger.tags = ['Review'] + #swagger.summary = '사용자 작성 리뷰 목록 조회' + #swagger.parameters['userId'] = { + in: 'path', + description: '사용자 ID', + required: true, + type: 'integer' + } + #swagger.responses[200] = { + description: '리뷰 목록 조회 성공', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: true }, + data: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer', example: 1 }, + storeId: { type: 'integer', example: 1 }, + userId: { type: 'integer', example: 1 }, + score: { type: 'number', example: 4.5 }, + content: { type: 'string', example: '맛있고 친절해요!' }, + createdAt: { type: 'string', example: '2024-01-01T00:00:00.000Z' }, + store: { + type: 'object', + properties: { + name: { type: 'string', example: '맛있는 치킨집' }, + address: { type: 'string', example: '서울시 강남구' } + } + } + } + } + }, + message: { type: 'string', example: '리뷰 목록 조회에 성공했습니다' } + } + } + } + #swagger.responses[400] = { + description: '잘못된 요청', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: false }, + error: { + type: 'object', + properties: { + code: { type: 'string', example: 'INVALID_INPUT' }, + message: { type: 'string', example: '유효하지 않은 사용자 ID입니다.' } + } + } + } + } + } + */ + const userId = req.user.userId; + try { + if (!userId || isNaN(parseInt(userId))) { + throw new CustomError( + 400, + ErrorCodes.INVALID_INPUT, + '유효하지 않은 사용자 ID입니다.' + ); + } + const reviews = await getReviewList(getReviewDto(userId)); + res.jsonSuccess( + 200, + SuccessCodes.OK, + '리뷰 목록 조회에 성공했습니다', + reviews, + ); + } catch (error) { + next(error); + } +}; \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/controllers/store.controller.js b/Week10/J2H3233/mission/src/controllers/store.controller.js new file mode 100644 index 0000000..0e66f09 --- /dev/null +++ b/Week10/J2H3233/mission/src/controllers/store.controller.js @@ -0,0 +1,174 @@ +import { createStoreDto } from '../dtos/store.dto.js'; +import { createStore } from '../services/store.service.js'; +import { CustomError, ErrorCodes } from '../error/customError.js'; +import { SuccessCodes } from '../error/responseCodes.js'; + +// 특정 지역에 가게 추가하기 +export const handlerCreateStore = async (req, res, next) => { + /* + #swagger.tags = ['Store'] + #swagger.summary = '특정 지역에 가게 추가하기' + #swagger.parameters['regionCode'] = { + in: 'path', + description: '지역 코드', + required: true, + type: 'string' + } + #swagger.parameters['store'] = { + in: 'body', + description: '가게 정보', + required: true, + schema: { + type: 'object', + required: ['name', 'address', 'category'], + properties: { + name: { + type: 'string', + description: '가게 이름', + example: '맛있는 치킨집' + }, + address: { + type: 'string', + description: '가게 주소', + example: '서울시 강남구 역삼동 123-45' + }, + category: { + type: 'string', + description: '가게 카테고리', + example: '치킨' + } + } + } + } + #swagger.responses[201] = { + description: '가게 생성 성공', + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: true + }, + data: { + type: 'object', + properties: { + id: { + type: 'integer', + description: '가게 ID', + example: 1 + }, + name: { + type: 'string', + description: '가게 이름', + example: '맛있는 치킨집' + }, + address: { + type: 'string', + description: '가게 주소', + example: '서울시 강남구 역삼동 123-45' + } + } + }, + message: { + type: 'string', + example: '가게를 성공적으로 추가하였습니다.' + } + } + } + } + #swagger.responses[400] = { + description: '잘못된 요청', + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: false + }, + error: { + type: 'object', + properties: { + code: { + type: 'string', + example: 'INVALID_INPUT' + }, + message: { + type: 'string', + example: '유효하지 않은 지역 코드입니다.' + } + } + } + } + } + } + #swagger.responses[404] = { + description: '리소스를 찾을 수 없음', + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: false + }, + error: { + type: 'object', + properties: { + code: { + type: 'string', + example: 'REGION_NOT_FOUND' + }, + message: { + type: 'string', + example: '존재하지 않는 지역 코드입니다.' + } + } + } + } + } + } + #swagger.responses[500] = { + description: '서버 내부 오류', + schema: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: false + }, + error: { + type: 'object', + properties: { + code: { + type: 'string', + example: 'STORE_CREATE_FAILED' + }, + message: { + type: 'string', + example: '가게 생성에 실패했습니다.' + } + } + } + } + } + } + */ + const { regionCode } = req.params; + const data = req.body; + + try { + if (!regionCode || typeof regionCode !== 'string' || regionCode.trim() === '') { + throw new CustomError(400, ErrorCodes.INVALID_INPUT, '유효하지 않은 지역 코드입니다.'); + } + const store = await createStore(createStoreDto(data, regionCode)); + + res.jsonSuccess( + 201, + SuccessCodes.Created, + '가게를 성공적으로 추가하였습니다.', + store + ); + } catch (error) { + next(error); + } +}; + diff --git a/Week10/J2H3233/mission/src/dtos/mission.dto.js b/Week10/J2H3233/mission/src/dtos/mission.dto.js new file mode 100644 index 0000000..14996ba --- /dev/null +++ b/Week10/J2H3233/mission/src/dtos/mission.dto.js @@ -0,0 +1,67 @@ +export const createMissionDto = (storeId, body) => { + return { + storeId: Number(storeId), + amount : Number(body.amount), + deadline : body.deadline, + point : Number(body.point), + }; +} + +export const responseCreateMissionDto = (mission) => { + return { + id: Number(mission.id), + storeId: Number(mission.store_id), + amount: Number(mission.amount), + deadline: mission.deadline, + point: Number(mission.point), + }; +} + +export const addMissionToUserDto = (missionId, body) => { + return { + missionId: Number(missionId), + userId: Number(body.userId), + }; +} + +export const responseAddMissionToUserDto = (userMission) => { + return { + id: Number(userMission.id), + missionId: Number(userMission.mission_id), + userId: Number(userMission.user_id), + status: userMission.status, + verificationCode: userMission.verification_code, + }; +} + +export const getMissionListDto = (storeId) => { + return Number(storeId); +} + +export const responseGetMissionListDto = (missions, nextCursor) => { + return missions.map((mission) => ({ + id: Number(mission.id), + amount: Number(mission.amount), + deadline: mission.deadline, + point: Number(mission.point), + nextCursor: nextCursor ? Number(nextCursor) : null, + })); +} + +export const responseGetUserMissionListDto = (userMissions, nextCursor) => { + return userMissions.map((userMission) => ({ + id: Number(userMission.id), + missionId: Number(userMission.mission_id), + amount: Number(userMission.mission.amount), + deadline: userMission.mission.deadline, + point: Number(userMission.mission.point), + nextCursor: nextCursor ? Number(nextCursor) : null, + })); +} + +export const responseCompleteUserMissionDto = (userMission) => { + return { + id: Number(userMission.id), + status: userMission.status, + }; +} \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/dtos/review.dto.js b/Week10/J2H3233/mission/src/dtos/review.dto.js new file mode 100644 index 0000000..a31e317 --- /dev/null +++ b/Week10/J2H3233/mission/src/dtos/review.dto.js @@ -0,0 +1,39 @@ +export const createReviewDto = (id, body) => { + return { + storeId : Number(id), + userId : Number(body.userId), + score : parseFloat(body.score), + content : body.content, + }; +}; + +export const responseCreateReviewDto = (review) => { + return { + id: Number(review.id), + storeId: Number(review.store_id), + userId: Number(review.user_id), + score: parseFloat(review.score), + content: review.content, + }; +}; + +export const getReviewDto = (userId) => { + return Number(userId); +}; + +export const responseGetReviewDto = (reviews, nextCursor) => { + return reviews.map((review) => ({ + id: Number(review.id), + storeId: Number(review.store_id), + userId: Number(review.user_id), + score: parseFloat(review.score), + content: review.content, + createdAt: review.created_at, + updatedAt: review.updated_at, + store: { + name: review.store.name, + address: review.store.address, + }, + nextCursor: nextCursor ? Number(nextCursor) : null, + })); +} \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/dtos/store.dto.js b/Week10/J2H3233/mission/src/dtos/store.dto.js new file mode 100644 index 0000000..22fcedd --- /dev/null +++ b/Week10/J2H3233/mission/src/dtos/store.dto.js @@ -0,0 +1,16 @@ +export const createStoreDto = (body, regionCode) => { + return { + regionCode: regionCode, + name: body.name, + address: body.address, + category: body.category, + }; +}; + +export const responseCreateStoreDto = (store) => { + return { + id: store.id, + name: store.name, + address: store.address, + }; +}; diff --git a/Week10/J2H3233/mission/src/error/customError.js b/Week10/J2H3233/mission/src/error/customError.js new file mode 100644 index 0000000..ad1c15a --- /dev/null +++ b/Week10/J2H3233/mission/src/error/customError.js @@ -0,0 +1,13 @@ +import { ErrorCodes } from './responseCodes.js'; + +class CustomError extends Error { + constructor(httpStatus = 500, code, message = '알 수 없는 오류가 발생했습니다.') { + super(message); + + this.code = code || ErrorCodes.UNKNOWN_ERROR; + this.httpStatus = httpStatus; + + } +} + +export { CustomError, ErrorCodes }; \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/error/responseCodes.js b/Week10/J2H3233/mission/src/error/responseCodes.js new file mode 100644 index 0000000..24b29dc --- /dev/null +++ b/Week10/J2H3233/mission/src/error/responseCodes.js @@ -0,0 +1,64 @@ + export const ErrorCodes = Object.freeze({ + + // 유효성 검사 관련 + VALIDATION_FAILED: 'VALIDATION_FAILED', + + // 리소스 관련 + USER_NOT_FOUND: 'USER_NOT_FOUND', + STORE_NOT_FOUND: 'STORE_NOT_FOUND', + REVIEW_NOT_FOUND: 'REVIEW_NOT_FOUND', + CATEGORY_NOT_FOUND: 'CATEGORY_NOT_FOUND', + REGION_NOT_FOUND: 'REGION_NOT_FOUND', + USER_MISSION_NOT_FOUND: 'USER_MISSION_NOT_FOUND', + MISSION_NOT_FOUND: 'MISSION_NOT_FOUND', + + // 중복 관련 + USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS', + STORE_ALREADY_EXISTS: 'STORE_ALREADY_EXISTS', + REVIEW_ALREADY_EXISTS: 'REVIEW_ALREADY_EXISTS', + CATEGORY_ALREADY_EXISTS: 'CATEGORY_ALREADY_EXISTS', + REGION_ALREADY_EXISTS: 'REGION_ALREADY_EXISTS', + USER_MISSION_ALREADY_EXISTS: 'USER_MISSION_ALREADY_EXISTS', + USER_MISSION_ALREADY_COMPLETED: 'USER_MISSION_ALREADY_COMPLETED', + MISSION_ALREADY_EXISTS: 'MISSION_ALREADY_EXISTS', + + // 리소스 생성 실패 + USER_CREATE_FAILED: 'USER_CREATE_FAILED', + STORE_CREATE_FAILED: 'STORE_CREATE_FAILED', + REVIEW_CREATE_FAILED: 'REVIEW_CREATE_FAILED', + CATEGORY_CREATE_FAILED: 'CATEGORY_CREATE_FAILED', + REGION_CREATE_FAILED: 'REGION_CREATE_FAILED', + USER_MISSION_CREATE_FAILED: 'USER_MISSION_CREATE_FAILED', + MISSION_CREATE_FAILED: 'MISSION_CREATE_FAILED', + + + // 인증 및 권한 관련 + UNAUTHORIZED: 'UNAUTHORIZED', + FORBIDDEN: 'FORBIDDEN', + INVALID_CREDENTIALS: 'INVALID_CREDENTIALS', + INVALID_TOKEN: 'INVALID_TOKEN', + + // 입력 관련 + INVALID_INPUT: 'INVALID_INPUT', + + // 데이터베이스 관련 + DB_OPERATION_FAILED: 'DB_OPERATION_FAILED', + + // 범용 + INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR', + UNKNOWN_ERROR: 'UNKNOWN_ERROR' + +}); + + + export const SuccessCodes = Object.freeze({ + + Ok: 'OK', + Created: 'CREATED', + Updated: 'UPDATED', + Deleted: 'DELETED', + Retrieved: 'RETRIEVED' + +}); + + diff --git a/Week10/J2H3233/mission/src/index.js b/Week10/J2H3233/mission/src/index.js new file mode 100644 index 0000000..aed1c14 --- /dev/null +++ b/Week10/J2H3233/mission/src/index.js @@ -0,0 +1,195 @@ +import express from 'express'; +import cors from 'cors'; +import morgan from 'morgan'; +import dotenv from 'dotenv'; +import compression from 'compression'; +import cookieParser from 'cookie-parser'; +import swaggerAutogen from "swagger-autogen"; +import swaggerUiExpress from "swagger-ui-express"; +import passport from 'passport'; + +import { responseHandler } from './middlewares/responseHandler.js'; +import { errorHandler } from './middlewares/errorHandler.js'; +import { handlerCreateStore } from './controllers/store.controller.js'; +import { + handlerCreateReview, + handlerGetReviewList } from './controllers/review.controller.js'; +import { + handlerCompleteUserMission, + handlerCreateMission, + handlerAddMissionToUser, + handlerGetMissionList, + handlerGetUserMissionList } from './controllers/mission.controller.js'; +import { + handlerLocalLogin, + hanlderLocalRegister, + hanlderTokenRefresh, + handlerLogout, + handlerGoogleCallback } from './controllers/auth.controller.js'; +import { + jwtStrategy, + jwtAuthenticate } from './middlewares/passport/jwtStrategy.js'; +import { googleStrategy } from './middlewares/passport/googleStrategy.js'; + + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3000; + +// 미들웨어 설정 +app.use(compression({ threshold: 512 })); +app.use(cors({ + origin: ['http://127.0.0.1:5500', 'https://mydomain.com', 'http://localhost:3000'], + methods: ['GET', 'POST', 'PATCH', 'DELETE'], + credentials: true, +})); +app.use(morgan('combined')); +app.use(morgan('dev')); +app.use(cookieParser()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use( + "/docs", + swaggerUiExpress.serve, + swaggerUiExpress.setup(null, { + swaggerOptions: { + url: "/openapi.json", + }, + }) +); + +app.use(responseHandler); + + + + +app.use(passport.initialize()); +passport.use(jwtStrategy); +passport.use(googleStrategy); + + + +// 라우터 설정 + +// week5 +// 특정 지역에 가게 추가하기 +app.post('/api/v1/store/:regionCode',jwtAuthenticate, handlerCreateStore); + +// 가게에 리뷰 추가하기 +app.post('/api/v1/store/review/:storeId',jwtAuthenticate,handlerCreateReview); + +// 가게 미션 추가하기 +app.post('/api/v1/store/mission/:storeId',jwtAuthenticate, handlerCreateMission); + +// 가게의 미션을 도전 중인 미션에 추가 +app.post('/api/v1/mission/:missionId',jwtAuthenticate, handlerAddMissionToUser); + + +// week6 +// 작성 리뷰 조회하기 +app.get('/api/v1/users/:userId/reviews',handlerGetReviewList); + +// 가게 미션 목록 조회하기 +app.get('/api/v1/stores/:storeId/missions', handlerGetMissionList); + +// 내가 진행 중인 미션 목록 +app.get('/api/v1/users/:userId/missions/ongoing',jwtAuthenticate, handlerGetUserMissionList); + +// 내가 진행 중인 미션 진행 완료로 바꾸기 +app.patch('/api/v1/user-missions/:userMissionId',jwtAuthenticate, handlerCompleteUserMission); + + + +// 인증 인가 +app.post('/api/v1/auth/register', hanlderLocalRegister); + +app.post('/api/v1/auth/login/local', handlerLocalLogin); + +app.post('/api/v1/auth/logout', jwtAuthenticate, handlerLogout); + +app.post('/api/v1/auth/refresh', hanlderTokenRefresh); + +app.get('/api/v1/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] })); + +app.get('/api/v1/auth/google/callback', + passport.authenticate('google', { session: false, failureRedirect: '/login' }), + handlerGoogleCallback +); + + + + + + + + + +app.get('/small', (req, res) => { + res.send(`작은데이터`); + console.log('/small 응답: 압축되지 않음 (512b 미만)'); +}); + +app.get('/large', (req, res) => { + res.send(`Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`); + console.log('/large 응답: 압축됨 (512b 초과)'); +}); + +app.get("/openapi.json", async (req, res, next) => { + + const options = { + openapi: "3.0.0", + disableLogs: true, + writeOutputFile: false, + }; + const outputFile = "/dev/null"; + const routes = ["./src/index.js"]; + + const doc = { + info: { + title: "UMC 9th", + description: "UMC 9th Node.js 테스트 프로젝트입니다.", + }, + servers: [ + { + url: "http://test.nodetest.r-e.kr:3000", + description: "Production Server" + }, + { + url: "http://localhost:3000", + description: "Local Server" + } + ], + components: { + securitySchemes: { + bearerAuth: { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + in: "header", + description: "JWT 액세스 토큰을 입력하세요" + } + } + }, + security: [ + { + bearerAuth: [] + } + ] + }; + + const result = await swaggerAutogen(options)(outputFile, routes, doc); + res.json(result ? result.data : null); +}); + +app.get('/week10', (req, res) => { + res.send('10주차 서버 연결 테스트용'); +}); + + +// 에러 처리 미들웨어 +app.use(errorHandler); + +app.listen(PORT, () => { + console.log(`http://localhost:${PORT} 서버 시작`); +}); \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/middlewares/errorHandler.js b/Week10/J2H3233/mission/src/middlewares/errorHandler.js new file mode 100644 index 0000000..a8f0846 --- /dev/null +++ b/Week10/J2H3233/mission/src/middlewares/errorHandler.js @@ -0,0 +1,12 @@ +import { SuccessCodes } from "../error/responseCodes.js"; + +export const errorHandler = (err, req, res, next) => { + console.error(err.stack); + + const httpStatus = err.httpStatus || 500; + + res.status(httpStatus).json({ + code: err.code || SuccessCodes.INTERNAL_SERVER_ERROR , + message: err.message || '서버 오류', + }); +}; diff --git a/Week10/J2H3233/mission/src/middlewares/passport/googleStrategy.js b/Week10/J2H3233/mission/src/middlewares/passport/googleStrategy.js new file mode 100644 index 0000000..4275f13 --- /dev/null +++ b/Week10/J2H3233/mission/src/middlewares/passport/googleStrategy.js @@ -0,0 +1,36 @@ +import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; +import { findByEmail } from '../../repositories/auth.repository.js'; +import { SocialRegisterService } from '../../services/auth.service.js'; +import { Provider } from "@prisma/client"; + +export const googleStrategy = new GoogleStrategy({ + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackURL: '/api/v1/auth/google/callback', + scope: ['email', 'profile'] +}, async (accessToken, refreshToken, profile, done) => { + try { + const snsId = profile.id; + const email = profile.emails[0].value; + const name = profile.displayName; + + let user = await findByEmail(email); + + if (!user) { + user = await SocialRegisterService( + email, + name, + snsId, + Provider.google + ); + } else { + user = await updateUserInfo(user.id,{ + name: name, + snsId: snsId, + }) + } + return done(null, user); + } catch (error) { + return done(error); + } +}); \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/middlewares/passport/jwtStrategy.js b/Week10/J2H3233/mission/src/middlewares/passport/jwtStrategy.js new file mode 100644 index 0000000..707adfc --- /dev/null +++ b/Week10/J2H3233/mission/src/middlewares/passport/jwtStrategy.js @@ -0,0 +1,25 @@ +import { Strategy , ExtractJwt } from 'passport-jwt'; +import { findByUserId } from '../../repositories/user.repository.js'; +import passport from 'passport'; + +const opts = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.ACCESS_TOKEN_SECRET, + ignoreExpiration: false, +}; + +export const jwtStrategy = new Strategy(opts, async (jwt_payload, done) => { + try { + const user = await findByUserId(jwt_payload.userId); + + if (user) { + return done(null, user); + } else { + return done(null, false); + } + } catch (error) { + return done(error, false); + } +}); + +export const jwtAuthenticate = passport.authenticate('jwt', { session: false }); \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/middlewares/responseHandler.js b/Week10/J2H3233/mission/src/middlewares/responseHandler.js new file mode 100644 index 0000000..b4fad19 --- /dev/null +++ b/Week10/J2H3233/mission/src/middlewares/responseHandler.js @@ -0,0 +1,14 @@ +import { SuccessCodes } from '../error/responseCodes.js'; + +export const responseHandler = (req, res, next) => { + res.jsonSuccess = (statusCode, code, message, data) => { + res.status(statusCode).json({ + isSuccess: true, + code: code, + message: message, + data: data, + }); + }; + + next(); +}; diff --git a/Week10/J2H3233/mission/src/repositories/auth.repository.js b/Week10/J2H3233/mission/src/repositories/auth.repository.js new file mode 100644 index 0000000..e914955 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/auth.repository.js @@ -0,0 +1,121 @@ +import { prisma } from "../config/db.config.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; + +export const createAccount = async (data, client = prisma) => { + try { + return await client.user.create({ + data: { + email: data.email, + name: data.name, + gender: data.gender, + birth_date: data.birth_date, + phone_num : data.phone_num, + }, + }); + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '사용자 계정을 생성하는 중 오류가 발생하였습니다.'); + } +} + +export const createLoginInfo = async (data, client = prisma) => { + try { + return await client.loginInfo.create({ + data: { + user : { + connect: { id: data.user_id } + }, + password_hash: data.password_hash, + sns_id: data.sns_id, + provider: data.provider, + }, + }); + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '로컬 로그인 정보를 생성하는 중 오류가 발생하였습니다.'); + } +} + +export const findLoginInfoByEmailAndProvider = async (email, provider, client = prisma) => { + try { + return await client.loginInfo.findFirst({ + where: { + user: { + email: email + }, + provider: provider, + }, + select: { + id: true, + password_hash: true, + sns_id: true, + provider: true, + user: { + select: { + id: true, + email: true, + name: true, + } + } + } + }); + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '이메일로 로그인 정보를 조회하는 중 오류가 발생하였습니다.'); + } +} + +export const findLoginInfoByEmail = async (email, client = prisma) => { + try { + return await client.loginInfo.findFirst({ + where: { + user: { + email: email + } + }, + select: { + id: true, + refresh_token: true, + user: { + select: { + id: true, + email: true, + } + } + } + }); + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '이메일로 로그인 정보를 조회하는 중 오류가 발생하였습니다.'); + } +} + + + + +export const updateRefreshToken = async (userId, refreshToken, client = prisma) => { + try { + return await client.loginInfo.update({ + where: { + user_id: userId, + }, + data: { + refresh_token: refreshToken, + }, + }); + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '리프레시 토큰을 업데이트하는 중 오류가 발생하였습니다.'); + } +} + +export const findByEmail = async (email, client = prisma) => { + try { + return await client.user.findUnique({ + where: { email: email } + }); + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '이메일로 사용자를 조회하는 중 오류가 발생하였습니다.'); + } +} \ No newline at end of file diff --git a/Week10/J2H3233/mission/src/repositories/category.repository.js b/Week10/J2H3233/mission/src/repositories/category.repository.js new file mode 100644 index 0000000..dbaf466 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/category.repository.js @@ -0,0 +1,16 @@ +import { prisma } from "../config/db.config.js" +import { CustomError, ErrorCodes } from '../error/customError.js'; + +export const findByCategory = async (category, client = prisma) => { + try { + const categoryData = await client.category.findUnique({ + where: { category } + }); + return categoryData; + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '카테고리를 조회하는 중 오류가 발생하였습니다.'); + } +}; + + diff --git a/Week10/J2H3233/mission/src/repositories/mission.repository.js b/Week10/J2H3233/mission/src/repositories/mission.repository.js new file mode 100644 index 0000000..74ec879 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/mission.repository.js @@ -0,0 +1,156 @@ +import { prisma } from "../config/db.config.js"; +import { CustomError } from '../error/customError.js'; + +export const getMissionById = async (missionId, client = prisma) => { + try { + const mission = await client.mission.findUnique({ + where: { id: missionId } + }); + return mission; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','미션을 조회하는 중 오류가 발생하였습니다.'); + } +}; + +export const insertMission = async (data, client = prisma) => { + try { + const result = await client.mission.create({ + data: { + store_id: data.storeId, + amount: data.amount, + deadline: data.deadline, + point: data.point + } + }); + return result; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','미션을 생성하는 중 오류가 발생하였습니다.'); + } +}; + +export const insertUserMission = async (missionId, data, status, verificationCode, client = prisma) => { + try { + const result = await client.userMission.create({ + data: { + mission_id: missionId, + user_id: data.userId, + status, + verification_code: verificationCode + } + }); + return result; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','사용자 미션 추가 중 오류가 발생하였습니다.'); + } +}; + +export const getUserMissionById = async (userMissionId, client = prisma) => { + try { + const userMission = await client.userMission.findUnique({ + where: { id: userMissionId } + }); + return userMission; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','사용자 미션 조회 중 오류가 발생하였습니다.'); + } +}; + +export const existUserMission = async (data, client = prisma) => { + try { + const userMission = await client.userMission.findFirst({ + where: { + mission_id: data.missionId, + user_id: data.userId + } + }); + return userMission !== null; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','사용자 미션 존재 여부 확인 중 오류가 발생하였습니다.'); + } +}; + +export const getMissionsByStoreId = async (storeId, cursor, client = prisma) => { + const pageSize = 10; + try { + const missions = await client.mission.findMany({ + where: { store_id: storeId }, + orderBy: { created_at: 'desc' }, + take: pageSize + 1, + ...(cursor && { + skip: 1, + cursor: { id: cursor } + }) + }); + const hasMore = missions.length > pageSize; + const data = hasMore ? missions.slice(0, pageSize) : missions; + + return { + missions: data, + nextCursor: hasMore ? Number(data[data.length - 1].id) : null, + }; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','가게의 미션 목록 조회 중 오류가 발생하였습니다.'); + } +}; + +export const getUserMissionsByUserId = async (userId, cursor, client = prisma) => { + const pageSize = 10; + try { + const userMissions = await client.userMission.findMany({ + where: { user_id: userId, status: 'in_progress' }, + include: { mission: true }, + orderBy: { created_at: 'desc' }, + take: pageSize + 1, + ...(cursor && { + skip: 1, + cursor: { id: cursor } + }) + }); + + const hasMore = userMissions.length > pageSize; + const data = hasMore ? userMissions.slice(0, pageSize) : userMissions; + + return { + userMissions: data, + nextCursor: hasMore ? Number(data[data.length - 1].id) : null, + }; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','사용자 미션 목록 조회 중 오류가 발생하였습니다.'); + } +}; + +export const updateUserMissionStatus = async (userMissionId, status, client = prisma) => { + try { + const updatedUserMission = await client.userMission.update({ + where: { id: userMissionId }, + data: { status } + }); + return updatedUserMission; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','사용자 미션 상태 업데이트 중 오류가 발생하였습니다.'); + } +}; + +export const insertPointByUserMission = async (userMission, client = prisma) => { + try { + const result = await client.point.create({ + data: { + userId: userMission.userId, + points: userMission.mission.points, + userMissionId: userMission.id, + } + }); + return result; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','포인트를 추가하는 중 오류가 발생하였습니다.'); + } +}; diff --git a/Week10/J2H3233/mission/src/repositories/point.repository.js b/Week10/J2H3233/mission/src/repositories/point.repository.js new file mode 100644 index 0000000..64efd20 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/point.repository.js @@ -0,0 +1,5 @@ +import { prisma } from "../config/db.config.js"; +import { CustomError } from '../error/customError.js'; + + + diff --git a/Week10/J2H3233/mission/src/repositories/region.repository.js b/Week10/J2H3233/mission/src/repositories/region.repository.js new file mode 100644 index 0000000..742ad40 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/region.repository.js @@ -0,0 +1,26 @@ +import { prisma } from "../config/db.config.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; + +export const findByRegionCode = async (regionCode, client = prisma) => { + try { + const region = await client.region.findUnique({ + where: { region_code: regionCode } + }); + return region; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','지역 코드 조회중 오류가 발생하였습니다.'); + } +}; + +export const findById = async (id, client = prisma) => { + try { + const region = await client.region.findUnique({ + where: { id } + }); + return region; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','지역을 조회하는 중 오류가 발생하였습니다.'); + } +}; diff --git a/Week10/J2H3233/mission/src/repositories/review.repository.js b/Week10/J2H3233/mission/src/repositories/review.repository.js new file mode 100644 index 0000000..2dc6931 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/review.repository.js @@ -0,0 +1,68 @@ +import { prisma } from "../config/db.config.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; + +export const insertReview = async (data) => { + try { + const review = await prisma.review.create({ + data: { + store_id: data.storeId, + user_id: data.userId, + score: data.score, + content: data.content + } + }); + return review; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','리뷰 생성 중 오류가 발생하였습니다.'); + } +}; + + +export const getReviewsById = async (reviewId, client = prisma) => { + try { + const review = await client.review.findUnique({ + where: { id: reviewId }, + }); + return review; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','리뷰 조회 중 오류가 발생하였습니다.'); + } +}; + + +export const getReviewsByUserId = async (userId, cursor, client = prisma) => { + const pageSize = 10; + try { + const reviews = await client.review.findMany({ + where: { user_id: userId }, + include: { + store: { + select: { + name: true, + address: true, + }, + }, + }, + orderBy: { created_at: 'desc' }, + take: pageSize + 1, + ...(cursor && { + skip: 1, + cursor: { id: cursor } + }) + }); + const hasMore = reviews.length > pageSize; + const data = hasMore ? reviews.slice(0, pageSize) : reviews; + + return { + reviews: data, + nextCursor: hasMore ? Number(data[data.length - 1].id) : null, + }; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,'DB_OPERATION_FAILED','리뷰 목록 조회 중 오류가 발생하였습니다.'); + } +}; + + diff --git a/Week10/J2H3233/mission/src/repositories/store.repository.js b/Week10/J2H3233/mission/src/repositories/store.repository.js new file mode 100644 index 0000000..fc9a767 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/store.repository.js @@ -0,0 +1,45 @@ +import { prisma } from "../config/db.config.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; + +export const insertStore = async (data, region, category) => { + try { + const store = await prisma.store.create({ + data: { + region_id: region.id, + store_name: data.name, + address_detail: data.address, + category_id: category.id + } + }); + return store; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,ErrorCodes.DB_OPERATION_FAILED,'가게 생성 중 오류가 발생하였습니다.'); + } +}; + +export const getStoreById = async (storeId, client = prisma) => { + try { + const store = await client.store.findUnique({ + where: { id: storeId } + }); + return store; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,ErrorCodes.DB_OPERATION_FAILED,'가게 조회 중 오류가 발생하였습니다.'); + } +}; + +export const existStoreById = async (storeId, client = prisma) => { + try { + const count = await client.store.count({ + where: { id: storeId } + }); + return count > 0; + } catch (error) { + console.error(error.stack); + throw new CustomError(500,ErrorCodes.DB_OPERATION_FAILED,'가게 조회 중 오류가 발생하였습니다.'); + } +}; + + diff --git a/Week10/J2H3233/mission/src/repositories/user.repository.js b/Week10/J2H3233/mission/src/repositories/user.repository.js new file mode 100644 index 0000000..152def8 --- /dev/null +++ b/Week10/J2H3233/mission/src/repositories/user.repository.js @@ -0,0 +1,16 @@ +import { prisma } from "../config/db.config.js" +import { CustomError, ErrorCodes } from '../error/customError.js'; + +export const findByUserId = async (userId, client = prisma) => { + try { + const userData = await client.user.findUnique({ + where: { id: userId } + }); + return userData; + } catch (error) { + console.error(error.stack); + throw new CustomError(500, ErrorCodes.DB_OPERATION_FAILED, '사용자 정보를 조회하는 중 오류가 발생하였습니다.'); + } +}; + + diff --git a/Week10/J2H3233/mission/src/services/auth.service.js b/Week10/J2H3233/mission/src/services/auth.service.js new file mode 100644 index 0000000..ed81ab3 --- /dev/null +++ b/Week10/J2H3233/mission/src/services/auth.service.js @@ -0,0 +1,154 @@ +import { CustomError} from "../error/customError.js"; +import { ErrorCodes } from "../error/customError.js"; +import { prisma } from "../config/db.config.js"; +import bcrypt from "bcrypt"; +import { createAccount, createLoginInfo, updateRefreshToken, findLoginInfoByEmailAndProvider, findLoginInfoByEmail} from "../repositories/auth.repository.js"; +import { Provider } from "@prisma/client"; +import jwt from "jsonwebtoken"; + +export const hashPassword = async (password) => { + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(password, saltRounds); + return hashedPassword; +} + +export const LocalregisterService = async ( + email, + name, + password +) => { + + const user = await prisma.$transaction(async (tx) => { + const createdUser = await createAccount({ + email, + name, + }, tx); + + const password_hash = await hashPassword(password); + + await createLoginInfo({ + password_hash, + provider: Provider.local, + user_id: createdUser.id, + }, tx); + + return createdUser; + }); + return user; + +} + +export const SocialRegisterService = async ( + email, + name, + sns_id, + provider +) => { + const user = await prisma.$transaction(async (tx) => { + const createdUser = await createAccount({ + email, + name, + }, tx); + + await createLoginInfo({ + sns_id, + provider, + user_id: createdUser.id, + }, tx); + + return createdUser; + }); + return user; +} + +export const LocalLoginService = async (email, password) => { + + const loginInfo = await findLoginInfoByEmailAndProvider(email, Provider.LOCAL); + + if (!loginInfo) { + throw new CustomError(401, ErrorCodes.INVALID_CREDENTIALS, '이메일 또는 비밀번호가 올바르지 않습니다.'); + } + + const isPasswordValid = await bcrypt.compare(password, loginInfo.password_hash); + + if (!isPasswordValid) { + throw new CustomError(401, ErrorCodes.INVALID_CREDENTIALS, '이메일 또는 비밀번호가 올바르지 않습니다.'); + } + + const accessToken = generateAccessToken(loginInfo.user.id, loginInfo.user.email); + const refreshToken = generateRefreshToken(loginInfo.user.id, loginInfo.user.email); + + await updateRefreshToken(loginInfo.user.id, refreshToken, prisma); + + return { accessToken, refreshToken } + +} + +export const GoogleLoginService = async (email) => { + const loginInfo = await findLoginInfoByEmailAndProvider(email, Provider.google); + + if (!loginInfo) { + throw new CustomError(401, ErrorCodes.USER_NOT_FOUND, '등록되지 않은 사용자입니다.'); + } + + const accessToken = generateAccessToken(loginInfo.user.id, loginInfo.user.email); + const refreshToken = generateRefreshToken(loginInfo.user.id, loginInfo.user.email); + + await updateRefreshToken(loginInfo.user.id, refreshToken, prisma); + + return { accessToken, refreshToken }; +} + + +export const refreshTokenService = async (clientRefreshToken) => { + try{ + const { email } = verifyRefreshToken(clientRefreshToken); + } catch (error) { + throw new CustomError(401, ErrorCodes.INVALID_TOKEN, '유효하지 않은 리프레시 토큰입니다.'); + } + + const { refresh_token, user } = await findLoginInfoByEmail(email); + + if (!refresh_token || refresh_token !== clientRefreshToken) { + throw new CustomError(401, ErrorCodes.INVALID_TOKEN, '유효하지 않은 리프레시 토큰입니다.'); + } + + const newAccessToken = generateAccessToken(user.id, user.email); + const newRefreshToken = generateRefreshToken(user.id, user.email); + await updateRefreshToken(user.id, newRefreshToken, prisma); + + return { newAccessToken, newRefreshToken }; +} + + +export const clearRefreshToken = async (userId) => { + await updateRefreshToken(userId, null); +} + +export const generateAccessToken = (userId, email) => { + return jwt.sign( + { userId, email }, + process.env.ACCESS_TOKEN_SECRET, + { expiresIn: process.env.ACCESS_TOKEN_EXPIRY } + ); +} + +export const generateRefreshToken = (userId, email) => { + return jwt.sign( + { userId, email }, + process.env.REFRESH_TOKEN_SECRET, + { expiresIn: process.env.REFRESH_TOKEN_EXPIRY } + ); +} + +export const verifyRefreshToken = (token) => { + try { + const decoded = jwt.verify(token, process.env.REFRESH_TOKEN_SECRET); + return decoded; + } catch (error) { + throw new CustomError(401, ErrorCodes.INVALID_TOKEN, '유효하지 않은 리프레시 토큰입니다.'); + } +} + + + diff --git a/Week10/J2H3233/mission/src/services/mission.service.js b/Week10/J2H3233/mission/src/services/mission.service.js new file mode 100644 index 0000000..c70cac7 --- /dev/null +++ b/Week10/J2H3233/mission/src/services/mission.service.js @@ -0,0 +1,79 @@ +import { existStoreById } from "../repositories/store.repository.js"; +import { updateUserMissionStatus, getUserMissionsByUserId, getMissionsByStoreId, existUserMission, insertMission, getMissionById, insertUserMission, getUserMissionById, insertPointByUserMission } from "../repositories/mission.repository.js"; +import { responseGetMissionListDto, responseCompleteUserMissionDto, responseCreateMissionDto, responseAddMissionToUserDto, responseGetUserMissionListDto } from "../dtos/mission.dto.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; +import { prisma } from "../config/db.config.js"; + +export const createMission = async (data) => { + + const store = await existStoreById(data.storeId); + if(!store) { + throw new CustomError(404, ErrorCodes.STORE_NOT_FOUND, '존재하지 않는 가게입니다.'); + } + const mission = await insertMission(data); + if(!mission) { + throw new CustomError(404, ErrorCodes.MISSION_CREATE_FAILED, '미션 생성에 실패했습니다.'); + } + + return responseCreateMissionDto(mission); +}; + +export const addMissionToUser = async (data) => { + const mission = await getMissionById(data.missionId); + if (!mission) { + throw new CustomError(404, ErrorCodes.MISSION_NOT_FOUND, '존재하지 않는 미션입니다.'); + } + + if(await existUserMission(data)) { + throw new CustomError(409, ErrorCodes.MISSION_ALREADY_EXISTS, '이미 추가된 미션입니다.'); + } + + const generateVerificationCode = () => { + const min = 10000000; + const max = 99999999; + + const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min; + + return randomNumber.toString(); + } + + const newUserMission = await insertUserMission(data.missionId, data, "in_progress", generateVerificationCode()); + if (!newUserMission) { + throw new CustomError(500, ErrorCodes.USER_MISSION_CREATE_FAILED, '미션을 사용자에게 추가하는 데 실패했습니다.'); + } + + + return responseAddMissionToUserDto(newUserMission); +}; + + +export const getMissionList = async (storeId) => { + const { missions, nextCursor } = await getMissionsByStoreId(storeId); + return responseGetMissionListDto(missions, nextCursor); +}; + +export const getUserMissionList = async (userId) => { + const { userMissions, nextCursor } = await getUserMissionsByUserId(userId); + return responseGetUserMissionListDto(userMissions, nextCursor); +} + +export const completeUserMission = async (userMissionId) => { + + const userMission = await getUserMissionById(userMissionId); + if (!userMission) { + throw new CustomError(404, ErrorCodes.USER_MISSION_NOT_FOUND, '존재하지 않는 사용자 미션입니다.'); + } + + if (userMission.status === 'success') { + throw new CustomError(400, ErrorCodes.USER_MISSION_ALREADY_COMPLETED, '이미 완료된 미션입니다.'); + } + + const updatedUserMission = await prisma.$transaction(async (tx) => { + const newUpdatedUserMission = await updateUserMissionStatus(userMissionId, 'success', tx); + await insertPointByUserMission(userMission, tx); + + return newUpdatedUserMission; + }); + + return responseCompleteUserMissionDto(updatedUserMission); +}; diff --git a/Week10/J2H3233/mission/src/services/review.service.js b/Week10/J2H3233/mission/src/services/review.service.js new file mode 100644 index 0000000..f20b324 --- /dev/null +++ b/Week10/J2H3233/mission/src/services/review.service.js @@ -0,0 +1,29 @@ +import { getStoreById } from "../repositories/store.repository.js"; +import { insertReview, getReviewsById, getReviewsByUserId } from "../repositories/review.repository.js"; +import { responseCreateReviewDto, responseGetReviewDto } from "../dtos/review.dto.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; +import { findByUserId } from "../repositories/user.repository.js"; + +export const createReview = async (data) => { + const store = await getStoreById(data.storeId); + if(!store) { + throw new CustomError(404, ErrorCodes.STORE_NOT_FOUND, '존재하지 않는 가게입니다.'); + } + const review = await insertReview(data); + if(!review) { + throw new CustomError(500, ErrorCodes.REVIEW_CREATE_FAILED, '리뷰 생성에 실패했습니다.'); + } + + return responseCreateReviewDto(review); +}; + +export const getReviewList = async (userId) => { + + const user = await findByUserId(userId); + if (!user) { + throw new CustomError(404, ErrorCodes.USER_NOT_FOUND, '존재하지 않는 사용자입니다.'); + } + + const {reviews, nextCursor} = await getReviewsByUserId(userId); + return responseGetReviewDto(reviews, nextCursor); +}; diff --git a/Week10/J2H3233/mission/src/services/store.service.js b/Week10/J2H3233/mission/src/services/store.service.js new file mode 100644 index 0000000..2f32018 --- /dev/null +++ b/Week10/J2H3233/mission/src/services/store.service.js @@ -0,0 +1,25 @@ +import { getStoreById, insertStore } from "../repositories/store.repository.js"; +import { findByRegionCode } from "../repositories/region.repository.js"; +import { findByCategory } from "../repositories/category.repository.js"; +import { responseCreateStoreDto } from "../dtos/store.dto.js"; +import { CustomError, ErrorCodes } from '../error/customError.js'; + +export const createStore = async (data) => { + const region = await findByRegionCode(data.regionCode); + if(!region) { + throw new CustomError(404, ErrorCodes.REGION_NOT_FOUND, '존재하지 않는 지역 코드입니다.'); + } + + const category = await findByCategory(data.category); + if(!category) { + throw new CustomError(404, ErrorCodes.CATEGORY_NOT_FOUND, '존재하지 않는 카테고리입니다.'); + } + + const newStore = await insertStore(data, region, category); + if(!newStore) { + throw new CustomError(500, ErrorCodes.STORE_CREATE_FAILED, '가게 생성에 실패했습니다.'); + } + + return responseCreateStoreDto(newStore); +} +