├── .gitignore ├── package-lock.json ├── package.json ├── public └── temp │ └── .gitkeep └── src ├── app.js ├── constants.js ├── controllers ├── comment.controller.js ├── dashboard.controller.js ├── healthcheck.controller.js ├── like.controller.js ├── playlist.controller.js ├── subscription.controller.js ├── tweet.controller.js ├── user.controller.js └── video.controller.js ├── db └── index.js ├── index.js ├── middlewares ├── auth.middleware.js └── multer.middleware.js ├── models ├── comment.model.js ├── like.model.js ├── playlist.model.js ├── subscriptions.model.js ├── tweet.model.js ├── user.model.js └── video.model.js ├── routes ├── comment.routes.js ├── dashboard.routes.js ├── healthcheck.routes.js ├── like.routes.js ├── playlist.routes.js ├── subscription.routes.js ├── tweet.routes.js ├── user.routes.js └── video.routes.js └── utils ├── .prettierignore ├── .prettierrc ├── ApiError.js ├── ApiResponse.js ├── asyncHandler.js └── cloudinary.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | 120 | # End of https://mrkandreev.name/snippets/gitignore-generator/#Node -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backendproject", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "backendproject", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "bcrypt": "^5.1.1", 13 | "cloudinary": "^2.2.0", 14 | "cookie-parser": "^1.4.6", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.4.5", 17 | "express": "^4.19.2", 18 | "jsonwebtoken": "^9.0.2", 19 | "mongoose": "^8.4.3", 20 | "mongoose-aggregate-paginate-v2": "^1.0.7", 21 | "multer": "^1.4.5-lts.1" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^3.1.3", 25 | "prettier": "^3.3.2" 26 | } 27 | }, 28 | "node_modules/@mapbox/node-pre-gyp": { 29 | "version": "1.0.11", 30 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", 31 | "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", 32 | "dependencies": { 33 | "detect-libc": "^2.0.0", 34 | "https-proxy-agent": "^5.0.0", 35 | "make-dir": "^3.1.0", 36 | "node-fetch": "^2.6.7", 37 | "nopt": "^5.0.0", 38 | "npmlog": "^5.0.1", 39 | "rimraf": "^3.0.2", 40 | "semver": "^7.3.5", 41 | "tar": "^6.1.11" 42 | }, 43 | "bin": { 44 | "node-pre-gyp": "bin/node-pre-gyp" 45 | } 46 | }, 47 | "node_modules/@mongodb-js/saslprep": { 48 | "version": "1.1.7", 49 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz", 50 | "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==", 51 | "dependencies": { 52 | "sparse-bitfield": "^3.0.3" 53 | } 54 | }, 55 | "node_modules/@types/webidl-conversions": { 56 | "version": "7.0.3", 57 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", 58 | "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" 59 | }, 60 | "node_modules/@types/whatwg-url": { 61 | "version": "11.0.5", 62 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", 63 | "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", 64 | "dependencies": { 65 | "@types/webidl-conversions": "*" 66 | } 67 | }, 68 | "node_modules/abbrev": { 69 | "version": "1.1.1", 70 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 71 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 72 | }, 73 | "node_modules/accepts": { 74 | "version": "1.3.8", 75 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 76 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 77 | "dependencies": { 78 | "mime-types": "~2.1.34", 79 | "negotiator": "0.6.3" 80 | }, 81 | "engines": { 82 | "node": ">= 0.6" 83 | } 84 | }, 85 | "node_modules/agent-base": { 86 | "version": "6.0.2", 87 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 88 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 89 | "dependencies": { 90 | "debug": "4" 91 | }, 92 | "engines": { 93 | "node": ">= 6.0.0" 94 | } 95 | }, 96 | "node_modules/ansi-regex": { 97 | "version": "5.0.1", 98 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 99 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 100 | "engines": { 101 | "node": ">=8" 102 | } 103 | }, 104 | "node_modules/anymatch": { 105 | "version": "3.1.3", 106 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 107 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 108 | "dev": true, 109 | "dependencies": { 110 | "normalize-path": "^3.0.0", 111 | "picomatch": "^2.0.4" 112 | }, 113 | "engines": { 114 | "node": ">= 8" 115 | } 116 | }, 117 | "node_modules/append-field": { 118 | "version": "1.0.0", 119 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", 120 | "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" 121 | }, 122 | "node_modules/aproba": { 123 | "version": "2.0.0", 124 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", 125 | "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" 126 | }, 127 | "node_modules/are-we-there-yet": { 128 | "version": "2.0.0", 129 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", 130 | "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", 131 | "deprecated": "This package is no longer supported.", 132 | "dependencies": { 133 | "delegates": "^1.0.0", 134 | "readable-stream": "^3.6.0" 135 | }, 136 | "engines": { 137 | "node": ">=10" 138 | } 139 | }, 140 | "node_modules/array-flatten": { 141 | "version": "1.1.1", 142 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 143 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 144 | }, 145 | "node_modules/balanced-match": { 146 | "version": "1.0.2", 147 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 148 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 149 | }, 150 | "node_modules/bcrypt": { 151 | "version": "5.1.1", 152 | "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", 153 | "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", 154 | "hasInstallScript": true, 155 | "dependencies": { 156 | "@mapbox/node-pre-gyp": "^1.0.11", 157 | "node-addon-api": "^5.0.0" 158 | }, 159 | "engines": { 160 | "node": ">= 10.0.0" 161 | } 162 | }, 163 | "node_modules/binary-extensions": { 164 | "version": "2.3.0", 165 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 166 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 167 | "dev": true, 168 | "engines": { 169 | "node": ">=8" 170 | }, 171 | "funding": { 172 | "url": "https://github.com/sponsors/sindresorhus" 173 | } 174 | }, 175 | "node_modules/body-parser": { 176 | "version": "1.20.2", 177 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", 178 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", 179 | "dependencies": { 180 | "bytes": "3.1.2", 181 | "content-type": "~1.0.5", 182 | "debug": "2.6.9", 183 | "depd": "2.0.0", 184 | "destroy": "1.2.0", 185 | "http-errors": "2.0.0", 186 | "iconv-lite": "0.4.24", 187 | "on-finished": "2.4.1", 188 | "qs": "6.11.0", 189 | "raw-body": "2.5.2", 190 | "type-is": "~1.6.18", 191 | "unpipe": "1.0.0" 192 | }, 193 | "engines": { 194 | "node": ">= 0.8", 195 | "npm": "1.2.8000 || >= 1.4.16" 196 | } 197 | }, 198 | "node_modules/body-parser/node_modules/debug": { 199 | "version": "2.6.9", 200 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 201 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 202 | "dependencies": { 203 | "ms": "2.0.0" 204 | } 205 | }, 206 | "node_modules/body-parser/node_modules/ms": { 207 | "version": "2.0.0", 208 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 209 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 210 | }, 211 | "node_modules/brace-expansion": { 212 | "version": "1.1.11", 213 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 214 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 215 | "dependencies": { 216 | "balanced-match": "^1.0.0", 217 | "concat-map": "0.0.1" 218 | } 219 | }, 220 | "node_modules/braces": { 221 | "version": "3.0.3", 222 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 223 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 224 | "dev": true, 225 | "dependencies": { 226 | "fill-range": "^7.1.1" 227 | }, 228 | "engines": { 229 | "node": ">=8" 230 | } 231 | }, 232 | "node_modules/bson": { 233 | "version": "6.7.0", 234 | "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz", 235 | "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==", 236 | "engines": { 237 | "node": ">=16.20.1" 238 | } 239 | }, 240 | "node_modules/buffer-equal-constant-time": { 241 | "version": "1.0.1", 242 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 243 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 244 | }, 245 | "node_modules/buffer-from": { 246 | "version": "1.1.2", 247 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 248 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 249 | }, 250 | "node_modules/busboy": { 251 | "version": "1.6.0", 252 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", 253 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", 254 | "dependencies": { 255 | "streamsearch": "^1.1.0" 256 | }, 257 | "engines": { 258 | "node": ">=10.16.0" 259 | } 260 | }, 261 | "node_modules/bytes": { 262 | "version": "3.1.2", 263 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 264 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 265 | "engines": { 266 | "node": ">= 0.8" 267 | } 268 | }, 269 | "node_modules/call-bind": { 270 | "version": "1.0.7", 271 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 272 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 273 | "dependencies": { 274 | "es-define-property": "^1.0.0", 275 | "es-errors": "^1.3.0", 276 | "function-bind": "^1.1.2", 277 | "get-intrinsic": "^1.2.4", 278 | "set-function-length": "^1.2.1" 279 | }, 280 | "engines": { 281 | "node": ">= 0.4" 282 | }, 283 | "funding": { 284 | "url": "https://github.com/sponsors/ljharb" 285 | } 286 | }, 287 | "node_modules/chokidar": { 288 | "version": "3.6.0", 289 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 290 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 291 | "dev": true, 292 | "dependencies": { 293 | "anymatch": "~3.1.2", 294 | "braces": "~3.0.2", 295 | "glob-parent": "~5.1.2", 296 | "is-binary-path": "~2.1.0", 297 | "is-glob": "~4.0.1", 298 | "normalize-path": "~3.0.0", 299 | "readdirp": "~3.6.0" 300 | }, 301 | "engines": { 302 | "node": ">= 8.10.0" 303 | }, 304 | "funding": { 305 | "url": "https://paulmillr.com/funding/" 306 | }, 307 | "optionalDependencies": { 308 | "fsevents": "~2.3.2" 309 | } 310 | }, 311 | "node_modules/chownr": { 312 | "version": "2.0.0", 313 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 314 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", 315 | "engines": { 316 | "node": ">=10" 317 | } 318 | }, 319 | "node_modules/cloudinary": { 320 | "version": "2.2.0", 321 | "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.2.0.tgz", 322 | "integrity": "sha512-akbLTZcNegGSkl07Frnt9fyiK9KZ2zPS+a+j7uLrjNYxVhDpDdIBz9G6snPCYqgk+WLVMRPfXTObalLr5L6g0Q==", 323 | "dependencies": { 324 | "lodash": "^4.17.21", 325 | "q": "^1.5.1" 326 | }, 327 | "engines": { 328 | "node": ">=9" 329 | } 330 | }, 331 | "node_modules/color-support": { 332 | "version": "1.1.3", 333 | "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", 334 | "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", 335 | "bin": { 336 | "color-support": "bin.js" 337 | } 338 | }, 339 | "node_modules/concat-map": { 340 | "version": "0.0.1", 341 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 342 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 343 | }, 344 | "node_modules/concat-stream": { 345 | "version": "1.6.2", 346 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 347 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 348 | "engines": [ 349 | "node >= 0.8" 350 | ], 351 | "dependencies": { 352 | "buffer-from": "^1.0.0", 353 | "inherits": "^2.0.3", 354 | "readable-stream": "^2.2.2", 355 | "typedarray": "^0.0.6" 356 | } 357 | }, 358 | "node_modules/concat-stream/node_modules/readable-stream": { 359 | "version": "2.3.8", 360 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 361 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 362 | "dependencies": { 363 | "core-util-is": "~1.0.0", 364 | "inherits": "~2.0.3", 365 | "isarray": "~1.0.0", 366 | "process-nextick-args": "~2.0.0", 367 | "safe-buffer": "~5.1.1", 368 | "string_decoder": "~1.1.1", 369 | "util-deprecate": "~1.0.1" 370 | } 371 | }, 372 | "node_modules/concat-stream/node_modules/safe-buffer": { 373 | "version": "5.1.2", 374 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 375 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 376 | }, 377 | "node_modules/concat-stream/node_modules/string_decoder": { 378 | "version": "1.1.1", 379 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 380 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 381 | "dependencies": { 382 | "safe-buffer": "~5.1.0" 383 | } 384 | }, 385 | "node_modules/console-control-strings": { 386 | "version": "1.1.0", 387 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 388 | "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" 389 | }, 390 | "node_modules/content-disposition": { 391 | "version": "0.5.4", 392 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 393 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 394 | "dependencies": { 395 | "safe-buffer": "5.2.1" 396 | }, 397 | "engines": { 398 | "node": ">= 0.6" 399 | } 400 | }, 401 | "node_modules/content-type": { 402 | "version": "1.0.5", 403 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 404 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 405 | "engines": { 406 | "node": ">= 0.6" 407 | } 408 | }, 409 | "node_modules/cookie": { 410 | "version": "0.6.0", 411 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 412 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 413 | "engines": { 414 | "node": ">= 0.6" 415 | } 416 | }, 417 | "node_modules/cookie-parser": { 418 | "version": "1.4.6", 419 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", 420 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", 421 | "dependencies": { 422 | "cookie": "0.4.1", 423 | "cookie-signature": "1.0.6" 424 | }, 425 | "engines": { 426 | "node": ">= 0.8.0" 427 | } 428 | }, 429 | "node_modules/cookie-parser/node_modules/cookie": { 430 | "version": "0.4.1", 431 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 432 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", 433 | "engines": { 434 | "node": ">= 0.6" 435 | } 436 | }, 437 | "node_modules/cookie-signature": { 438 | "version": "1.0.6", 439 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 440 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 441 | }, 442 | "node_modules/core-util-is": { 443 | "version": "1.0.3", 444 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 445 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 446 | }, 447 | "node_modules/cors": { 448 | "version": "2.8.5", 449 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 450 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 451 | "dependencies": { 452 | "object-assign": "^4", 453 | "vary": "^1" 454 | }, 455 | "engines": { 456 | "node": ">= 0.10" 457 | } 458 | }, 459 | "node_modules/debug": { 460 | "version": "4.3.5", 461 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 462 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 463 | "dependencies": { 464 | "ms": "2.1.2" 465 | }, 466 | "engines": { 467 | "node": ">=6.0" 468 | }, 469 | "peerDependenciesMeta": { 470 | "supports-color": { 471 | "optional": true 472 | } 473 | } 474 | }, 475 | "node_modules/define-data-property": { 476 | "version": "1.1.4", 477 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 478 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 479 | "dependencies": { 480 | "es-define-property": "^1.0.0", 481 | "es-errors": "^1.3.0", 482 | "gopd": "^1.0.1" 483 | }, 484 | "engines": { 485 | "node": ">= 0.4" 486 | }, 487 | "funding": { 488 | "url": "https://github.com/sponsors/ljharb" 489 | } 490 | }, 491 | "node_modules/delegates": { 492 | "version": "1.0.0", 493 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 494 | "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" 495 | }, 496 | "node_modules/depd": { 497 | "version": "2.0.0", 498 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 499 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 500 | "engines": { 501 | "node": ">= 0.8" 502 | } 503 | }, 504 | "node_modules/destroy": { 505 | "version": "1.2.0", 506 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 507 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 508 | "engines": { 509 | "node": ">= 0.8", 510 | "npm": "1.2.8000 || >= 1.4.16" 511 | } 512 | }, 513 | "node_modules/detect-libc": { 514 | "version": "2.0.3", 515 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", 516 | "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", 517 | "engines": { 518 | "node": ">=8" 519 | } 520 | }, 521 | "node_modules/dotenv": { 522 | "version": "16.4.5", 523 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", 524 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", 525 | "engines": { 526 | "node": ">=12" 527 | }, 528 | "funding": { 529 | "url": "https://dotenvx.com" 530 | } 531 | }, 532 | "node_modules/ecdsa-sig-formatter": { 533 | "version": "1.0.11", 534 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 535 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 536 | "dependencies": { 537 | "safe-buffer": "^5.0.1" 538 | } 539 | }, 540 | "node_modules/ee-first": { 541 | "version": "1.1.1", 542 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 543 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 544 | }, 545 | "node_modules/emoji-regex": { 546 | "version": "8.0.0", 547 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 548 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 549 | }, 550 | "node_modules/encodeurl": { 551 | "version": "1.0.2", 552 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 553 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 554 | "engines": { 555 | "node": ">= 0.8" 556 | } 557 | }, 558 | "node_modules/es-define-property": { 559 | "version": "1.0.0", 560 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 561 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 562 | "dependencies": { 563 | "get-intrinsic": "^1.2.4" 564 | }, 565 | "engines": { 566 | "node": ">= 0.4" 567 | } 568 | }, 569 | "node_modules/es-errors": { 570 | "version": "1.3.0", 571 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 572 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 573 | "engines": { 574 | "node": ">= 0.4" 575 | } 576 | }, 577 | "node_modules/escape-html": { 578 | "version": "1.0.3", 579 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 580 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 581 | }, 582 | "node_modules/etag": { 583 | "version": "1.8.1", 584 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 585 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 586 | "engines": { 587 | "node": ">= 0.6" 588 | } 589 | }, 590 | "node_modules/express": { 591 | "version": "4.19.2", 592 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", 593 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", 594 | "dependencies": { 595 | "accepts": "~1.3.8", 596 | "array-flatten": "1.1.1", 597 | "body-parser": "1.20.2", 598 | "content-disposition": "0.5.4", 599 | "content-type": "~1.0.4", 600 | "cookie": "0.6.0", 601 | "cookie-signature": "1.0.6", 602 | "debug": "2.6.9", 603 | "depd": "2.0.0", 604 | "encodeurl": "~1.0.2", 605 | "escape-html": "~1.0.3", 606 | "etag": "~1.8.1", 607 | "finalhandler": "1.2.0", 608 | "fresh": "0.5.2", 609 | "http-errors": "2.0.0", 610 | "merge-descriptors": "1.0.1", 611 | "methods": "~1.1.2", 612 | "on-finished": "2.4.1", 613 | "parseurl": "~1.3.3", 614 | "path-to-regexp": "0.1.7", 615 | "proxy-addr": "~2.0.7", 616 | "qs": "6.11.0", 617 | "range-parser": "~1.2.1", 618 | "safe-buffer": "5.2.1", 619 | "send": "0.18.0", 620 | "serve-static": "1.15.0", 621 | "setprototypeof": "1.2.0", 622 | "statuses": "2.0.1", 623 | "type-is": "~1.6.18", 624 | "utils-merge": "1.0.1", 625 | "vary": "~1.1.2" 626 | }, 627 | "engines": { 628 | "node": ">= 0.10.0" 629 | } 630 | }, 631 | "node_modules/express/node_modules/debug": { 632 | "version": "2.6.9", 633 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 634 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 635 | "dependencies": { 636 | "ms": "2.0.0" 637 | } 638 | }, 639 | "node_modules/express/node_modules/ms": { 640 | "version": "2.0.0", 641 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 642 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 643 | }, 644 | "node_modules/fill-range": { 645 | "version": "7.1.1", 646 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 647 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 648 | "dev": true, 649 | "dependencies": { 650 | "to-regex-range": "^5.0.1" 651 | }, 652 | "engines": { 653 | "node": ">=8" 654 | } 655 | }, 656 | "node_modules/finalhandler": { 657 | "version": "1.2.0", 658 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 659 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 660 | "dependencies": { 661 | "debug": "2.6.9", 662 | "encodeurl": "~1.0.2", 663 | "escape-html": "~1.0.3", 664 | "on-finished": "2.4.1", 665 | "parseurl": "~1.3.3", 666 | "statuses": "2.0.1", 667 | "unpipe": "~1.0.0" 668 | }, 669 | "engines": { 670 | "node": ">= 0.8" 671 | } 672 | }, 673 | "node_modules/finalhandler/node_modules/debug": { 674 | "version": "2.6.9", 675 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 676 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 677 | "dependencies": { 678 | "ms": "2.0.0" 679 | } 680 | }, 681 | "node_modules/finalhandler/node_modules/ms": { 682 | "version": "2.0.0", 683 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 684 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 685 | }, 686 | "node_modules/forwarded": { 687 | "version": "0.2.0", 688 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 689 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 690 | "engines": { 691 | "node": ">= 0.6" 692 | } 693 | }, 694 | "node_modules/fresh": { 695 | "version": "0.5.2", 696 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 697 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 698 | "engines": { 699 | "node": ">= 0.6" 700 | } 701 | }, 702 | "node_modules/fs-minipass": { 703 | "version": "2.1.0", 704 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 705 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 706 | "dependencies": { 707 | "minipass": "^3.0.0" 708 | }, 709 | "engines": { 710 | "node": ">= 8" 711 | } 712 | }, 713 | "node_modules/fs-minipass/node_modules/minipass": { 714 | "version": "3.3.6", 715 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 716 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 717 | "dependencies": { 718 | "yallist": "^4.0.0" 719 | }, 720 | "engines": { 721 | "node": ">=8" 722 | } 723 | }, 724 | "node_modules/fs.realpath": { 725 | "version": "1.0.0", 726 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 727 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 728 | }, 729 | "node_modules/fsevents": { 730 | "version": "2.3.3", 731 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 732 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 733 | "dev": true, 734 | "hasInstallScript": true, 735 | "optional": true, 736 | "os": [ 737 | "darwin" 738 | ], 739 | "engines": { 740 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 741 | } 742 | }, 743 | "node_modules/function-bind": { 744 | "version": "1.1.2", 745 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 746 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 747 | "funding": { 748 | "url": "https://github.com/sponsors/ljharb" 749 | } 750 | }, 751 | "node_modules/gauge": { 752 | "version": "3.0.2", 753 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", 754 | "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", 755 | "deprecated": "This package is no longer supported.", 756 | "dependencies": { 757 | "aproba": "^1.0.3 || ^2.0.0", 758 | "color-support": "^1.1.2", 759 | "console-control-strings": "^1.0.0", 760 | "has-unicode": "^2.0.1", 761 | "object-assign": "^4.1.1", 762 | "signal-exit": "^3.0.0", 763 | "string-width": "^4.2.3", 764 | "strip-ansi": "^6.0.1", 765 | "wide-align": "^1.1.2" 766 | }, 767 | "engines": { 768 | "node": ">=10" 769 | } 770 | }, 771 | "node_modules/get-intrinsic": { 772 | "version": "1.2.4", 773 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 774 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 775 | "dependencies": { 776 | "es-errors": "^1.3.0", 777 | "function-bind": "^1.1.2", 778 | "has-proto": "^1.0.1", 779 | "has-symbols": "^1.0.3", 780 | "hasown": "^2.0.0" 781 | }, 782 | "engines": { 783 | "node": ">= 0.4" 784 | }, 785 | "funding": { 786 | "url": "https://github.com/sponsors/ljharb" 787 | } 788 | }, 789 | "node_modules/glob": { 790 | "version": "7.2.3", 791 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 792 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 793 | "deprecated": "Glob versions prior to v9 are no longer supported", 794 | "dependencies": { 795 | "fs.realpath": "^1.0.0", 796 | "inflight": "^1.0.4", 797 | "inherits": "2", 798 | "minimatch": "^3.1.1", 799 | "once": "^1.3.0", 800 | "path-is-absolute": "^1.0.0" 801 | }, 802 | "engines": { 803 | "node": "*" 804 | }, 805 | "funding": { 806 | "url": "https://github.com/sponsors/isaacs" 807 | } 808 | }, 809 | "node_modules/glob-parent": { 810 | "version": "5.1.2", 811 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 812 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 813 | "dev": true, 814 | "dependencies": { 815 | "is-glob": "^4.0.1" 816 | }, 817 | "engines": { 818 | "node": ">= 6" 819 | } 820 | }, 821 | "node_modules/gopd": { 822 | "version": "1.0.1", 823 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 824 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 825 | "dependencies": { 826 | "get-intrinsic": "^1.1.3" 827 | }, 828 | "funding": { 829 | "url": "https://github.com/sponsors/ljharb" 830 | } 831 | }, 832 | "node_modules/has-flag": { 833 | "version": "3.0.0", 834 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 835 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 836 | "dev": true, 837 | "engines": { 838 | "node": ">=4" 839 | } 840 | }, 841 | "node_modules/has-property-descriptors": { 842 | "version": "1.0.2", 843 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 844 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 845 | "dependencies": { 846 | "es-define-property": "^1.0.0" 847 | }, 848 | "funding": { 849 | "url": "https://github.com/sponsors/ljharb" 850 | } 851 | }, 852 | "node_modules/has-proto": { 853 | "version": "1.0.3", 854 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 855 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 856 | "engines": { 857 | "node": ">= 0.4" 858 | }, 859 | "funding": { 860 | "url": "https://github.com/sponsors/ljharb" 861 | } 862 | }, 863 | "node_modules/has-symbols": { 864 | "version": "1.0.3", 865 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 866 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 867 | "engines": { 868 | "node": ">= 0.4" 869 | }, 870 | "funding": { 871 | "url": "https://github.com/sponsors/ljharb" 872 | } 873 | }, 874 | "node_modules/has-unicode": { 875 | "version": "2.0.1", 876 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 877 | "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" 878 | }, 879 | "node_modules/hasown": { 880 | "version": "2.0.2", 881 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 882 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 883 | "dependencies": { 884 | "function-bind": "^1.1.2" 885 | }, 886 | "engines": { 887 | "node": ">= 0.4" 888 | } 889 | }, 890 | "node_modules/http-errors": { 891 | "version": "2.0.0", 892 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 893 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 894 | "dependencies": { 895 | "depd": "2.0.0", 896 | "inherits": "2.0.4", 897 | "setprototypeof": "1.2.0", 898 | "statuses": "2.0.1", 899 | "toidentifier": "1.0.1" 900 | }, 901 | "engines": { 902 | "node": ">= 0.8" 903 | } 904 | }, 905 | "node_modules/https-proxy-agent": { 906 | "version": "5.0.1", 907 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 908 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 909 | "dependencies": { 910 | "agent-base": "6", 911 | "debug": "4" 912 | }, 913 | "engines": { 914 | "node": ">= 6" 915 | } 916 | }, 917 | "node_modules/iconv-lite": { 918 | "version": "0.4.24", 919 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 920 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 921 | "dependencies": { 922 | "safer-buffer": ">= 2.1.2 < 3" 923 | }, 924 | "engines": { 925 | "node": ">=0.10.0" 926 | } 927 | }, 928 | "node_modules/ignore-by-default": { 929 | "version": "1.0.1", 930 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 931 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 932 | "dev": true 933 | }, 934 | "node_modules/inflight": { 935 | "version": "1.0.6", 936 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 937 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 938 | "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.", 939 | "dependencies": { 940 | "once": "^1.3.0", 941 | "wrappy": "1" 942 | } 943 | }, 944 | "node_modules/inherits": { 945 | "version": "2.0.4", 946 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 947 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 948 | }, 949 | "node_modules/ipaddr.js": { 950 | "version": "1.9.1", 951 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 952 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 953 | "engines": { 954 | "node": ">= 0.10" 955 | } 956 | }, 957 | "node_modules/is-binary-path": { 958 | "version": "2.1.0", 959 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 960 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 961 | "dev": true, 962 | "dependencies": { 963 | "binary-extensions": "^2.0.0" 964 | }, 965 | "engines": { 966 | "node": ">=8" 967 | } 968 | }, 969 | "node_modules/is-extglob": { 970 | "version": "2.1.1", 971 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 972 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 973 | "dev": true, 974 | "engines": { 975 | "node": ">=0.10.0" 976 | } 977 | }, 978 | "node_modules/is-fullwidth-code-point": { 979 | "version": "3.0.0", 980 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 981 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 982 | "engines": { 983 | "node": ">=8" 984 | } 985 | }, 986 | "node_modules/is-glob": { 987 | "version": "4.0.3", 988 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 989 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 990 | "dev": true, 991 | "dependencies": { 992 | "is-extglob": "^2.1.1" 993 | }, 994 | "engines": { 995 | "node": ">=0.10.0" 996 | } 997 | }, 998 | "node_modules/is-number": { 999 | "version": "7.0.0", 1000 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1001 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1002 | "dev": true, 1003 | "engines": { 1004 | "node": ">=0.12.0" 1005 | } 1006 | }, 1007 | "node_modules/isarray": { 1008 | "version": "1.0.0", 1009 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1010 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 1011 | }, 1012 | "node_modules/jsonwebtoken": { 1013 | "version": "9.0.2", 1014 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", 1015 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", 1016 | "dependencies": { 1017 | "jws": "^3.2.2", 1018 | "lodash.includes": "^4.3.0", 1019 | "lodash.isboolean": "^3.0.3", 1020 | "lodash.isinteger": "^4.0.4", 1021 | "lodash.isnumber": "^3.0.3", 1022 | "lodash.isplainobject": "^4.0.6", 1023 | "lodash.isstring": "^4.0.1", 1024 | "lodash.once": "^4.0.0", 1025 | "ms": "^2.1.1", 1026 | "semver": "^7.5.4" 1027 | }, 1028 | "engines": { 1029 | "node": ">=12", 1030 | "npm": ">=6" 1031 | } 1032 | }, 1033 | "node_modules/jwa": { 1034 | "version": "1.4.1", 1035 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 1036 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 1037 | "dependencies": { 1038 | "buffer-equal-constant-time": "1.0.1", 1039 | "ecdsa-sig-formatter": "1.0.11", 1040 | "safe-buffer": "^5.0.1" 1041 | } 1042 | }, 1043 | "node_modules/jws": { 1044 | "version": "3.2.2", 1045 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 1046 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 1047 | "dependencies": { 1048 | "jwa": "^1.4.1", 1049 | "safe-buffer": "^5.0.1" 1050 | } 1051 | }, 1052 | "node_modules/kareem": { 1053 | "version": "2.6.3", 1054 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", 1055 | "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", 1056 | "engines": { 1057 | "node": ">=12.0.0" 1058 | } 1059 | }, 1060 | "node_modules/lodash": { 1061 | "version": "4.17.21", 1062 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1063 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 1064 | }, 1065 | "node_modules/lodash.includes": { 1066 | "version": "4.3.0", 1067 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 1068 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" 1069 | }, 1070 | "node_modules/lodash.isboolean": { 1071 | "version": "3.0.3", 1072 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 1073 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" 1074 | }, 1075 | "node_modules/lodash.isinteger": { 1076 | "version": "4.0.4", 1077 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 1078 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" 1079 | }, 1080 | "node_modules/lodash.isnumber": { 1081 | "version": "3.0.3", 1082 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 1083 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" 1084 | }, 1085 | "node_modules/lodash.isplainobject": { 1086 | "version": "4.0.6", 1087 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 1088 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" 1089 | }, 1090 | "node_modules/lodash.isstring": { 1091 | "version": "4.0.1", 1092 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 1093 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" 1094 | }, 1095 | "node_modules/lodash.once": { 1096 | "version": "4.1.1", 1097 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 1098 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" 1099 | }, 1100 | "node_modules/make-dir": { 1101 | "version": "3.1.0", 1102 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 1103 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 1104 | "dependencies": { 1105 | "semver": "^6.0.0" 1106 | }, 1107 | "engines": { 1108 | "node": ">=8" 1109 | }, 1110 | "funding": { 1111 | "url": "https://github.com/sponsors/sindresorhus" 1112 | } 1113 | }, 1114 | "node_modules/make-dir/node_modules/semver": { 1115 | "version": "6.3.1", 1116 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 1117 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 1118 | "bin": { 1119 | "semver": "bin/semver.js" 1120 | } 1121 | }, 1122 | "node_modules/media-typer": { 1123 | "version": "0.3.0", 1124 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1125 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1126 | "engines": { 1127 | "node": ">= 0.6" 1128 | } 1129 | }, 1130 | "node_modules/memory-pager": { 1131 | "version": "1.5.0", 1132 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 1133 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" 1134 | }, 1135 | "node_modules/merge-descriptors": { 1136 | "version": "1.0.1", 1137 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1138 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 1139 | }, 1140 | "node_modules/methods": { 1141 | "version": "1.1.2", 1142 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1143 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1144 | "engines": { 1145 | "node": ">= 0.6" 1146 | } 1147 | }, 1148 | "node_modules/mime": { 1149 | "version": "1.6.0", 1150 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1151 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1152 | "bin": { 1153 | "mime": "cli.js" 1154 | }, 1155 | "engines": { 1156 | "node": ">=4" 1157 | } 1158 | }, 1159 | "node_modules/mime-db": { 1160 | "version": "1.52.0", 1161 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1162 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1163 | "engines": { 1164 | "node": ">= 0.6" 1165 | } 1166 | }, 1167 | "node_modules/mime-types": { 1168 | "version": "2.1.35", 1169 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1170 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1171 | "dependencies": { 1172 | "mime-db": "1.52.0" 1173 | }, 1174 | "engines": { 1175 | "node": ">= 0.6" 1176 | } 1177 | }, 1178 | "node_modules/minimatch": { 1179 | "version": "3.1.2", 1180 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1181 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1182 | "dependencies": { 1183 | "brace-expansion": "^1.1.7" 1184 | }, 1185 | "engines": { 1186 | "node": "*" 1187 | } 1188 | }, 1189 | "node_modules/minimist": { 1190 | "version": "1.2.8", 1191 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1192 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1193 | "funding": { 1194 | "url": "https://github.com/sponsors/ljharb" 1195 | } 1196 | }, 1197 | "node_modules/minipass": { 1198 | "version": "5.0.0", 1199 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", 1200 | "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", 1201 | "engines": { 1202 | "node": ">=8" 1203 | } 1204 | }, 1205 | "node_modules/minizlib": { 1206 | "version": "2.1.2", 1207 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 1208 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 1209 | "dependencies": { 1210 | "minipass": "^3.0.0", 1211 | "yallist": "^4.0.0" 1212 | }, 1213 | "engines": { 1214 | "node": ">= 8" 1215 | } 1216 | }, 1217 | "node_modules/minizlib/node_modules/minipass": { 1218 | "version": "3.3.6", 1219 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 1220 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 1221 | "dependencies": { 1222 | "yallist": "^4.0.0" 1223 | }, 1224 | "engines": { 1225 | "node": ">=8" 1226 | } 1227 | }, 1228 | "node_modules/mkdirp": { 1229 | "version": "1.0.4", 1230 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 1231 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 1232 | "bin": { 1233 | "mkdirp": "bin/cmd.js" 1234 | }, 1235 | "engines": { 1236 | "node": ">=10" 1237 | } 1238 | }, 1239 | "node_modules/mongodb": { 1240 | "version": "6.6.2", 1241 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz", 1242 | "integrity": "sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw==", 1243 | "dependencies": { 1244 | "@mongodb-js/saslprep": "^1.1.5", 1245 | "bson": "^6.7.0", 1246 | "mongodb-connection-string-url": "^3.0.0" 1247 | }, 1248 | "engines": { 1249 | "node": ">=16.20.1" 1250 | }, 1251 | "peerDependencies": { 1252 | "@aws-sdk/credential-providers": "^3.188.0", 1253 | "@mongodb-js/zstd": "^1.1.0", 1254 | "gcp-metadata": "^5.2.0", 1255 | "kerberos": "^2.0.1", 1256 | "mongodb-client-encryption": ">=6.0.0 <7", 1257 | "snappy": "^7.2.2", 1258 | "socks": "^2.7.1" 1259 | }, 1260 | "peerDependenciesMeta": { 1261 | "@aws-sdk/credential-providers": { 1262 | "optional": true 1263 | }, 1264 | "@mongodb-js/zstd": { 1265 | "optional": true 1266 | }, 1267 | "gcp-metadata": { 1268 | "optional": true 1269 | }, 1270 | "kerberos": { 1271 | "optional": true 1272 | }, 1273 | "mongodb-client-encryption": { 1274 | "optional": true 1275 | }, 1276 | "snappy": { 1277 | "optional": true 1278 | }, 1279 | "socks": { 1280 | "optional": true 1281 | } 1282 | } 1283 | }, 1284 | "node_modules/mongodb-connection-string-url": { 1285 | "version": "3.0.1", 1286 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", 1287 | "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", 1288 | "dependencies": { 1289 | "@types/whatwg-url": "^11.0.2", 1290 | "whatwg-url": "^13.0.0" 1291 | } 1292 | }, 1293 | "node_modules/mongoose": { 1294 | "version": "8.4.3", 1295 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.4.3.tgz", 1296 | "integrity": "sha512-GxPVLD+I/dxVkgcts2r2QmJJvS62/++btVj3RFt8YnHt+DSOp1Qjj62YEvgZaElwIOTcc4KGJM95X5LlrU1qQg==", 1297 | "dependencies": { 1298 | "bson": "^6.7.0", 1299 | "kareem": "2.6.3", 1300 | "mongodb": "6.6.2", 1301 | "mpath": "0.9.0", 1302 | "mquery": "5.0.0", 1303 | "ms": "2.1.3", 1304 | "sift": "17.1.3" 1305 | }, 1306 | "engines": { 1307 | "node": ">=16.20.1" 1308 | }, 1309 | "funding": { 1310 | "type": "opencollective", 1311 | "url": "https://opencollective.com/mongoose" 1312 | } 1313 | }, 1314 | "node_modules/mongoose-aggregate-paginate-v2": { 1315 | "version": "1.0.7", 1316 | "resolved": "https://registry.npmjs.org/mongoose-aggregate-paginate-v2/-/mongoose-aggregate-paginate-v2-1.0.7.tgz", 1317 | "integrity": "sha512-Xij2wux3xs9yfXhV9d7M1P92WJsz39fyNJWyVAcSDhVO4HZnoM2zz03b2ZLCT13ha5TJL9uPM+jw1Sj2IqjHAQ==", 1318 | "engines": { 1319 | "node": ">=4.0.0" 1320 | } 1321 | }, 1322 | "node_modules/mongoose/node_modules/ms": { 1323 | "version": "2.1.3", 1324 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1325 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1326 | }, 1327 | "node_modules/mpath": { 1328 | "version": "0.9.0", 1329 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", 1330 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", 1331 | "engines": { 1332 | "node": ">=4.0.0" 1333 | } 1334 | }, 1335 | "node_modules/mquery": { 1336 | "version": "5.0.0", 1337 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", 1338 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", 1339 | "dependencies": { 1340 | "debug": "4.x" 1341 | }, 1342 | "engines": { 1343 | "node": ">=14.0.0" 1344 | } 1345 | }, 1346 | "node_modules/ms": { 1347 | "version": "2.1.2", 1348 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1349 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1350 | }, 1351 | "node_modules/multer": { 1352 | "version": "1.4.5-lts.1", 1353 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", 1354 | "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", 1355 | "dependencies": { 1356 | "append-field": "^1.0.0", 1357 | "busboy": "^1.0.0", 1358 | "concat-stream": "^1.5.2", 1359 | "mkdirp": "^0.5.4", 1360 | "object-assign": "^4.1.1", 1361 | "type-is": "^1.6.4", 1362 | "xtend": "^4.0.0" 1363 | }, 1364 | "engines": { 1365 | "node": ">= 6.0.0" 1366 | } 1367 | }, 1368 | "node_modules/multer/node_modules/mkdirp": { 1369 | "version": "0.5.6", 1370 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 1371 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 1372 | "dependencies": { 1373 | "minimist": "^1.2.6" 1374 | }, 1375 | "bin": { 1376 | "mkdirp": "bin/cmd.js" 1377 | } 1378 | }, 1379 | "node_modules/negotiator": { 1380 | "version": "0.6.3", 1381 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1382 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1383 | "engines": { 1384 | "node": ">= 0.6" 1385 | } 1386 | }, 1387 | "node_modules/node-addon-api": { 1388 | "version": "5.1.0", 1389 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", 1390 | "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" 1391 | }, 1392 | "node_modules/node-fetch": { 1393 | "version": "2.7.0", 1394 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 1395 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 1396 | "dependencies": { 1397 | "whatwg-url": "^5.0.0" 1398 | }, 1399 | "engines": { 1400 | "node": "4.x || >=6.0.0" 1401 | }, 1402 | "peerDependencies": { 1403 | "encoding": "^0.1.0" 1404 | }, 1405 | "peerDependenciesMeta": { 1406 | "encoding": { 1407 | "optional": true 1408 | } 1409 | } 1410 | }, 1411 | "node_modules/node-fetch/node_modules/tr46": { 1412 | "version": "0.0.3", 1413 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1414 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 1415 | }, 1416 | "node_modules/node-fetch/node_modules/webidl-conversions": { 1417 | "version": "3.0.1", 1418 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1419 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 1420 | }, 1421 | "node_modules/node-fetch/node_modules/whatwg-url": { 1422 | "version": "5.0.0", 1423 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1424 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 1425 | "dependencies": { 1426 | "tr46": "~0.0.3", 1427 | "webidl-conversions": "^3.0.0" 1428 | } 1429 | }, 1430 | "node_modules/nodemon": { 1431 | "version": "3.1.3", 1432 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz", 1433 | "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==", 1434 | "dev": true, 1435 | "dependencies": { 1436 | "chokidar": "^3.5.2", 1437 | "debug": "^4", 1438 | "ignore-by-default": "^1.0.1", 1439 | "minimatch": "^3.1.2", 1440 | "pstree.remy": "^1.1.8", 1441 | "semver": "^7.5.3", 1442 | "simple-update-notifier": "^2.0.0", 1443 | "supports-color": "^5.5.0", 1444 | "touch": "^3.1.0", 1445 | "undefsafe": "^2.0.5" 1446 | }, 1447 | "bin": { 1448 | "nodemon": "bin/nodemon.js" 1449 | }, 1450 | "engines": { 1451 | "node": ">=10" 1452 | }, 1453 | "funding": { 1454 | "type": "opencollective", 1455 | "url": "https://opencollective.com/nodemon" 1456 | } 1457 | }, 1458 | "node_modules/nopt": { 1459 | "version": "5.0.0", 1460 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 1461 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 1462 | "dependencies": { 1463 | "abbrev": "1" 1464 | }, 1465 | "bin": { 1466 | "nopt": "bin/nopt.js" 1467 | }, 1468 | "engines": { 1469 | "node": ">=6" 1470 | } 1471 | }, 1472 | "node_modules/normalize-path": { 1473 | "version": "3.0.0", 1474 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1475 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1476 | "dev": true, 1477 | "engines": { 1478 | "node": ">=0.10.0" 1479 | } 1480 | }, 1481 | "node_modules/npmlog": { 1482 | "version": "5.0.1", 1483 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", 1484 | "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", 1485 | "deprecated": "This package is no longer supported.", 1486 | "dependencies": { 1487 | "are-we-there-yet": "^2.0.0", 1488 | "console-control-strings": "^1.1.0", 1489 | "gauge": "^3.0.0", 1490 | "set-blocking": "^2.0.0" 1491 | } 1492 | }, 1493 | "node_modules/object-assign": { 1494 | "version": "4.1.1", 1495 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1496 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1497 | "engines": { 1498 | "node": ">=0.10.0" 1499 | } 1500 | }, 1501 | "node_modules/object-inspect": { 1502 | "version": "1.13.1", 1503 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 1504 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 1505 | "funding": { 1506 | "url": "https://github.com/sponsors/ljharb" 1507 | } 1508 | }, 1509 | "node_modules/on-finished": { 1510 | "version": "2.4.1", 1511 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1512 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1513 | "dependencies": { 1514 | "ee-first": "1.1.1" 1515 | }, 1516 | "engines": { 1517 | "node": ">= 0.8" 1518 | } 1519 | }, 1520 | "node_modules/once": { 1521 | "version": "1.4.0", 1522 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1523 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1524 | "dependencies": { 1525 | "wrappy": "1" 1526 | } 1527 | }, 1528 | "node_modules/parseurl": { 1529 | "version": "1.3.3", 1530 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1531 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1532 | "engines": { 1533 | "node": ">= 0.8" 1534 | } 1535 | }, 1536 | "node_modules/path-is-absolute": { 1537 | "version": "1.0.1", 1538 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1539 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1540 | "engines": { 1541 | "node": ">=0.10.0" 1542 | } 1543 | }, 1544 | "node_modules/path-to-regexp": { 1545 | "version": "0.1.7", 1546 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1547 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 1548 | }, 1549 | "node_modules/picomatch": { 1550 | "version": "2.3.1", 1551 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1552 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1553 | "dev": true, 1554 | "engines": { 1555 | "node": ">=8.6" 1556 | }, 1557 | "funding": { 1558 | "url": "https://github.com/sponsors/jonschlinkert" 1559 | } 1560 | }, 1561 | "node_modules/prettier": { 1562 | "version": "3.3.2", 1563 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", 1564 | "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", 1565 | "dev": true, 1566 | "bin": { 1567 | "prettier": "bin/prettier.cjs" 1568 | }, 1569 | "engines": { 1570 | "node": ">=14" 1571 | }, 1572 | "funding": { 1573 | "url": "https://github.com/prettier/prettier?sponsor=1" 1574 | } 1575 | }, 1576 | "node_modules/process-nextick-args": { 1577 | "version": "2.0.1", 1578 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1579 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1580 | }, 1581 | "node_modules/proxy-addr": { 1582 | "version": "2.0.7", 1583 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1584 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1585 | "dependencies": { 1586 | "forwarded": "0.2.0", 1587 | "ipaddr.js": "1.9.1" 1588 | }, 1589 | "engines": { 1590 | "node": ">= 0.10" 1591 | } 1592 | }, 1593 | "node_modules/pstree.remy": { 1594 | "version": "1.1.8", 1595 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 1596 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 1597 | "dev": true 1598 | }, 1599 | "node_modules/punycode": { 1600 | "version": "2.3.1", 1601 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1602 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1603 | "engines": { 1604 | "node": ">=6" 1605 | } 1606 | }, 1607 | "node_modules/q": { 1608 | "version": "1.5.1", 1609 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 1610 | "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", 1611 | "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", 1612 | "engines": { 1613 | "node": ">=0.6.0", 1614 | "teleport": ">=0.2.0" 1615 | } 1616 | }, 1617 | "node_modules/qs": { 1618 | "version": "6.11.0", 1619 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 1620 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 1621 | "dependencies": { 1622 | "side-channel": "^1.0.4" 1623 | }, 1624 | "engines": { 1625 | "node": ">=0.6" 1626 | }, 1627 | "funding": { 1628 | "url": "https://github.com/sponsors/ljharb" 1629 | } 1630 | }, 1631 | "node_modules/range-parser": { 1632 | "version": "1.2.1", 1633 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1634 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1635 | "engines": { 1636 | "node": ">= 0.6" 1637 | } 1638 | }, 1639 | "node_modules/raw-body": { 1640 | "version": "2.5.2", 1641 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1642 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1643 | "dependencies": { 1644 | "bytes": "3.1.2", 1645 | "http-errors": "2.0.0", 1646 | "iconv-lite": "0.4.24", 1647 | "unpipe": "1.0.0" 1648 | }, 1649 | "engines": { 1650 | "node": ">= 0.8" 1651 | } 1652 | }, 1653 | "node_modules/readable-stream": { 1654 | "version": "3.6.2", 1655 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 1656 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 1657 | "dependencies": { 1658 | "inherits": "^2.0.3", 1659 | "string_decoder": "^1.1.1", 1660 | "util-deprecate": "^1.0.1" 1661 | }, 1662 | "engines": { 1663 | "node": ">= 6" 1664 | } 1665 | }, 1666 | "node_modules/readdirp": { 1667 | "version": "3.6.0", 1668 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1669 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1670 | "dev": true, 1671 | "dependencies": { 1672 | "picomatch": "^2.2.1" 1673 | }, 1674 | "engines": { 1675 | "node": ">=8.10.0" 1676 | } 1677 | }, 1678 | "node_modules/rimraf": { 1679 | "version": "3.0.2", 1680 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1681 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1682 | "deprecated": "Rimraf versions prior to v4 are no longer supported", 1683 | "dependencies": { 1684 | "glob": "^7.1.3" 1685 | }, 1686 | "bin": { 1687 | "rimraf": "bin.js" 1688 | }, 1689 | "funding": { 1690 | "url": "https://github.com/sponsors/isaacs" 1691 | } 1692 | }, 1693 | "node_modules/safe-buffer": { 1694 | "version": "5.2.1", 1695 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1696 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1697 | "funding": [ 1698 | { 1699 | "type": "github", 1700 | "url": "https://github.com/sponsors/feross" 1701 | }, 1702 | { 1703 | "type": "patreon", 1704 | "url": "https://www.patreon.com/feross" 1705 | }, 1706 | { 1707 | "type": "consulting", 1708 | "url": "https://feross.org/support" 1709 | } 1710 | ] 1711 | }, 1712 | "node_modules/safer-buffer": { 1713 | "version": "2.1.2", 1714 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1715 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1716 | }, 1717 | "node_modules/semver": { 1718 | "version": "7.6.2", 1719 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", 1720 | "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", 1721 | "bin": { 1722 | "semver": "bin/semver.js" 1723 | }, 1724 | "engines": { 1725 | "node": ">=10" 1726 | } 1727 | }, 1728 | "node_modules/send": { 1729 | "version": "0.18.0", 1730 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 1731 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 1732 | "dependencies": { 1733 | "debug": "2.6.9", 1734 | "depd": "2.0.0", 1735 | "destroy": "1.2.0", 1736 | "encodeurl": "~1.0.2", 1737 | "escape-html": "~1.0.3", 1738 | "etag": "~1.8.1", 1739 | "fresh": "0.5.2", 1740 | "http-errors": "2.0.0", 1741 | "mime": "1.6.0", 1742 | "ms": "2.1.3", 1743 | "on-finished": "2.4.1", 1744 | "range-parser": "~1.2.1", 1745 | "statuses": "2.0.1" 1746 | }, 1747 | "engines": { 1748 | "node": ">= 0.8.0" 1749 | } 1750 | }, 1751 | "node_modules/send/node_modules/debug": { 1752 | "version": "2.6.9", 1753 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1754 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1755 | "dependencies": { 1756 | "ms": "2.0.0" 1757 | } 1758 | }, 1759 | "node_modules/send/node_modules/debug/node_modules/ms": { 1760 | "version": "2.0.0", 1761 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1762 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1763 | }, 1764 | "node_modules/send/node_modules/ms": { 1765 | "version": "2.1.3", 1766 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1767 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1768 | }, 1769 | "node_modules/serve-static": { 1770 | "version": "1.15.0", 1771 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 1772 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 1773 | "dependencies": { 1774 | "encodeurl": "~1.0.2", 1775 | "escape-html": "~1.0.3", 1776 | "parseurl": "~1.3.3", 1777 | "send": "0.18.0" 1778 | }, 1779 | "engines": { 1780 | "node": ">= 0.8.0" 1781 | } 1782 | }, 1783 | "node_modules/set-blocking": { 1784 | "version": "2.0.0", 1785 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1786 | "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" 1787 | }, 1788 | "node_modules/set-function-length": { 1789 | "version": "1.2.2", 1790 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 1791 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 1792 | "dependencies": { 1793 | "define-data-property": "^1.1.4", 1794 | "es-errors": "^1.3.0", 1795 | "function-bind": "^1.1.2", 1796 | "get-intrinsic": "^1.2.4", 1797 | "gopd": "^1.0.1", 1798 | "has-property-descriptors": "^1.0.2" 1799 | }, 1800 | "engines": { 1801 | "node": ">= 0.4" 1802 | } 1803 | }, 1804 | "node_modules/setprototypeof": { 1805 | "version": "1.2.0", 1806 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1807 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1808 | }, 1809 | "node_modules/side-channel": { 1810 | "version": "1.0.6", 1811 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 1812 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 1813 | "dependencies": { 1814 | "call-bind": "^1.0.7", 1815 | "es-errors": "^1.3.0", 1816 | "get-intrinsic": "^1.2.4", 1817 | "object-inspect": "^1.13.1" 1818 | }, 1819 | "engines": { 1820 | "node": ">= 0.4" 1821 | }, 1822 | "funding": { 1823 | "url": "https://github.com/sponsors/ljharb" 1824 | } 1825 | }, 1826 | "node_modules/sift": { 1827 | "version": "17.1.3", 1828 | "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", 1829 | "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" 1830 | }, 1831 | "node_modules/signal-exit": { 1832 | "version": "3.0.7", 1833 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 1834 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" 1835 | }, 1836 | "node_modules/simple-update-notifier": { 1837 | "version": "2.0.0", 1838 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1839 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1840 | "dev": true, 1841 | "dependencies": { 1842 | "semver": "^7.5.3" 1843 | }, 1844 | "engines": { 1845 | "node": ">=10" 1846 | } 1847 | }, 1848 | "node_modules/sparse-bitfield": { 1849 | "version": "3.0.3", 1850 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 1851 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", 1852 | "dependencies": { 1853 | "memory-pager": "^1.0.2" 1854 | } 1855 | }, 1856 | "node_modules/statuses": { 1857 | "version": "2.0.1", 1858 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1859 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1860 | "engines": { 1861 | "node": ">= 0.8" 1862 | } 1863 | }, 1864 | "node_modules/streamsearch": { 1865 | "version": "1.1.0", 1866 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", 1867 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", 1868 | "engines": { 1869 | "node": ">=10.0.0" 1870 | } 1871 | }, 1872 | "node_modules/string_decoder": { 1873 | "version": "1.3.0", 1874 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1875 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1876 | "dependencies": { 1877 | "safe-buffer": "~5.2.0" 1878 | } 1879 | }, 1880 | "node_modules/string-width": { 1881 | "version": "4.2.3", 1882 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1883 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1884 | "dependencies": { 1885 | "emoji-regex": "^8.0.0", 1886 | "is-fullwidth-code-point": "^3.0.0", 1887 | "strip-ansi": "^6.0.1" 1888 | }, 1889 | "engines": { 1890 | "node": ">=8" 1891 | } 1892 | }, 1893 | "node_modules/strip-ansi": { 1894 | "version": "6.0.1", 1895 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1896 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1897 | "dependencies": { 1898 | "ansi-regex": "^5.0.1" 1899 | }, 1900 | "engines": { 1901 | "node": ">=8" 1902 | } 1903 | }, 1904 | "node_modules/supports-color": { 1905 | "version": "5.5.0", 1906 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1907 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1908 | "dev": true, 1909 | "dependencies": { 1910 | "has-flag": "^3.0.0" 1911 | }, 1912 | "engines": { 1913 | "node": ">=4" 1914 | } 1915 | }, 1916 | "node_modules/tar": { 1917 | "version": "6.2.1", 1918 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", 1919 | "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", 1920 | "dependencies": { 1921 | "chownr": "^2.0.0", 1922 | "fs-minipass": "^2.0.0", 1923 | "minipass": "^5.0.0", 1924 | "minizlib": "^2.1.1", 1925 | "mkdirp": "^1.0.3", 1926 | "yallist": "^4.0.0" 1927 | }, 1928 | "engines": { 1929 | "node": ">=10" 1930 | } 1931 | }, 1932 | "node_modules/to-regex-range": { 1933 | "version": "5.0.1", 1934 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1935 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1936 | "dev": true, 1937 | "dependencies": { 1938 | "is-number": "^7.0.0" 1939 | }, 1940 | "engines": { 1941 | "node": ">=8.0" 1942 | } 1943 | }, 1944 | "node_modules/toidentifier": { 1945 | "version": "1.0.1", 1946 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1947 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1948 | "engines": { 1949 | "node": ">=0.6" 1950 | } 1951 | }, 1952 | "node_modules/touch": { 1953 | "version": "3.1.1", 1954 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", 1955 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", 1956 | "dev": true, 1957 | "bin": { 1958 | "nodetouch": "bin/nodetouch.js" 1959 | } 1960 | }, 1961 | "node_modules/tr46": { 1962 | "version": "4.1.1", 1963 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", 1964 | "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", 1965 | "dependencies": { 1966 | "punycode": "^2.3.0" 1967 | }, 1968 | "engines": { 1969 | "node": ">=14" 1970 | } 1971 | }, 1972 | "node_modules/type-is": { 1973 | "version": "1.6.18", 1974 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1975 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1976 | "dependencies": { 1977 | "media-typer": "0.3.0", 1978 | "mime-types": "~2.1.24" 1979 | }, 1980 | "engines": { 1981 | "node": ">= 0.6" 1982 | } 1983 | }, 1984 | "node_modules/typedarray": { 1985 | "version": "0.0.6", 1986 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1987 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" 1988 | }, 1989 | "node_modules/undefsafe": { 1990 | "version": "2.0.5", 1991 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1992 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 1993 | "dev": true 1994 | }, 1995 | "node_modules/unpipe": { 1996 | "version": "1.0.0", 1997 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1998 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1999 | "engines": { 2000 | "node": ">= 0.8" 2001 | } 2002 | }, 2003 | "node_modules/util-deprecate": { 2004 | "version": "1.0.2", 2005 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2006 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 2007 | }, 2008 | "node_modules/utils-merge": { 2009 | "version": "1.0.1", 2010 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2011 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 2012 | "engines": { 2013 | "node": ">= 0.4.0" 2014 | } 2015 | }, 2016 | "node_modules/vary": { 2017 | "version": "1.1.2", 2018 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2019 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 2020 | "engines": { 2021 | "node": ">= 0.8" 2022 | } 2023 | }, 2024 | "node_modules/webidl-conversions": { 2025 | "version": "7.0.0", 2026 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 2027 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 2028 | "engines": { 2029 | "node": ">=12" 2030 | } 2031 | }, 2032 | "node_modules/whatwg-url": { 2033 | "version": "13.0.0", 2034 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", 2035 | "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", 2036 | "dependencies": { 2037 | "tr46": "^4.1.1", 2038 | "webidl-conversions": "^7.0.0" 2039 | }, 2040 | "engines": { 2041 | "node": ">=16" 2042 | } 2043 | }, 2044 | "node_modules/wide-align": { 2045 | "version": "1.1.5", 2046 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 2047 | "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", 2048 | "dependencies": { 2049 | "string-width": "^1.0.2 || 2 || 3 || 4" 2050 | } 2051 | }, 2052 | "node_modules/wrappy": { 2053 | "version": "1.0.2", 2054 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2055 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 2056 | }, 2057 | "node_modules/xtend": { 2058 | "version": "4.0.2", 2059 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 2060 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 2061 | "engines": { 2062 | "node": ">=0.4" 2063 | } 2064 | }, 2065 | "node_modules/yallist": { 2066 | "version": "4.0.0", 2067 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 2068 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 2069 | } 2070 | } 2071 | } 2072 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backendproject", 3 | "version": "1.0.0", 4 | "description": "backend project", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon src/index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "nodemon": "^3.1.3", 14 | "prettier": "^3.3.2" 15 | }, 16 | "dependencies": { 17 | "bcrypt": "^5.1.1", 18 | "cloudinary": "^2.2.0", 19 | "cookie-parser": "^1.4.6", 20 | "cors": "^2.8.5", 21 | "dotenv": "^16.4.5", 22 | "express": "^4.19.2", 23 | "jsonwebtoken": "^9.0.2", 24 | "mongoose": "^8.4.3", 25 | "mongoose-aggregate-paginate-v2": "^1.0.7", 26 | "multer": "^1.4.5-lts.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/temp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Damandeep-S/BackendProject/076837023c55c6e58857dcdf2b66303ca5655fd6/public/temp/.gitkeep -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import cookieParser from 'cookie-parser' 3 | import cors from 'cors' 4 | 5 | const app = express() 6 | 7 | app.use(cors()) // Enabling CORS makes it possible for your app to interact with servers located on different domains. 8 | app.use(express.json({limit:"16kb"})) //This line tells the app to use JSON (JavaScript Object Notation) parser middleware, which helps the app understand and work with JSON data sent in requests. The limit: "16kb" part restricts the size of the JSON data it will accept to 16 kilobytes. 9 | app.use(express.urlencoded({extended:true})) //This line enables the app to parse URL-encoded data (like the data submitted in an HTML form). The {extended: true} option allows the app to handle complex data structures (nested objects) in the URL-encoded data. 10 | app.use(express.static('public')) //example, if there's an image in the public folder, it can be accessed via the URL http://yourapp.com/image.png. 11 | 12 | app.use(cookieParser()) 13 | 14 | 15 | //routes import 16 | 17 | import userRouter from './routes/user.routes.js' 18 | import videoRouter from './routes/video.routes.js' 19 | import tweetRouter from './routes/tweet.routes.js' 20 | import playlistRouter from './routes/playlist.routes.js' 21 | import subscriptionRouter from "./routes/subscription.routes.js" 22 | import likeRouter from './routes/like.routes.js' 23 | import commentRouter from './routes/comment.routes.js' 24 | import healthcheckRouter from "./routes/healthcheck.routes.js" 25 | import dashboardRouter from "./routes/dashboard.routes.js" 26 | 27 | //routes declaration 28 | 29 | app.use("/api/v1/users",userRouter) 30 | app.use("/api/v1/videos", videoRouter) 31 | app.use("/api/v1/tweets", tweetRouter) 32 | app.use("/api/v1/playlist",playlistRouter) 33 | app.use("/api/v1/subscriptions", subscriptionRouter) 34 | app.use("/api/v1/likes", likeRouter) 35 | app.use("/api/v1/comments",commentRouter) 36 | app.use("/api/v1/healthcheck", healthcheckRouter) 37 | app.use("/api/v1/dashboard", dashboardRouter) 38 | 39 | export {app} -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const DB_NAME="daman_db" -------------------------------------------------------------------------------- /src/controllers/comment.controller.js: -------------------------------------------------------------------------------- 1 | import mongoose, { isValidObjectId } from "mongoose"; 2 | import { Comment } from "../models/comment.model.js"; 3 | import { ApiError } from "../utils/ApiError.js"; 4 | import { ApiResponse } from "../utils/ApiResponse.js"; 5 | import { asyncHandler } from "../utils/asyncHandler.js"; 6 | 7 | const getVideoComments = asyncHandler(async (req, res) => { 8 | const { videoId } = req.params; 9 | const { page = 1, limit = 10 } = req.query; 10 | 11 | if (!isValidObjectId(videoId)) { 12 | throw new ApiError(400, "Invalid video ID"); 13 | } 14 | 15 | const skip = (page - 1) * limit; 16 | const comments = await Comment.find({ video: videoId }) 17 | .skip(skip) 18 | .limit(limit); 19 | 20 | if (!comments) { 21 | throw new ApiError(400, "No comments to display"); 22 | } 23 | 24 | return res 25 | .status(200) 26 | .json(new ApiResponse(200, comments, "Fetch All Comments Successfully")); 27 | }); 28 | 29 | const addComment = asyncHandler(async (req, res) => { 30 | const { videoId } = req.params; 31 | const { content } = req.body; 32 | 33 | if (!isValidObjectId(videoId)) { 34 | throw new ApiError(400, "Invalid video ID"); 35 | } 36 | 37 | const comment = await Comment.create({ 38 | content, 39 | video: videoId, 40 | owner: req.user?._id, 41 | }); 42 | 43 | if (!comment) { 44 | throw new ApiError(400, "Comment could not be added"); 45 | } 46 | 47 | return res 48 | .status(201) 49 | .json(new ApiResponse(200, comment, "Comment done Successfully")); 50 | }); 51 | 52 | const updateComment = asyncHandler(async (req, res) => { 53 | const { commentId } = req.params; 54 | const { content } = req.body; 55 | 56 | if (!isValidObjectId(commentId)) { 57 | throw new ApiError(400, "Invalid comment ID"); 58 | } 59 | 60 | if (!content) { 61 | throw new ApiError(400, "content is not filled"); 62 | } 63 | 64 | const updatedComment = await Comment.findByIdAndUpdate( 65 | commentId, 66 | { 67 | $set: { 68 | content: content, 69 | }, 70 | }, 71 | { new: true } 72 | ); 73 | 74 | if (!updatedComment) { 75 | throw new ApiError(400, "Comment not found"); 76 | } 77 | 78 | return res 79 | .status(200) 80 | .json(new ApiResponse(200, updatedComment, "Comment Updated Successfully")); 81 | }); 82 | 83 | const deleteComment = asyncHandler(async (req, res) => { 84 | const { commentId } = req.params; 85 | 86 | if (!isValidObjectId(commentId)) { 87 | throw new ApiError(400, "Invalid comment ID"); 88 | } 89 | 90 | await Comment.findByIdAndDelete({ _id: commentId }); 91 | 92 | 93 | return res 94 | .status(200) 95 | .json(new ApiResponse(200, {}, "Comment Deleted Successfully")); 96 | }); 97 | 98 | export { getVideoComments, addComment, updateComment, deleteComment }; 99 | -------------------------------------------------------------------------------- /src/controllers/dashboard.controller.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose" 2 | import {Video} from "../models/video.model.js" 3 | import {ApiError} from "../utils/ApiError.js" 4 | import {ApiResponse} from "../utils/ApiResponse.js" 5 | import {asyncHandler} from "../utils/asyncHandler.js" 6 | 7 | const getChannelStats = asyncHandler(async (req, res) => { 8 | 9 | if (!req.user?._id) throw new ApiError(404, "Unauthorized request"); 10 | const userId = req.user?._id; 11 | 12 | 13 | const channelStats = await Video.aggregate([ 14 | { $match: { owner: userId } }, 15 | // Lookup for Subscribers of a channel 16 | { 17 | $lookup: { 18 | from: "subscriptions", 19 | localField: "owner", 20 | foreignField: "channel", 21 | as: "subscribers", 22 | }, 23 | }, 24 | // Lookup for the channel which the owner Subscribe 25 | { 26 | $lookup: { 27 | from: "subscriptions", 28 | localField: "owner", 29 | foreignField: "subscriber", 30 | as: "subscribedTo", 31 | }, 32 | }, 33 | // Lookup likes for the user's videos 34 | { 35 | $lookup: { 36 | from: "likes", 37 | localField: "_id", 38 | foreignField: "video", 39 | as: "likedVideos", 40 | }, 41 | }, 42 | // Lookup comments for the user's videos 43 | { 44 | $lookup: { 45 | from: "comments", 46 | localField: "_id", 47 | foreignField: "video", 48 | as: "videoComments", 49 | }, 50 | }, 51 | // Lookup tweets by the user 52 | { 53 | $lookup: { 54 | from: "tweets", 55 | localField: "owner", 56 | foreignField: "owner", 57 | as: "tweets", 58 | }, 59 | }, 60 | // Group to calculate stats 61 | { 62 | $group: { 63 | _id: null, 64 | totalVideos: { $sum: 1 }, 65 | totalViews: { $sum: "$views" }, 66 | subscribers: { $first: "$subscribers" }, 67 | subscribedTo: { $first: "$subscribedTo" }, 68 | totalLikes: { $sum: {$size: "$likedVideos"} }, 69 | totalComments: { $sum: { $size: "$videoComments" } }, 70 | totalTweets: { $first: { $size: "$tweets" } }, 71 | }, 72 | }, 73 | // Project the desired fields 74 | { 75 | $project: { 76 | _id: 0, 77 | totalVideos: 1, 78 | totalViews: 1, 79 | subscribers: { $size: "$subscribers" }, 80 | subscribedTo: { $size: "$subscribedTo" }, 81 | totalLikes: 1, 82 | totalComments: 1, 83 | totalTweets: 1, 84 | }, 85 | }, 86 | ]); 87 | 88 | res 89 | .status(200) 90 | .json( 91 | new ApiResponse( 92 | 200, 93 | channelStats[0], 94 | "Channel stats fetched successfully" 95 | ) 96 | ); 97 | }) 98 | 99 | const getChannelVideos = asyncHandler(async (req, res) => { 100 | 101 | if (!req.user?._id) throw new ApiError(404, "Unauthorized request"); 102 | 103 | const videos = await Video.find({ 104 | owner: req.user._id 105 | }) 106 | 107 | if (!videos[0]) { 108 | return res.status(200) 109 | .json(new ApiResponse(200, [], "No videos found")) 110 | } 111 | 112 | return res.status(200) 113 | .json(new ApiResponse(200, videos, "Total videos fetched successfully")) 114 | }) 115 | 116 | export { 117 | getChannelStats, 118 | getChannelVideos 119 | } -------------------------------------------------------------------------------- /src/controllers/healthcheck.controller.js: -------------------------------------------------------------------------------- 1 | import { ApiResponse } from "../utils/ApiResponse.js"; 2 | import { asyncHandler } from "../utils/asyncHandler.js"; 3 | 4 | const healthCheck = asyncHandler(async (req, res) => { 5 | 6 | return res 7 | .status(200) 8 | .json(new ApiResponse(200, {}, "Health Checkup Successfull")); 9 | }); 10 | 11 | export { healthCheck }; 12 | -------------------------------------------------------------------------------- /src/controllers/like.controller.js: -------------------------------------------------------------------------------- 1 | import mongoose, { isValidObjectId } from "mongoose"; 2 | import { Like } from "../models/like.model.js"; 3 | import { ApiError } from "../utils/ApiError.js"; 4 | import { ApiResponse } from "../utils/ApiResponse.js"; 5 | import { asyncHandler } from "../utils/asyncHandler.js"; 6 | 7 | const toggleVideoLike = asyncHandler(async (req, res) => { 8 | const { videoId } = req.params; 9 | 10 | if (!isValidObjectId(videoId)) { 11 | throw new ApiError(400, "Invalid video ID"); 12 | } 13 | 14 | const like = await Like.findOne({ video: videoId, likedBy: req.user._id }); 15 | 16 | if (like) { 17 | await like.deleteOne(); 18 | return res 19 | .status(200) 20 | .json(new ApiResponse(200, {}, "Like Removed Successfully")); 21 | } 22 | 23 | const likedVideo = await Like.create({ 24 | video: videoId, 25 | likedBy: req.user._id, 26 | }); 27 | 28 | return res 29 | .status(201) 30 | .json(new ApiResponse(200, likedVideo, "Like Added Successfully")); 31 | }); 32 | 33 | const toggleCommentLike = asyncHandler(async (req, res) => { 34 | const { commentId } = req.params; 35 | 36 | if (!isValidObjectId(commentId)) { 37 | throw new ApiError(400, "Invalid comment ID"); 38 | } 39 | 40 | const like = await Like.findOne({ 41 | comment: commentId, 42 | likedBy: req.user._id, 43 | }); 44 | 45 | if (like) { 46 | await like.deleteOne(); 47 | return res 48 | .status(200) 49 | .json(new ApiResponse(200, {}, "Like Removed Successfully")); 50 | } 51 | 52 | const likedComment = await Like.create({ 53 | comment: commentId, 54 | likedBy: req.user._id, 55 | }); 56 | 57 | return res 58 | .status(201) 59 | .json(new ApiResponse(200, likedComment, "Comment Added Successfully")); 60 | }); 61 | 62 | const toggleTweetLike = asyncHandler(async (req, res) => { 63 | const { tweetId } = req.params; 64 | 65 | if (!isValidObjectId(tweetId)) { 66 | throw new ApiError(400, "Invalid tweet ID"); 67 | } 68 | 69 | const like = await Like.findOne({ tweet: tweetId, likedBy: req.user._id }); 70 | 71 | if (like) { 72 | await like.deleteOne(); 73 | return res 74 | .status(200) 75 | .json(new ApiResponse(200, {}, "Like Removed Successfully")); 76 | } 77 | 78 | const likedTweet = await Like.create({ 79 | tweet: tweetId, 80 | likedBy: req.user._id, 81 | }); 82 | 83 | return res 84 | .status(201) 85 | .json(new ApiResponse(200, likedTweet, "Tweet Added Successfully")); 86 | }); 87 | 88 | const getLikedVideos = asyncHandler(async (req, res) => { 89 | const likedVideos = await Like.find({ 90 | likedBy: req.user?._id, 91 | video: { $exists: true }, 92 | }).populate("video"); 93 | 94 | // const likedVideos = likes.filter(like => like.video).map(like => like.video) 95 | 96 | if (likedVideos.length === 0) { 97 | return res 98 | .status(200) 99 | .json(new ApiResponse(200, {}, "You haven't liked any videos yet")); 100 | } 101 | 102 | return res 103 | .status(200) 104 | .json( 105 | new ApiResponse(200, likedVideos, "Liked videos fetched successfully") 106 | ); 107 | }); 108 | 109 | export { toggleCommentLike, toggleTweetLike, toggleVideoLike, getLikedVideos }; 110 | -------------------------------------------------------------------------------- /src/controllers/playlist.controller.js: -------------------------------------------------------------------------------- 1 | import { asyncHandler } from "../utils/asyncHandler.js"; 2 | import { Playlist } from "../models/playlist.model.js"; 3 | import { Video } from "../models/video.model.js"; 4 | import { ApiError } from "../utils/ApiError.js"; 5 | import { ApiResponse } from "../utils/ApiResponse.js"; 6 | import mongoose, { isValidObjectId } from "mongoose"; 7 | 8 | const createPlaylist = asyncHandler(async (req, res) => { 9 | const { name, description } = req.body; 10 | 11 | if (!name || !description) { 12 | throw new ApiError(400, "Both name and description is required"); 13 | } 14 | 15 | const playlist = await Playlist.create({ 16 | name, 17 | description, 18 | owner: req.user._id, 19 | }); 20 | if (!playlist) { 21 | throw new ApiError(400, "Error while creating playlist"); 22 | } 23 | 24 | return res 25 | .status(200) 26 | .json(new ApiResponse(200, playlist, "Playlist created")); 27 | }); 28 | 29 | const getUserPlaylists = asyncHandler(async (req, res) => { 30 | const { userId } = req.params; 31 | if (!userId) { 32 | throw new ApiError(400, "UserId is required"); 33 | } 34 | 35 | const playlists = await Playlist.find({ owner: userId }); 36 | 37 | if (!playlists) { 38 | throw new ApiError(400, "No playlist found"); 39 | } 40 | 41 | return res 42 | .status(200) 43 | .json(new ApiResponse(200, playlists, "Playlists fetched")); 44 | }); 45 | 46 | const getPlaylistById = asyncHandler(async (req, res) => { 47 | const { playlistId } = req.params; 48 | 49 | if (!playlistId) { 50 | throw new ApiError(400, "Playlist does not exists"); 51 | } 52 | 53 | const playlist = await Playlist.findById(playlistId); 54 | 55 | if (!playlist) { 56 | throw new ApiError(404, "Error while fetching playlist"); 57 | } 58 | 59 | return res 60 | .status(200) 61 | .json( 62 | new ApiResponse(200, playlist, "Playlist Details Fetched Successfully") 63 | ); 64 | }); 65 | 66 | const addVideoToPlaylist = asyncHandler(async (req, res) => { 67 | const { playlistId, videoId } = req.params; 68 | const video = await Video.findById(videoId); 69 | 70 | if (!video) { 71 | throw new ApiError(400, "No such video exists"); 72 | } 73 | 74 | // const playlist = await Playlist.findById(playlistId); 75 | // if (!playlist) { 76 | // return res.status(404).json({ message: "Playlist not found" }); 77 | // } 78 | 79 | // if (playlist.videos.includes(videoId)) { 80 | // return res 81 | // .status(400) 82 | // .json({ message: "Video already exists in the playlist" }); 83 | // } 84 | 85 | const updatedPlaylist = await Playlist.findOneAndUpdate( 86 | { _id: playlistId, videos: { $ne: videoId } }, 87 | { $push: { videos: videoId } }, 88 | { new: true } 89 | ); 90 | 91 | if (!updatedPlaylist) { 92 | throw new ApiError( 93 | 404, 94 | "Playlist not found or Video already exists in the playlist" 95 | ); 96 | } 97 | 98 | return res 99 | .status(200) 100 | .json(new ApiResponse(200, updatedPlaylist, "Video added to playlist")); 101 | }); 102 | 103 | const removeVideoFromPlaylist = asyncHandler(async (req, res) => { 104 | const { playlistId, videoId } = req.params; 105 | 106 | if (!isValidObjectId(playlistId)) { 107 | throw new ApiError(400, "Invalid Playlist Id"); 108 | } 109 | 110 | if (!isValidObjectId(videoId)) { 111 | throw new ApiError(400, "Invalid Video Id"); 112 | } 113 | 114 | const playlist = await Playlist.findByIdAndUpdate( 115 | playlistId, 116 | { $pull: { videos: videoId } }, 117 | { new: true } 118 | ); 119 | 120 | if (!playlist) { 121 | throw new ApiError(404, "Playlist not found"); 122 | } 123 | 124 | return res 125 | .status(200) 126 | .json(new ApiResponse(200, playlist, "Video Removed from the playlist")); 127 | }); 128 | 129 | const deletePlaylist = asyncHandler(async (req, res) => { 130 | const { playlistId } = req.params; 131 | 132 | if (!isValidObjectId(playlistId)) { 133 | throw new ApiError(400, "Invalid playlist ID"); 134 | } 135 | 136 | await Playlist.findByIdAndDelete(playlistId); 137 | return res 138 | .status(200) 139 | .json(new ApiResponse(200, {}, "Playlist Deleted Successfully")); 140 | }); 141 | 142 | const updatePlaylist = asyncHandler(async (req, res) => { 143 | const { playlistId } = req.params; 144 | const { name, description } = req.body; 145 | 146 | if (!isValidObjectId(playlistId)) { 147 | throw new ApiError(401, "Invalid playlist Id"); 148 | } 149 | 150 | const updatedPlaylist = await Playlist.findByIdAndUpdate( 151 | playlistId, 152 | { 153 | $set: { 154 | name: name, 155 | description: description, 156 | }, 157 | }, 158 | { new: true } 159 | ); 160 | if (!updatedPlaylist) { 161 | throw new ApiError(500, "playlist not updated"); 162 | } 163 | return res 164 | .status(200) 165 | .json( 166 | new ApiResponse( 167 | 200, 168 | updatedPlaylist, 169 | "Playlist Details Updated Successfully" 170 | ) 171 | ); 172 | }); 173 | 174 | export { 175 | createPlaylist, 176 | getUserPlaylists, 177 | getPlaylistById, 178 | addVideoToPlaylist, 179 | removeVideoFromPlaylist, 180 | deletePlaylist, 181 | updatePlaylist, 182 | }; 183 | -------------------------------------------------------------------------------- /src/controllers/subscription.controller.js: -------------------------------------------------------------------------------- 1 | import mongoose, { isValidObjectId } from "mongoose"; 2 | import { Subscription } from "../models/subscriptions.model.js"; 3 | import { ApiError } from "../utils/ApiError.js"; 4 | import { ApiResponse } from "../utils/ApiResponse.js"; 5 | import { asyncHandler } from "../utils/asyncHandler.js"; 6 | 7 | const toggleSubscription = asyncHandler(async (req, res) => { 8 | const { channelId } = req.params; 9 | // TODO: toggle subscription 10 | 11 | if (!isValidObjectId(channelId)) { 12 | throw new ApiError(400, "Invalid channel ID"); 13 | } 14 | 15 | const subscriptionCheck = await Subscription.findOne({ 16 | channel: channelId, 17 | subscriber: req.user?._id, 18 | }); 19 | 20 | if (subscriptionCheck) { 21 | await subscriptionCheck.deleteOne(); 22 | return res 23 | .status(200) 24 | .json(new ApiResponse(200, {}, "Subscription Removed Successfully")); 25 | } 26 | 27 | const createSubscription = await Subscription.create({ 28 | channel: channelId, 29 | subscriber: req.user?._id, 30 | }); 31 | 32 | return res 33 | .status(200) 34 | .json( 35 | new ApiResponse( 36 | 200, 37 | createSubscription, 38 | "Congratulation! You have Successfully Subscribed this channel" 39 | ) 40 | ); 41 | }); 42 | 43 | const getUserChannelSubscribers = asyncHandler(async (req, res) => { 44 | const { channelId } = req.params; 45 | 46 | if (!isValidObjectId(channelId)) { 47 | throw new ApiError(400, "Invalid channel id"); 48 | } 49 | 50 | const subscribers = await Subscription.find({ channel: channelId }).populate( 51 | "subscriber", 52 | "fullName email username avatar coverImage" 53 | ); 54 | 55 | return res 56 | .status(200) 57 | .json( 58 | new ApiResponse( 59 | 200, 60 | { subscribers }, 61 | "Subscribers are fetched successfully" 62 | ) 63 | ); 64 | }); 65 | 66 | const getSubscribedChannels = asyncHandler(async (req, res) => { 67 | const { subscriberId } = req.params; 68 | 69 | if (!isValidObjectId(subscriberId)) { 70 | throw new ApiError(400, "Invalid subscriberId id"); 71 | } 72 | 73 | const subscribedChannels = await Subscription.find({ 74 | subscriber: subscriberId, 75 | }).populate("channel","fullname email username avatar coverImage") 76 | return res 77 | .status(200) 78 | .json( 79 | new ApiResponse( 80 | 200, 81 | { subscribedChannels }, 82 | "Subscribers are fetched successfully" 83 | ) 84 | ); 85 | }); 86 | 87 | export { toggleSubscription, getUserChannelSubscribers, getSubscribedChannels }; 88 | -------------------------------------------------------------------------------- /src/controllers/tweet.controller.js: -------------------------------------------------------------------------------- 1 | import { asyncHandler } from "../utils/asyncHandler.js"; 2 | import { ApiError } from "../utils/ApiError.js"; 3 | import { Tweet } from "../models/tweet.model.js"; 4 | import { ApiResponse } from "../utils/ApiResponse.js"; 5 | import mongoose from "mongoose"; 6 | import { User } from "../models/user.model.js"; 7 | 8 | const createTweet = asyncHandler(async (req, res) => { 9 | const { content } = req.body; 10 | const user = req.user?._id; 11 | 12 | if (!content) { 13 | throw new ApiError(400, "Tweet content is required"); 14 | } 15 | 16 | const newTweet = await Tweet.create({ 17 | content, 18 | owner: user, 19 | }); 20 | 21 | if (!newTweet) { 22 | throw new ApiError(400, "Tweet could not be posted"); 23 | } 24 | 25 | return res 26 | .status(200) 27 | .json(new ApiResponse(200, newTweet, "Tweet posted successfully")); 28 | }); 29 | 30 | const getUserTweets = asyncHandler(async (req, res) => { 31 | const { userId } = req.params; 32 | if (!userId) { 33 | throw new ApiError(400, "User not found"); 34 | } 35 | 36 | const tweets = await Tweet.find({ owner: userId }); 37 | 38 | if (!tweets) { 39 | throw new ApiError(400, "Error occurred while fetching tweets"); 40 | } 41 | 42 | return res 43 | .status(200) 44 | .json(new ApiResponse(200, tweets, "Tweets fetched successfully")); 45 | }); 46 | 47 | const updateTweet = asyncHandler(async (req, res) => { 48 | const { tweetId } = req.params; 49 | const { content } = req.body; 50 | 51 | if (!tweetId) { 52 | throw new ApiError(400, "Tweet Id is required"); 53 | } 54 | if (!content) { 55 | throw new ApiError(400, "Tweet content is required"); 56 | } 57 | 58 | const tweet = await Tweet.findByIdAndUpdate( 59 | tweetId, 60 | { 61 | $set: { content: content }, 62 | }, 63 | { new: true } 64 | ); 65 | 66 | if (!tweet) { 67 | throw new ApiError(400, "Tweet does not exists"); 68 | } 69 | 70 | return res.status(200) 71 | .json(new ApiResponse(200,tweet,"Tweet content updated")) 72 | }); 73 | 74 | 75 | const deleteTweet= asyncHandler(async (req, res) => { 76 | const {tweetId}=req.params 77 | 78 | if(!tweetId?.trim()){ 79 | throw new ApiError(404,"TweetId is required") 80 | } 81 | 82 | const tweet=await Tweet.findByIdAndDelete(tweetId) 83 | if(!tweet){ 84 | throw new ApiError(400,"Tweet does not exists") 85 | } 86 | 87 | return res.status(200) 88 | .json(new ApiResponse(200,{},"Tweet deleted successfully")) 89 | }); 90 | 91 | export { createTweet, getUserTweets,updateTweet ,deleteTweet}; 92 | -------------------------------------------------------------------------------- /src/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | import { asyncHandler } from "../utils/asyncHandler.js"; 2 | import { ApiError } from "../utils/ApiError.js"; 3 | import { User } from "../models/user.model.js"; 4 | import { uploadOnCloudinary } from "../utils/cloudinary.js"; 5 | import { ApiResponse } from "../utils/ApiResponse.js"; 6 | import jwt from "jsonwebtoken"; 7 | import mongoose from "mongoose"; 8 | 9 | const generateAccessAndRefreshTokens = async (userId) => { 10 | try { 11 | const user = await User.findById(userId); 12 | const accessToken = user.generateAccessToken(); 13 | const refreshToken = user.generateRefreshToken(); 14 | 15 | user.refreshToken = refreshToken; //store refresh token in the database 16 | await user.save({ validateBeforeSave: false }); //otherwise it will require password validation again, ek hi toh add krke update kia hai so we should avoid it 17 | 18 | return { accessToken, refreshToken }; 19 | } catch (error) { 20 | throw new ApiError(500, "Something went wrong while generating tokens"); 21 | } 22 | }; 23 | 24 | const registerUser = asyncHandler(async (req, res) => { 25 | //steps to be followed 26 | //Get user details from the frontend 27 | //Validation- not empty 28 | //check if user is already register - username,email 29 | //check for images, check for avatar 30 | // Upload them to cloudinary 31 | //create user object- create entry in db 32 | //Remove password and refresh token field from the response 33 | //check for user creation 34 | //return res 35 | 36 | const { username, email, fullName, password } = req.body; 37 | 38 | if ( 39 | [fullName, email, username, password].some((field) => field?.trim() === "") 40 | ) { 41 | throw new ApiError(400, "All fields are required"); 42 | } 43 | 44 | const existedUser = await User.findOne({ 45 | $or: [{ username }, { email }], 46 | }); 47 | if (existedUser) { 48 | throw new ApiError(409, "User already exists"); 49 | } 50 | 51 | const avatarLocalPath = req.files?.avatar[0]?.path; 52 | // const coverImageLocalPath=req.files?.coverImage[0]?.path; 53 | //It will give error cannot read properties, when we did not give cover image 54 | 55 | let coverImageLocalPath; 56 | if ( 57 | req.files && 58 | Array.isArray(req.files.coverImage) && 59 | req.files.coverImage.length > 0 60 | ) { 61 | coverImageLocalPath = req.files.coverImage[0].path; 62 | } 63 | 64 | if (!avatarLocalPath) { 65 | throw new ApiError(400, "Avatar file is required"); 66 | } 67 | 68 | const avatar = await uploadOnCloudinary(avatarLocalPath); 69 | const coverImage = await uploadOnCloudinary(coverImageLocalPath); 70 | 71 | if (!avatar) { 72 | throw new ApiError(400, "Avatar is required"); 73 | } 74 | 75 | //Now if everything is good then we make a object and send it to db 76 | 77 | const newUser = await User.create({ 78 | fullName, 79 | avatar: avatar.url, 80 | coverImage: coverImage?.url || "", 81 | email, 82 | password, 83 | username: username.toLowerCase(), 84 | }); 85 | 86 | const createdUser = await User.findById(newUser._id).select( 87 | "-password -refreshToken" 88 | ); 89 | 90 | if (!createdUser) { 91 | throw new ApiError(500, "Something went wrong while registering User"); 92 | } 93 | 94 | return res 95 | .status(201) 96 | .json(new ApiResponse(200, createdUser, "User Registered Successfully")); 97 | }); 98 | 99 | const loginUser = asyncHandler(async (req, res) => { 100 | //req body->data 101 | //username or email 102 | //find the user 103 | //password check 104 | //access token and refresh token 105 | //send cookie 106 | 107 | const { email, username, password } = req.body; 108 | 109 | if (!username && !email) { 110 | throw new ApiError(400, "Username or email is required"); 111 | } 112 | 113 | const user = await User.findOne({ 114 | $or: [{ username }, { email }], 115 | }); 116 | 117 | if (!user) { 118 | throw new ApiError(404, "User does not exist"); 119 | } 120 | 121 | const isPasswordValid = await user.isPasswordCorrect(password); //Here user is in lowercase remember, User is monodb related but you want to access own methods, so use user 122 | 123 | if (!isPasswordValid) { 124 | throw new ApiError(401, "Invalid Credentials"); 125 | } 126 | 127 | const { accessToken, refreshToken } = await generateAccessAndRefreshTokens( 128 | user._id 129 | ); 130 | 131 | const loggedInUser = await User.findById(user._id).select( 132 | "-password -refreshToken" 133 | ); //this is optional, 134 | 135 | const options = { 136 | httpOnly: true, 137 | secure: true, 138 | }; 139 | 140 | return res 141 | .status(200) 142 | .cookie("accessToken", accessToken, options) 143 | .cookie("refreshToken", refreshToken, options) 144 | .json( 145 | new ApiResponse( 146 | 200, 147 | { 148 | user: loggedInUser, 149 | accessToken, 150 | refreshToken, 151 | }, 152 | "User logged in successfully" 153 | ) 154 | ); 155 | }); 156 | 157 | const logoutUser = asyncHandler(async (req, res) => { 158 | await User.findByIdAndUpdate( 159 | req.user._id, 160 | { 161 | $set: { 162 | refreshToken: undefined, 163 | }, 164 | }, 165 | { 166 | new: true, 167 | } 168 | ); 169 | const options = { 170 | httpOnly: true, 171 | secure: true, 172 | }; 173 | 174 | return res 175 | .status(200) 176 | .clearCookie("accessToken", options) 177 | .clearCookie("refreshToken", options) 178 | .json(new ApiResponse(200, {}, "User logged out")); 179 | }); 180 | 181 | const refreshAccessToken = asyncHandler(async (req, res) => { 182 | const incomingRefreshToken = 183 | req.cookies.refreshToken || req.body.refreshToken; 184 | 185 | if (!incomingRefreshToken) { 186 | throw new ApiError(401, "Unauthorized request"); 187 | } 188 | 189 | try { 190 | const decodedToken = jwt.verify( 191 | incomingRefreshToken, 192 | process.env.REFRESH_TOKEN_SECRET 193 | ); 194 | const user = await User.findById(decodedToken?._id); 195 | 196 | if (!user) { 197 | throw new ApiError(401, "Refresh token is invalid"); 198 | } 199 | 200 | if (incomingRefreshToken !== user?.refreshToken) { 201 | throw new ApiError(401, "Refresh token is expired or used"); 202 | } 203 | 204 | const options = { 205 | httpOnly: true, 206 | secure: true, 207 | }; 208 | 209 | const { accessToken, newRefreshToken } = 210 | await generateAccessAndRefreshTokens(user._id); 211 | 212 | return res 213 | .status(200) 214 | .cookie("accessToken", accessToken, options) 215 | .cookie("refreshToken", newRefreshToken, options) 216 | .json( 217 | new ApiResponse( 218 | 200, 219 | { accessToken, newRefreshToken }, 220 | "Access token refreshed" 221 | ) 222 | ); 223 | } catch (error) { 224 | throw ApiError(401, error?.message || "Invalid refresh token 401"); 225 | } 226 | }); 227 | 228 | const changeCurrentPassword = asyncHandler(async (req, res) => { 229 | const { oldPassword, newPassword } = req.body; 230 | 231 | const user = await User.findById(req.user?._id); 232 | const isPasswordCorrect = await user.isPasswordCorrect(oldPassword); 233 | 234 | if (!isPasswordCorrect) { 235 | throw new ApiError(400, "Invalid current password"); 236 | } 237 | 238 | user.password = newPassword; 239 | await user.save({ validateBeforeSave: false }); 240 | 241 | return res 242 | .status(200) 243 | .json(new ApiResponse(200, {}, "Password changed Successfully")); 244 | }); 245 | 246 | const getCurrentUser = asyncHandler(async (req, res) => { 247 | return res.status(200).json( new ApiResponse(200, req.user, "Current user fetched")); 248 | }); 249 | 250 | const updateAccountDetails = asyncHandler(async (req, res) => { 251 | const { fullName, email } = req.body; 252 | 253 | if (!fullName && !email) { 254 | throw new ApiError(400, "All fields are required"); 255 | } 256 | 257 | const user = await User.findByIdAndUpdate( 258 | req.user?._id, 259 | { 260 | $set: { fullName: fullName, email: email }, 261 | }, 262 | { new: true } 263 | ).select("-password"); 264 | 265 | return res 266 | .status(200) 267 | .json(new ApiResponse(200, user, "Account details Updated")); 268 | }); 269 | 270 | const updateAvatar = asyncHandler(async (req, res) => { 271 | const avatarLocalPath = req.file?.path; 272 | 273 | if (!avatarLocalPath) { 274 | throw new ApiError(400, "Avatar is missing"); 275 | } 276 | 277 | const avatar = await uploadOnCloudinary(avatarLocalPath); 278 | 279 | if (!avatar.url) { 280 | throw new ApiError(400, "Error while uploading"); 281 | } 282 | 283 | const user = await User.findByIdAndUpdate( 284 | req.user?._id, 285 | { 286 | avatar: avatar.url, 287 | }, 288 | { new: true } 289 | ).select("-password"); 290 | 291 | return res 292 | .status(200) 293 | .json(new ApiResponse(200, user, "Avatar image is updated")); 294 | }); 295 | 296 | const updateCoverImage = asyncHandler(async (req, res) => { 297 | const coverLocalPath = req.file?.path; 298 | 299 | if (!coverLocalPath) { 300 | throw new ApiError(400, "Cover image file is missing"); 301 | } 302 | 303 | const coverImage = await uploadOnCloudinary(coverLocalPath); 304 | 305 | if (!coverImage.url) { 306 | throw new ApiError(400, "Error while uploading"); 307 | } 308 | 309 | const user = await User.findByIdAndUpdate( 310 | req.user?._id, 311 | { 312 | coverImage: coverImage.url, 313 | }, 314 | { new: true } 315 | ).select("-password"); 316 | 317 | return res 318 | .status(200) 319 | .json(new ApiResponse(200, user, "Cover image is updated")); 320 | }); 321 | 322 | const getUserChannelProfile = asyncHandler(async (req, res) => { 323 | const { username } = req.params; 324 | if (!username?.trim()) { 325 | throw new ApiError(400, "username is missing"); 326 | } 327 | 328 | const channel = await User.aggregate([ 329 | { 330 | $match: { 331 | username: username?.toLowerCase(), 332 | }, 333 | }, 334 | { 335 | $lookup: { 336 | from: "subscriptions", 337 | localField: "_id", 338 | foreignField: "channel", 339 | as: "subscribers", 340 | }, 341 | }, 342 | { 343 | $lookup: { 344 | from: "subscriptions", 345 | localField: "_id", 346 | foreignField: "subscriber", 347 | as: "subscribedTo", 348 | }, 349 | }, 350 | { 351 | $addFields: { 352 | subscribersCount: { 353 | $size: "$subscribers", 354 | }, 355 | channelsSubscribedToCount: { 356 | $size: "$subscribedTo", 357 | }, 358 | isSubscribed: { 359 | $cond: { 360 | if: { $in: [req.user?._id, "$subscribers.subscriber"] }, 361 | then: true, 362 | else: false, 363 | }, 364 | }, 365 | }, 366 | }, 367 | { 368 | $project: { 369 | fullName: 1, 370 | username: 1, 371 | subscribersCount: 1, 372 | channelsSubscribedToCount: 1, 373 | isSubscribed: 1, 374 | avatar: 1, 375 | coverImage: 1, 376 | email: 1, 377 | }, 378 | }, 379 | ]); 380 | 381 | if (!channel?.length) { 382 | throw new ApiError(404, "Channel does not exists"); 383 | } 384 | 385 | return res 386 | .status(200) 387 | .json( 388 | new ApiResponse(200, channel[0], "User channel fetched successfully") 389 | ); 390 | }); 391 | 392 | const getWatchHistory = asyncHandler(async (req, res) => { 393 | const user = await User.aggregate([ 394 | { 395 | $match: { 396 | _id: new mongoose.Types.ObjectId(req.user._id), 397 | }, 398 | }, 399 | { 400 | $lookup: { 401 | from: "videos", 402 | localField: "watchHistory", 403 | foreignField: "_id", 404 | as: "watchHistory", 405 | pipeline: [ 406 | { 407 | $lookup: { 408 | from: "users", 409 | localField: "owner", 410 | foreignField: "_id", 411 | as: "owner", 412 | pipeline: [ 413 | { 414 | $project: { 415 | fullName: 1, 416 | username: 1, 417 | avatar: 1, 418 | }, 419 | }, 420 | ], 421 | }, 422 | }, 423 | { 424 | $addFields: { 425 | owner: { 426 | $first: "$owner", 427 | }, 428 | }, 429 | }, 430 | ], 431 | }, 432 | }, 433 | ]); 434 | 435 | return res 436 | .status(200) 437 | .json( 438 | new ApiResponse( 439 | 200, 440 | user[0].watchHistory, 441 | "Watch history fetched successfully" 442 | ) 443 | ); 444 | }); 445 | 446 | export { 447 | registerUser, 448 | loginUser, 449 | logoutUser, 450 | refreshAccessToken, 451 | changeCurrentPassword, 452 | getCurrentUser, 453 | updateAccountDetails, 454 | updateCoverImage, 455 | updateAvatar, 456 | getUserChannelProfile, 457 | getWatchHistory 458 | }; 459 | -------------------------------------------------------------------------------- /src/controllers/video.controller.js: -------------------------------------------------------------------------------- 1 | import { asyncHandler } from "../utils/asyncHandler.js"; 2 | import { ApiError } from "../utils/ApiError.js"; 3 | import { Video } from "../models/video.model.js"; 4 | import { uploadOnCloudinary } from "../utils/cloudinary.js"; 5 | import { ApiResponse } from "../utils/ApiResponse.js"; 6 | import path from "path"; 7 | import jwt from "jsonwebtoken"; 8 | import mongoose from "mongoose"; 9 | 10 | const publishAVideo = asyncHandler(async (req, res) => { 11 | const { title, description } = req.body; 12 | 13 | if ([title, description].some((field) => field?.trim() === "")) { 14 | throw new ApiError(400, "All fields are required"); 15 | } 16 | 17 | const videoExists = await Video.findOne({ title }); 18 | if (videoExists) { 19 | throw new ApiError(409, "Video title already exists"); 20 | } 21 | 22 | let videoLocalPath; 23 | let thumbnailLocalPath; 24 | if ( 25 | req.files && 26 | Array.isArray(req.files.videoFile) && 27 | req.files.videoFile.length > 0 28 | ) { 29 | videoLocalPath = req.files.videoFile[0].path; 30 | } 31 | 32 | if ( 33 | req.files && 34 | Array.isArray(req.files.thumbnail) && 35 | req.files.thumbnail.length > 0 36 | ) { 37 | thumbnailLocalPath = req.files.thumbnail[0].path; 38 | } 39 | 40 | if (!videoLocalPath) { 41 | throw new ApiError(400, "Video file is required"); 42 | } 43 | const videoExt = path.extname(videoLocalPath); 44 | if (!(videoExt === ".mp4")) { 45 | throw new ApiError(400, "Only video file is accepted"); 46 | } 47 | 48 | if (!thumbnailLocalPath) { 49 | throw new ApiError(400, "Thumbnail file is required"); 50 | } 51 | 52 | const videoFile = await uploadOnCloudinary(videoLocalPath); 53 | const thumbnail = await uploadOnCloudinary(thumbnailLocalPath); 54 | 55 | if (!videoFile) { 56 | throw new ApiError(400, "Video is required"); 57 | } 58 | if (!thumbnail) { 59 | throw new ApiError(400, "Thumbnail is required"); 60 | } 61 | 62 | const videoDetails = await Video.create({ 63 | title, 64 | description, 65 | videoFile: videoFile.url, 66 | thumbnail: thumbnail.url, 67 | duration: videoFile?.duration, 68 | owner: req.user?._id 69 | }); 70 | 71 | if (!videoDetails) { 72 | throw new ApiError(500, "Something went wrong while Uploading"); 73 | } 74 | 75 | return res 76 | .status(201) 77 | .json(new ApiResponse(200, videoDetails, "Video Uploaded Successfully")); 78 | }); 79 | 80 | const getVideoById = asyncHandler(async (req, res) => { 81 | const { videoId } = req.params; 82 | if (!videoId?.trim()) { 83 | throw new ApiError(400, "Video Id is required"); 84 | } 85 | 86 | const video = await Video.findById(videoId).select("videoFile"); 87 | 88 | if (!video) { 89 | throw new ApiError(400, "Video not found with this id"); 90 | } 91 | return res 92 | .status(200) 93 | .json(new ApiResponse(200, video, "Video link fetched Successfully")); 94 | }); 95 | 96 | const deleteVideo = asyncHandler(async (req, res) => { 97 | const { videoId } = req.params; 98 | if (!videoId?.trim()) { 99 | throw new ApiError(404, "Video Id is required"); 100 | } 101 | 102 | const video = await Video.findByIdAndDelete(videoId); 103 | if (!video) { 104 | throw new ApiError(400, "Video does not exists"); 105 | } 106 | 107 | return res 108 | .status(200) 109 | .json(new ApiResponse(200, null, "Video deleted successfully")); 110 | }); 111 | 112 | const updateVideo = asyncHandler(async (req, res) => { 113 | const { videoId } = req.params; 114 | const { title, description } = req.body; 115 | 116 | if (!videoId) { 117 | throw new ApiError(400, "Video Id is required"); 118 | } 119 | 120 | if ([title, description].some((fields) => fields.trim() === "")) { 121 | throw new ApiError(400, "Title and Description is required"); 122 | } 123 | 124 | const thumbnailLocalPath = req.file?.path; 125 | 126 | if (!thumbnailLocalPath) { 127 | throw new ApiError(400, "Thumbnail is required"); 128 | } 129 | const thumbnail = await uploadOnCloudinary(thumbnailLocalPath); 130 | 131 | if (!thumbnail) { 132 | throw new ApiError( 133 | 500, 134 | "There was Problem while uploading Thumbnail on cloud" 135 | ); 136 | } 137 | 138 | const updatedVideo = await Video.findByIdAndUpdate( 139 | videoId, 140 | { 141 | $set: { 142 | title, 143 | description, 144 | thumbnail: thumbnail.url, 145 | }, 146 | }, 147 | { 148 | new: true, 149 | } 150 | ); 151 | 152 | if (!updatedVideo) { 153 | throw new ApiError(500, "Video was not Updated Due to some error"); 154 | } 155 | 156 | return res 157 | .status(200) 158 | .json(new ApiResponse(200, updatedVideo, "Video was updated successfully")); 159 | }); 160 | 161 | const togglePublishStatus = asyncHandler(async (req, res) => { 162 | const { videoId } = req.params; 163 | if (!videoId) { 164 | throw new ApiError(400, "Video id is required"); 165 | } 166 | // const currentVideo = await Video.findById(videoId); 167 | 168 | // if(!currentVideo){ 169 | // throw new ApiError(404,"Video does not exists") 170 | // } 171 | 172 | // const updatedVideo = await Video.findByIdAndUpdate( 173 | // videoId, 174 | // { 175 | // $set: { 176 | // isPublished: !currentVideo.isPublished, 177 | // }, 178 | // }, 179 | // { 180 | // new: true, 181 | // } 182 | // ); 183 | 184 | const updatedVideo = await Video.findOneAndUpdate( 185 | { _id: videoId }, 186 | [ 187 | { 188 | $set: { 189 | isPublished: { $not: "$isPublished" }, 190 | }, 191 | }, 192 | ], 193 | { new: true, runValidators: true } 194 | ); 195 | 196 | if (!updatedVideo) { 197 | throw new ApiError(400, "Video status was not updated"); 198 | } 199 | 200 | return res 201 | .status(200) 202 | .json(new ApiResponse(200, updatedVideo, "Video status updated")); 203 | }); 204 | 205 | const getAllVideos = asyncHandler(async (req, res) => { 206 | const { page = 1, limit = 10, query, sortBy, sortType, userId } = req.query; 207 | //TODO: get all videos based on query, sort, pagination 208 | const matchCondition = { 209 | $or: [ 210 | { title: { $regex: query, $options: "i" } }, //The "i" option makes the matching case-insensitive, meaning it will match both uppercase and lowercase characters 211 | { description: { $regex: query, $options: "i" } }, 212 | ], 213 | }; 214 | 215 | if (userId) { 216 | matchCondition.owner = new mongoose.Types.ObjectId(userId); 217 | } 218 | var videoAggregate; 219 | try { 220 | videoAggregate = Video.aggregate([ 221 | { 222 | $match: matchCondition, 223 | }, 224 | 225 | { 226 | $lookup: { 227 | from: "users", 228 | localField: "owner", 229 | foreignField: "_id", 230 | as: "owner", 231 | pipeline: [ 232 | { 233 | $project: { 234 | _id: 1, 235 | fullName: 1, 236 | avatar: "$avatar.url", 237 | username: 1, 238 | }, 239 | }, 240 | ], 241 | }, 242 | }, 243 | 244 | { 245 | $addFields: { 246 | owner: { 247 | $first: "$owner", 248 | }, 249 | }, 250 | }, 251 | 252 | { 253 | $sort: { 254 | [sortBy || "createdAt"]: sortType || 1, 255 | }, 256 | }, 257 | ]); 258 | } catch (error) { 259 | throw new ApiError( 260 | 500, 261 | error.message || "Internal server error in video aggregation" 262 | ); 263 | } 264 | 265 | const options = { 266 | page, 267 | limit, 268 | customLabels: { 269 | totalDocs: "totalVideos", 270 | docs: "videos", 271 | }, 272 | skip: (page - 1) * limit, 273 | limit: parseInt(limit), 274 | }; 275 | Video.aggregatePaginate(videoAggregate, options) 276 | .then((result) => { 277 | if (result?.videos?.length === 0 && userId) { 278 | return res 279 | .status(200) 280 | .json(new ApiResponse(200, [], "No videos found")); 281 | } 282 | 283 | return res 284 | .status(200) 285 | .json(new ApiResponse(200, result, "video fetched successfully")); 286 | }) 287 | .catch((error) => { 288 | throw new ApiError( 289 | 500, 290 | error?.message || "Internal server error in video aggregate Paginate" 291 | ); 292 | }); 293 | }); 294 | 295 | export { 296 | publishAVideo, 297 | getVideoById, 298 | deleteVideo, 299 | updateVideo, 300 | togglePublishStatus, 301 | getAllVideos, 302 | }; 303 | -------------------------------------------------------------------------------- /src/db/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import { DB_NAME } from "../constants.js"; 3 | import 'dotenv/config' 4 | 5 | const connectDB = async()=>{ 6 | try{ 7 | const connectionInstance= await mongoose.connect(`${process.env.MONGODB_URL}/${DB_NAME}`) 8 | console.log(`\n MongoDB Connected !! DB HOST : ${connectionInstance.connection.host}`); 9 | }catch(error){ 10 | console.log("MONGODB connection failed",error); 11 | process.exit(1) 12 | } 13 | } 14 | 15 | export default connectDB -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import connectDB from "./db/index.js"; 2 | import 'dotenv/config' 3 | import {app} from './app.js' 4 | 5 | connectDB() 6 | .then(()=>{ 7 | app.listen(process.env.PORT || 4000,()=>{ 8 | console.log(`Server is running at port ${process.env.PORT}`) 9 | }) 10 | }) 11 | .catch((err)=>{ 12 | console.log("MONGODB connection failed !!!",err); 13 | }) 14 | 15 | -------------------------------------------------------------------------------- /src/middlewares/auth.middleware.js: -------------------------------------------------------------------------------- 1 | import { User } from "../models/user.model.js" 2 | import { ApiError } from "../utils/ApiError.js" 3 | import { asyncHandler } from "../utils/asyncHandler.js" 4 | import jwt from "jsonwebtoken" 5 | 6 | export const verifyJWT = asyncHandler(async(req, _, next) => { 7 | try { 8 | const token = req.cookies?.accessToken || req.header("Authorization")?.replace("Bearer ", "") 9 | 10 | // console.log(token); 11 | if (!token) { 12 | throw new ApiError(401, "Unauthorized request") 13 | } 14 | 15 | const decodedToken = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET) //it give us id,username,email,fullName 16 | 17 | const user = await User.findById(decodedToken?._id).select("-password -refreshToken") //give us whole user 18 | 19 | if (!user) { 20 | 21 | throw new ApiError(401, "Invalid Access Token") 22 | } 23 | 24 | req.user = user; 25 | next() 26 | } catch (error) { 27 | throw new ApiError(401, error?.message || "Invalid access token") 28 | } 29 | 30 | }) -------------------------------------------------------------------------------- /src/middlewares/multer.middleware.js: -------------------------------------------------------------------------------- 1 | import multer from "multer"; 2 | 3 | const storage = multer.diskStorage({ 4 | destination: function (req, file, cb) { 5 | cb(null, "./public/temp") 6 | }, 7 | filename: function (req, file, cb) { 8 | cb(null, file.originalname) 9 | } 10 | }) 11 | 12 | export const upload = multer({ 13 | storage: storage 14 | }) -------------------------------------------------------------------------------- /src/models/comment.model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | import mongooseAggregatePaginate from "mongoose-aggregate-paginate-v2"; 3 | 4 | const commentSchema = new Schema( 5 | { 6 | content: { 7 | type: String, 8 | required: true, 9 | }, 10 | video: { 11 | type: Schema.Types.ObjectId, 12 | ref: "Video", 13 | }, 14 | owner: { 15 | type: Schema.Types.ObjectId, 16 | ref: "User", 17 | }, 18 | }, 19 | { timestamps: true } 20 | ); 21 | 22 | commentSchema.plugin(mongooseAggregatePaginate); 23 | 24 | export const Comment = mongoose.model("Comment", commentSchema); 25 | -------------------------------------------------------------------------------- /src/models/like.model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const likeSchema = new Schema( 4 | { 5 | video: { 6 | type: Schema.Types.ObjectId, 7 | ref: "Video", 8 | }, 9 | comment: { 10 | type: Schema.Types.ObjectId, 11 | ref: "Comment", 12 | }, 13 | tweet: { 14 | type: Schema.Types.ObjectId, 15 | ref: "Tweet", 16 | }, 17 | likedBy: { 18 | type: Schema.Types.ObjectId, 19 | ref: "User", 20 | }, 21 | }, 22 | { timestamps: true } 23 | ); 24 | 25 | export const Like = mongoose.model("Like", likeSchema); 26 | -------------------------------------------------------------------------------- /src/models/playlist.model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const playlistSchema = new Schema( 4 | { 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | description: { 10 | type: String, 11 | required: true, 12 | }, 13 | videos: [ 14 | { 15 | type: Schema.Types.ObjectId, 16 | ref: "Video", 17 | }, 18 | ], 19 | owner: { 20 | type: Schema.Types.ObjectId, 21 | ref: "User", 22 | }, 23 | }, 24 | { timestamps: true } 25 | ); 26 | 27 | export const Playlist = mongoose.model("Playlist", playlistSchema); 28 | -------------------------------------------------------------------------------- /src/models/subscriptions.model.js: -------------------------------------------------------------------------------- 1 | import mongoose,{Schema} from "mongoose"; 2 | 3 | const subscriptionSchema=new Schema({ 4 | subscriber: { 5 | type:Schema.Types.ObjectId, //one who is subscribing 6 | ref:"User" 7 | }, 8 | channel:{ 9 | type:Schema.Types.ObjectId, //one whom 'subscriber' is subscribing 10 | ref:"User" 11 | } 12 | },{timestamps:true}) 13 | 14 | export const Subscription=mongoose.model("Subscription",subscriptionSchema) -------------------------------------------------------------------------------- /src/models/tweet.model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const tweetSchema = new Schema( 4 | { 5 | content: { 6 | type: String, 7 | required: true, 8 | }, 9 | owner: { 10 | type: Schema.Types.ObjectId, 11 | ref: "User", 12 | }, 13 | }, 14 | { timestamps: true } 15 | ); 16 | 17 | export const Tweet = mongoose.model("Tweet", tweetSchema); 18 | -------------------------------------------------------------------------------- /src/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | import jwt from "jsonwebtoken"; 3 | import bcrypt from "bcrypt"; 4 | 5 | const userSchema = new Schema( 6 | { 7 | username: { 8 | type: String, 9 | required: true, 10 | unique: true, 11 | lowercase: true, 12 | trim: true, 13 | index: true, 14 | }, 15 | email: { 16 | type: String, 17 | required: true, 18 | unique: true, 19 | lowercase: true, 20 | trim: true, 21 | }, 22 | fullName: { 23 | type: String, 24 | required: true, 25 | trim: true, 26 | index: true, 27 | }, 28 | avatar: { 29 | type: String, 30 | required: true, 31 | }, 32 | coverImage: { 33 | type: String, 34 | }, 35 | watchHistory: [ 36 | { 37 | type: Schema.Types.ObjectId, 38 | ref: "Video", 39 | }, 40 | ], 41 | password: { 42 | type: String, 43 | required: [true, "Password is required"], 44 | }, 45 | refreshToken: { 46 | type: String, 47 | }, 48 | }, 49 | { timestamps: true } 50 | ); 51 | 52 | userSchema.pre("save", async function (next) { 53 | if (!this.isModified("password")) return next(); 54 | 55 | this.password = await bcrypt.hash(this.password, 10); 56 | next(); 57 | }); 58 | 59 | userSchema.methods.isPasswordCorrect = async function (password) { 60 | return await bcrypt.compare(password, this.password); //create your own methods, here compare will give the true or false 61 | }; 62 | 63 | userSchema.methods.generateAccessToken = function () { 64 | return jwt.sign( 65 | { 66 | _id: this._id, 67 | email: this.email, 68 | username: this.username, 69 | fullName: this.fullName, 70 | }, 71 | process.env.ACCESS_TOKEN_SECRET, 72 | { 73 | expiresIn: process.env.ACCESS_TOKEN_EXPIRY, 74 | } 75 | ); 76 | }; 77 | userSchema.methods.generateRefreshToken = function () { 78 | return jwt.sign( 79 | { 80 | _id: this._id, 81 | }, 82 | process.env.REFRESH_TOKEN_SECRET, 83 | { 84 | expiresIn: process.env.REFRESH_TOKEN_EXPIRY, 85 | } 86 | ); 87 | }; 88 | 89 | export const User = mongoose.model("User", userSchema); 90 | -------------------------------------------------------------------------------- /src/models/video.model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | import mongooseAggregatePaginate from "mongoose-aggregate-paginate-v2"; 3 | 4 | const videoSchema = new Schema( 5 | { 6 | videoFile: { 7 | type: String, //cloudinary url 8 | required: true, 9 | }, 10 | thumbnail: { 11 | type: String, //cloudinary url 12 | required: true, 13 | }, 14 | title: { 15 | type: String, 16 | required: true, 17 | }, 18 | description: { 19 | type: String, 20 | required: true, 21 | }, 22 | duration: { 23 | type: Number, //cloudinary 24 | required: true, 25 | }, 26 | views: { 27 | type: Number, 28 | default: 0, 29 | }, 30 | isPublished: { 31 | type: Boolean, 32 | default: true, 33 | }, 34 | owner: { 35 | type: Schema.Types.ObjectId, 36 | ref: "User", 37 | }, 38 | }, 39 | { timestamps: true } 40 | ); 41 | 42 | videoSchema.plugin(mongooseAggregatePaginate) 43 | 44 | export const Video = mongoose.model("Video", videoSchema); 45 | -------------------------------------------------------------------------------- /src/routes/comment.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { verifyJWT } from "../middlewares/auth.middleware.js"; 3 | import { addComment, deleteComment, getVideoComments, updateComment } from "../controllers/comment.controller.js"; 4 | 5 | const router=Router() 6 | 7 | router.use(verifyJWT); 8 | 9 | router.route("/:videoId").get(getVideoComments).post(addComment); 10 | 11 | router.route("/c/:commentId").patch(updateComment).delete(deleteComment); 12 | 13 | export default router -------------------------------------------------------------------------------- /src/routes/dashboard.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import {verifyJWT} from "../middlewares/auth.middleware.js" 4 | import { getChannelStats, getChannelVideos } from '../controllers/dashboard.controller.js'; 5 | 6 | const router = Router(); 7 | 8 | router.use(verifyJWT); 9 | 10 | router.route("/stats").get(getChannelStats); 11 | router.route("/videos").get(getChannelVideos); 12 | 13 | export default router -------------------------------------------------------------------------------- /src/routes/healthcheck.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { healthCheck } from '../controllers/healthcheck.controller.js'; 3 | 4 | 5 | const router = Router(); 6 | 7 | router.route('/').get(healthCheck); 8 | 9 | export default router -------------------------------------------------------------------------------- /src/routes/like.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { 3 | getLikedVideos, 4 | toggleCommentLike, 5 | toggleVideoLike, 6 | toggleTweetLike, 7 | } from "../controllers/like.controller.js" 8 | import {verifyJWT} from "../middlewares/auth.middleware.js" 9 | 10 | const router = Router(); 11 | router.use(verifyJWT); 12 | 13 | router.route("/toggle/v/:videoId").post(toggleVideoLike); 14 | router.route("/toggle/c/:commentId").post(toggleCommentLike); 15 | router.route("/toggle/t/:tweetId").post(toggleTweetLike); 16 | router.route("/videos").get(getLikedVideos); 17 | 18 | export default router -------------------------------------------------------------------------------- /src/routes/playlist.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { verifyJWT } from "../middlewares/auth.middleware.js"; 3 | import { createPlaylist, getPlaylistById, getUserPlaylists,addVideoToPlaylist, removeVideoFromPlaylist, deletePlaylist, updatePlaylist } from "../controllers/playlist.controller.js"; 4 | 5 | const router=Router(); 6 | 7 | router.use(verifyJWT); 8 | 9 | router.route("/").post(createPlaylist) 10 | router.route("/user/:userId").get(getUserPlaylists); 11 | router.route("/:playlistId") 12 | .get(getPlaylistById) 13 | .delete(deletePlaylist) 14 | .patch(updatePlaylist) 15 | 16 | router.route("/add/:videoId/:playlistId").patch(addVideoToPlaylist); 17 | router.route("/remove/:videoId/:playlistId").patch(removeVideoFromPlaylist); 18 | 19 | export default router -------------------------------------------------------------------------------- /src/routes/subscription.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { 3 | getSubscribedChannels, 4 | getUserChannelSubscribers, 5 | toggleSubscription, 6 | } from "../controllers/subscription.controller.js"; 7 | import { verifyJWT } from "../middlewares/auth.middleware.js"; 8 | 9 | const router = Router(); 10 | router.use(verifyJWT); 11 | 12 | router 13 | .route("/c/:channelId") 14 | .get(getUserChannelSubscribers) 15 | .post(toggleSubscription); 16 | 17 | router.route("/u/:subscriberId").get(getSubscribedChannels); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /src/routes/tweet.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { verifyJWT } from "../middlewares/auth.middleware.js"; 3 | import { createTweet, deleteTweet, getUserTweets, updateTweet } from "../controllers/tweet.controller.js"; 4 | 5 | const router=Router(); 6 | router.use(verifyJWT); 7 | 8 | router.route("/").post(createTweet); 9 | 10 | router.route("/user/:userId").get(getUserTweets); 11 | 12 | router.route("/:tweetId").patch(updateTweet).delete(deleteTweet) 13 | 14 | export default router -------------------------------------------------------------------------------- /src/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { 3 | changeCurrentPassword, 4 | getCurrentUser, 5 | getUserChannelProfile, 6 | getWatchHistory, 7 | loginUser, 8 | logoutUser, 9 | refreshAccessToken, 10 | registerUser, 11 | updateAccountDetails, 12 | updateAvatar, 13 | updateCoverImage, 14 | } from "../controllers/user.controller.js"; 15 | import { upload } from "../middlewares/multer.middleware.js"; 16 | import { verifyJWT } from "../middlewares/auth.middleware.js"; 17 | 18 | const router = Router(); 19 | 20 | router.route("/register").post( 21 | upload.fields([ 22 | { 23 | name: "avatar", 24 | maxCount: 1, 25 | }, 26 | { 27 | name: "coverImage", 28 | maxCount: 1, 29 | }, 30 | ]), 31 | registerUser 32 | ); 33 | 34 | router.route("/login").post(loginUser); 35 | 36 | //secured routes 37 | router.route("/logout").post(verifyJWT, logoutUser); 38 | 39 | router.route("/refresh-token").post(refreshAccessToken); 40 | 41 | router.route("/change-password").post(verifyJWT, changeCurrentPassword); 42 | 43 | router.route("/current-user").get(verifyJWT, getCurrentUser); 44 | 45 | router.route("/update-account").patch(verifyJWT, updateAccountDetails); 46 | 47 | router.route("/avatar").patch(verifyJWT, upload.single("avatar"), updateAvatar); 48 | router.route("/cover-image").patch(verifyJWT, upload.single("coverImage"), updateCoverImage); 49 | 50 | router.route("/c/:username").get(verifyJWT, getUserChannelProfile); 51 | router.route("/history").get(verifyJWT, getWatchHistory); 52 | 53 | export default router; 54 | -------------------------------------------------------------------------------- /src/routes/video.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { verifyJWT } from "../middlewares/auth.middleware.js"; 3 | import { upload } from "../middlewares/multer.middleware.js"; 4 | import { deleteVideo, getAllVideos, getVideoById, publishAVideo, togglePublishStatus, updateVideo } from "../controllers/video.controller.js"; 5 | 6 | const router = Router(); 7 | router.use(verifyJWT); 8 | 9 | router.route("/").post( 10 | upload.fields([ 11 | { 12 | name: "videoFile", 13 | maxCount: 1, 14 | }, 15 | { 16 | name: "thumbnail", 17 | maxCount: 1, 18 | }, 19 | ]), 20 | publishAVideo 21 | ) 22 | 23 | router.route("/:videoId").get(getVideoById).delete(deleteVideo) 24 | 25 | router.route("/:videoId").patch(upload.single("thumbnail"),updateVideo) 26 | 27 | router.route("/toggle/publish/:videoId").patch(togglePublishStatus) 28 | 29 | router.route("/").get(getAllVideos) 30 | 31 | export default router -------------------------------------------------------------------------------- /src/utils/.prettierignore: -------------------------------------------------------------------------------- 1 | *.env 2 | .env 3 | 4 | /node_modules 5 | /.dist 6 | /.vscode -------------------------------------------------------------------------------- /src/utils/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "bracketSpacing": true, 4 | "tabWidth": 2, 5 | "semi": true, 6 | "trailingComma": "es5" 7 | } -------------------------------------------------------------------------------- /src/utils/ApiError.js: -------------------------------------------------------------------------------- 1 | class ApiError extends Error { 2 | constructor(statusCode,message="Something went wrong",errors=[],stack=""){ 3 | super(message) 4 | this.statusCode=statusCode 5 | this.data=null, 6 | this.message=message 7 | this.success=false; 8 | this.errors=errors 9 | 10 | if(stack){ 11 | this.stack=stack 12 | }else{ 13 | Error.captureStackTrace(this,this.constructor) 14 | } 15 | } 16 | } 17 | 18 | export {ApiError} -------------------------------------------------------------------------------- /src/utils/ApiResponse.js: -------------------------------------------------------------------------------- 1 | class ApiResponse { 2 | constructor(statusCode, data, message = "Success"){ 3 | this.statusCode = statusCode 4 | this.data = data 5 | this.message = message 6 | this.success = statusCode < 400 7 | } 8 | } 9 | 10 | export { ApiResponse } -------------------------------------------------------------------------------- /src/utils/asyncHandler.js: -------------------------------------------------------------------------------- 1 | const asyncHandler=(requestHandler)=>{ 2 | return (req,res,next)=>{ 3 | Promise.resolve(requestHandler(req,res,next)).catch((err)=>next(err)) 4 | } 5 | } 6 | 7 | export {asyncHandler} 8 | 9 | 10 | // const asyncHandler =(fn)=> async (req,res,next)=>{ 11 | // try { 12 | // await fn(req,res,next) 13 | // } catch (error) { 14 | // res.status(err.code || 500).json({ 15 | // success: false, 16 | // message:err.message 17 | // }) 18 | // } 19 | // } -------------------------------------------------------------------------------- /src/utils/cloudinary.js: -------------------------------------------------------------------------------- 1 | import {v2 as cloudinary} from "cloudinary" 2 | import fs from "fs" 3 | 4 | 5 | cloudinary.config({ 6 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME, 7 | api_key: process.env.CLOUDINARY_API_KEY, 8 | api_secret: process.env.CLOUDINARY_API_SECRET 9 | }); 10 | 11 | const uploadOnCloudinary = async (localFilePath) => { 12 | try { 13 | if (!localFilePath) return null 14 | //upload the file on cloudinary 15 | const response = await cloudinary.uploader.upload(localFilePath, { 16 | resource_type: "auto" 17 | }) 18 | // file has been uploaded successfull 19 | //console.log("file is uploaded on cloudinary ", response.url); 20 | fs.unlinkSync(localFilePath) 21 | return response; 22 | 23 | } catch (error) { 24 | fs.unlinkSync(localFilePath) // remove the locally saved temporary file as the upload operation got failed 25 | return null; 26 | } 27 | } 28 | 29 | 30 | 31 | export {uploadOnCloudinary} --------------------------------------------------------------------------------