diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..85320d4f Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 4b37c64b..d5730683 100644 --- a/.gitignore +++ b/.gitignore @@ -117,5 +117,11 @@ dist .yarn/install-state.gz .pnp.* +<<<<<<< HEAD # End of https://mrkandreev.name/snippets/gitignore-generator/#Node .DS_Store +======= + + +node_modules +>>>>>>> myrepo/main diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b28b04f6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ + + + diff --git a/package-lock.json b/package-lock.json index b23efc95..4626513c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "name": "chai-backend", "version": "1.0.0", "lockfileVersion": 3, @@ -2137,3 +2138,3740 @@ } } } +======= + + "name": "chai-backend", + + "version": "1.0.0", + + "lockfileVersion": 3, + + "requires": true, + + "packages": { + + "": { + + "name": "chai-backend", + + "version": "1.0.0", + + "license": "ISC", + + "dependencies": { + + "bcrypt": "^6.0.0", + + "cloudinary": "^2.7.0", + + "cookie-parser": "^1.4.7", + + "cors": "^2.8.5", + + "dotenv": "^17.2.2", + + "express": "^5.1.0", + + "jsonwebtoken": "^9.0.2", + + "mongodb": "^6.19.0", + + "mongoose": "^8.18.0", + + "mongoose-aggregate-paginate-v2": "^1.1.4", + + "multer": "^2.0.2" + + }, + + "devDependencies": { + + "nodemon": "^3.1.10", + + "prettier": "^3.6.2" + + } + + }, + + "node_modules/@mongodb-js/saslprep": { + + "version": "1.3.0", + + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + + "license": "MIT", + + "dependencies": { + + "sparse-bitfield": "^3.0.3" + + } + + }, + + "node_modules/@types/webidl-conversions": { + + "version": "7.0.3", + + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + + "license": "MIT" + + }, + + "node_modules/@types/whatwg-url": { + + "version": "11.0.5", + + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + + "license": "MIT", + + "dependencies": { + + "@types/webidl-conversions": "*" + + } + + }, + + "node_modules/accepts": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + + "license": "MIT", + + "dependencies": { + + "mime-types": "^3.0.0", + + "negotiator": "^1.0.0" + + }, + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/anymatch": { + + "version": "3.1.3", + + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + + "dev": true, + + "license": "ISC", + + "dependencies": { + + "normalize-path": "^3.0.0", + + "picomatch": "^2.0.4" + + }, + + "engines": { + + "node": ">= 8" + + } + + }, + + "node_modules/append-field": { + + "version": "1.0.0", + + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + + "license": "MIT" + + }, + + "node_modules/balanced-match": { + + "version": "1.0.2", + + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + + "dev": true, + + "license": "MIT" + + }, + + "node_modules/bcrypt": { + + "version": "6.0.0", + + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + + "hasInstallScript": true, + + "license": "MIT", + + "dependencies": { + + "node-addon-api": "^8.3.0", + + "node-gyp-build": "^4.8.4" + + }, + + "engines": { + + "node": ">= 18" + + } + + }, + + "node_modules/binary-extensions": { + + "version": "2.3.0", + + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + + "dev": true, + + "license": "MIT", + + "engines": { + + "node": ">=8" + + }, + + "funding": { + + "url": "https://github.com/sponsors/sindresorhus" + + } + + }, + + "node_modules/body-parser": { + + "version": "2.2.0", + + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + + "license": "MIT", + + "dependencies": { + + "bytes": "^3.1.2", + + "content-type": "^1.0.5", + + "debug": "^4.4.0", + + "http-errors": "^2.0.0", + + "iconv-lite": "^0.6.3", + + "on-finished": "^2.4.1", + + "qs": "^6.14.0", + + "raw-body": "^3.0.0", + + "type-is": "^2.0.0" + + }, + + "engines": { + + "node": ">=18" + + } + + }, + + "node_modules/brace-expansion": { + + "version": "1.1.12", + + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "balanced-match": "^1.0.0", + + "concat-map": "0.0.1" + + } + + }, + + "node_modules/braces": { + + "version": "3.0.3", + + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "fill-range": "^7.1.1" + + }, + + "engines": { + + "node": ">=8" + + } + + }, + + "node_modules/bson": { + + "version": "6.10.4", + + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + + "license": "Apache-2.0", + + "engines": { + + "node": ">=16.20.1" + + } + + }, + + "node_modules/buffer-equal-constant-time": { + + "version": "1.0.1", + + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + + "license": "BSD-3-Clause" + + }, + + "node_modules/buffer-from": { + + "version": "1.1.2", + + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + + "license": "MIT" + + }, + + "node_modules/busboy": { + + "version": "1.6.0", + + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + + "dependencies": { + + "streamsearch": "^1.1.0" + + }, + + "engines": { + + "node": ">=10.16.0" + + } + + }, + + "node_modules/bytes": { + + "version": "3.1.2", + + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/call-bind-apply-helpers": { + + "version": "1.0.2", + + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + + "license": "MIT", + + "dependencies": { + + "es-errors": "^1.3.0", + + "function-bind": "^1.1.2" + + }, + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/call-bound": { + + "version": "1.0.4", + + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + + "license": "MIT", + + "dependencies": { + + "call-bind-apply-helpers": "^1.0.2", + + "get-intrinsic": "^1.3.0" + + }, + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/chokidar": { + + "version": "3.6.0", + + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "anymatch": "~3.1.2", + + "braces": "~3.0.2", + + "glob-parent": "~5.1.2", + + "is-binary-path": "~2.1.0", + + "is-glob": "~4.0.1", + + "normalize-path": "~3.0.0", + + "readdirp": "~3.6.0" + + }, + + "engines": { + + "node": ">= 8.10.0" + + }, + + "funding": { + + "url": "https://paulmillr.com/funding/" + + }, + + "optionalDependencies": { + + "fsevents": "~2.3.2" + + } + + }, + + "node_modules/cloudinary": { + + "version": "2.7.0", + + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.7.0.tgz", + + "integrity": "sha512-qrqDn31+qkMCzKu1GfRpzPNAO86jchcNwEHCUiqvPHNSFqu7FTNF9FuAkBUyvM1CFFgFPu64NT0DyeREwLwK0w==", + + "license": "MIT", + + "dependencies": { + + "lodash": "^4.17.21", + + "q": "^1.5.1" + + }, + + "engines": { + + "node": ">=9" + + } + + }, + + "node_modules/concat-map": { + + "version": "0.0.1", + + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + + "dev": true, + + "license": "MIT" + + }, + + "node_modules/concat-stream": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + + "engines": [ + + "node >= 6.0" + + ], + + "license": "MIT", + + "dependencies": { + + "buffer-from": "^1.0.0", + + "inherits": "^2.0.3", + + "readable-stream": "^3.0.2", + + "typedarray": "^0.0.6" + + } + + }, + + "node_modules/content-disposition": { + + "version": "1.0.0", + + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + + "license": "MIT", + + "dependencies": { + + "safe-buffer": "5.2.1" + + }, + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/content-type": { + + "version": "1.0.5", + + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/cookie": { + + "version": "0.7.2", + + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/cookie-parser": { + + "version": "1.4.7", + + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + + "license": "MIT", + + "dependencies": { + + "cookie": "0.7.2", + + "cookie-signature": "1.0.6" + + }, + + "engines": { + + "node": ">= 0.8.0" + + } + + }, + + "node_modules/cookie-parser/node_modules/cookie-signature": { + + "version": "1.0.6", + + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + + "license": "MIT" + + }, + + "node_modules/cookie-signature": { + + "version": "1.2.2", + + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + + "license": "MIT", + + "engines": { + + "node": ">=6.6.0" + + } + + }, + + "node_modules/cors": { + + "version": "2.8.5", + + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + + "license": "MIT", + + "dependencies": { + + "object-assign": "^4", + + "vary": "^1" + + }, + + "engines": { + + "node": ">= 0.10" + + } + + }, + + "node_modules/debug": { + + "version": "4.4.1", + + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + + "license": "MIT", + + "dependencies": { + + "ms": "^2.1.3" + + }, + + "engines": { + + "node": ">=6.0" + + }, + + "peerDependenciesMeta": { + + "supports-color": { + + "optional": true + + } + + } + + }, + + "node_modules/depd": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/dotenv": { + + "version": "17.2.2", + + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + + "license": "BSD-2-Clause", + + "engines": { + + "node": ">=12" + + }, + + "funding": { + + "url": "https://dotenvx.com" + + } + + }, + + "node_modules/dunder-proto": { + + "version": "1.0.1", + + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + + "license": "MIT", + + "dependencies": { + + "call-bind-apply-helpers": "^1.0.1", + + "es-errors": "^1.3.0", + + "gopd": "^1.2.0" + + }, + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/ecdsa-sig-formatter": { + + "version": "1.0.11", + + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + + "license": "Apache-2.0", + + "dependencies": { + + "safe-buffer": "^5.0.1" + + } + + }, + + "node_modules/ee-first": { + + "version": "1.1.1", + + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + + "license": "MIT" + + }, + + "node_modules/encodeurl": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/es-define-property": { + + "version": "1.0.1", + + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/es-errors": { + + "version": "1.3.0", + + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/es-object-atoms": { + + "version": "1.1.1", + + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + + "license": "MIT", + + "dependencies": { + + "es-errors": "^1.3.0" + + }, + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/escape-html": { + + "version": "1.0.3", + + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + + "license": "MIT" + + }, + + "node_modules/etag": { + + "version": "1.8.1", + + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/express": { + + "version": "5.1.0", + + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + + "license": "MIT", + + "dependencies": { + + "accepts": "^2.0.0", + + "body-parser": "^2.2.0", + + "content-disposition": "^1.0.0", + + "content-type": "^1.0.5", + + "cookie": "^0.7.1", + + "cookie-signature": "^1.2.1", + + "debug": "^4.4.0", + + "encodeurl": "^2.0.0", + + "escape-html": "^1.0.3", + + "etag": "^1.8.1", + + "finalhandler": "^2.1.0", + + "fresh": "^2.0.0", + + "http-errors": "^2.0.0", + + "merge-descriptors": "^2.0.0", + + "mime-types": "^3.0.0", + + "on-finished": "^2.4.1", + + "once": "^1.4.0", + + "parseurl": "^1.3.3", + + "proxy-addr": "^2.0.7", + + "qs": "^6.14.0", + + "range-parser": "^1.2.1", + + "router": "^2.2.0", + + "send": "^1.1.0", + + "serve-static": "^2.2.0", + + "statuses": "^2.0.1", + + "type-is": "^2.0.1", + + "vary": "^1.1.2" + + }, + + "engines": { + + "node": ">= 18" + + }, + + "funding": { + + "type": "opencollective", + + "url": "https://opencollective.com/express" + + } + + }, + + "node_modules/fill-range": { + + "version": "7.1.1", + + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "to-regex-range": "^5.0.1" + + }, + + "engines": { + + "node": ">=8" + + } + + }, + + "node_modules/finalhandler": { + + "version": "2.1.0", + + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + + "license": "MIT", + + "dependencies": { + + "debug": "^4.4.0", + + "encodeurl": "^2.0.0", + + "escape-html": "^1.0.3", + + "on-finished": "^2.4.1", + + "parseurl": "^1.3.3", + + "statuses": "^2.0.1" + + }, + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/forwarded": { + + "version": "0.2.0", + + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/fresh": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/fsevents": { + + "version": "2.3.3", + + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + + "dev": true, + + "hasInstallScript": true, + + "license": "MIT", + + "optional": true, + + "os": [ + + "darwin" + + ], + + "engines": { + + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + + } + + }, + + "node_modules/function-bind": { + + "version": "1.1.2", + + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + + "license": "MIT", + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/get-intrinsic": { + + "version": "1.3.0", + + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + + "license": "MIT", + + "dependencies": { + + "call-bind-apply-helpers": "^1.0.2", + + "es-define-property": "^1.0.1", + + "es-errors": "^1.3.0", + + "es-object-atoms": "^1.1.1", + + "function-bind": "^1.1.2", + + "get-proto": "^1.0.1", + + "gopd": "^1.2.0", + + "has-symbols": "^1.1.0", + + "hasown": "^2.0.2", + + "math-intrinsics": "^1.1.0" + + }, + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/get-proto": { + + "version": "1.0.1", + + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + + "license": "MIT", + + "dependencies": { + + "dunder-proto": "^1.0.1", + + "es-object-atoms": "^1.0.0" + + }, + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/glob-parent": { + + "version": "5.1.2", + + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + + "dev": true, + + "license": "ISC", + + "dependencies": { + + "is-glob": "^4.0.1" + + }, + + "engines": { + + "node": ">= 6" + + } + + }, + + "node_modules/gopd": { + + "version": "1.2.0", + + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/has-flag": { + + "version": "3.0.0", + + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + + "dev": true, + + "license": "MIT", + + "engines": { + + "node": ">=4" + + } + + }, + + "node_modules/has-symbols": { + + "version": "1.1.0", + + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/hasown": { + + "version": "2.0.2", + + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + + "license": "MIT", + + "dependencies": { + + "function-bind": "^1.1.2" + + }, + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/http-errors": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + + "license": "MIT", + + "dependencies": { + + "depd": "2.0.0", + + "inherits": "2.0.4", + + "setprototypeof": "1.2.0", + + "statuses": "2.0.1", + + "toidentifier": "1.0.1" + + }, + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/http-errors/node_modules/statuses": { + + "version": "2.0.1", + + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/iconv-lite": { + + "version": "0.6.3", + + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + + "license": "MIT", + + "dependencies": { + + "safer-buffer": ">= 2.1.2 < 3.0.0" + + }, + + "engines": { + + "node": ">=0.10.0" + + } + + }, + + "node_modules/ignore-by-default": { + + "version": "1.0.1", + + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + + "dev": true, + + "license": "ISC" + + }, + + "node_modules/inherits": { + + "version": "2.0.4", + + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + + "license": "ISC" + + }, + + "node_modules/ipaddr.js": { + + "version": "1.9.1", + + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.10" + + } + + }, + + "node_modules/is-binary-path": { + + "version": "2.1.0", + + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "binary-extensions": "^2.0.0" + + }, + + "engines": { + + "node": ">=8" + + } + + }, + + "node_modules/is-extglob": { + + "version": "2.1.1", + + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + + "dev": true, + + "license": "MIT", + + "engines": { + + "node": ">=0.10.0" + + } + + }, + + "node_modules/is-glob": { + + "version": "4.0.3", + + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "is-extglob": "^2.1.1" + + }, + + "engines": { + + "node": ">=0.10.0" + + } + + }, + + "node_modules/is-number": { + + "version": "7.0.0", + + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + + "dev": true, + + "license": "MIT", + + "engines": { + + "node": ">=0.12.0" + + } + + }, + + "node_modules/is-promise": { + + "version": "4.0.0", + + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + + "license": "MIT" + + }, + + "node_modules/jsonwebtoken": { + + "version": "9.0.2", + + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + + "license": "MIT", + + "dependencies": { + + "jws": "^3.2.2", + + "lodash.includes": "^4.3.0", + + "lodash.isboolean": "^3.0.3", + + "lodash.isinteger": "^4.0.4", + + "lodash.isnumber": "^3.0.3", + + "lodash.isplainobject": "^4.0.6", + + "lodash.isstring": "^4.0.1", + + "lodash.once": "^4.0.0", + + "ms": "^2.1.1", + + "semver": "^7.5.4" + + }, + + "engines": { + + "node": ">=12", + + "npm": ">=6" + + } + + }, + + "node_modules/jwa": { + + "version": "1.4.2", + + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + + "license": "MIT", + + "dependencies": { + + "buffer-equal-constant-time": "^1.0.1", + + "ecdsa-sig-formatter": "1.0.11", + + "safe-buffer": "^5.0.1" + + } + + }, + + "node_modules/jws": { + + "version": "3.2.2", + + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + + "license": "MIT", + + "dependencies": { + + "jwa": "^1.4.1", + + "safe-buffer": "^5.0.1" + + } + + }, + + "node_modules/kareem": { + + "version": "2.6.3", + + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + + "license": "Apache-2.0", + + "engines": { + + "node": ">=12.0.0" + + } + + }, + + "node_modules/lodash": { + + "version": "4.17.21", + + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + + "license": "MIT" + + }, + + "node_modules/lodash.includes": { + + "version": "4.3.0", + + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + + "license": "MIT" + + }, + + "node_modules/lodash.isboolean": { + + "version": "3.0.3", + + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + + "license": "MIT" + + }, + + "node_modules/lodash.isinteger": { + + "version": "4.0.4", + + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + + "license": "MIT" + + }, + + "node_modules/lodash.isnumber": { + + "version": "3.0.3", + + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + + "license": "MIT" + + }, + + "node_modules/lodash.isplainobject": { + + "version": "4.0.6", + + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + + "license": "MIT" + + }, + + "node_modules/lodash.isstring": { + + "version": "4.0.1", + + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + + "license": "MIT" + + }, + + "node_modules/lodash.once": { + + "version": "4.1.1", + + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + + "license": "MIT" + + }, + + "node_modules/math-intrinsics": { + + "version": "1.1.0", + + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.4" + + } + + }, + + "node_modules/media-typer": { + + "version": "1.1.0", + + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/memory-pager": { + + "version": "1.5.0", + + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + + "license": "MIT" + + }, + + "node_modules/merge-descriptors": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + + "license": "MIT", + + "engines": { + + "node": ">=18" + + }, + + "funding": { + + "url": "https://github.com/sponsors/sindresorhus" + + } + + }, + + "node_modules/mime-db": { + + "version": "1.54.0", + + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/mime-types": { + + "version": "3.0.1", + + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + + "license": "MIT", + + "dependencies": { + + "mime-db": "^1.54.0" + + }, + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/minimatch": { + + "version": "3.1.2", + + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + + "dev": true, + + "license": "ISC", + + "dependencies": { + + "brace-expansion": "^1.1.7" + + }, + + "engines": { + + "node": "*" + + } + + }, + + "node_modules/minimist": { + + "version": "1.2.8", + + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + + "license": "MIT", + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/mkdirp": { + + "version": "0.5.6", + + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + + "license": "MIT", + + "dependencies": { + + "minimist": "^1.2.6" + + }, + + "bin": { + + "mkdirp": "bin/cmd.js" + + } + + }, + + "node_modules/mongodb": { + + "version": "6.19.0", + + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.19.0.tgz", + + "integrity": "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw==", + + "license": "Apache-2.0", + + "dependencies": { + + "@mongodb-js/saslprep": "^1.1.9", + + "bson": "^6.10.4", + + "mongodb-connection-string-url": "^3.0.0" + + }, + + "engines": { + + "node": ">=16.20.1" + + }, + + "peerDependencies": { + + "@aws-sdk/credential-providers": "^3.188.0", + + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + + "gcp-metadata": "^5.2.0", + + "kerberos": "^2.0.1", + + "mongodb-client-encryption": ">=6.0.0 <7", + + "snappy": "^7.3.2", + + "socks": "^2.7.1" + + }, + + "peerDependenciesMeta": { + + "@aws-sdk/credential-providers": { + + "optional": true + + }, + + "@mongodb-js/zstd": { + + "optional": true + + }, + + "gcp-metadata": { + + "optional": true + + }, + + "kerberos": { + + "optional": true + + }, + + "mongodb-client-encryption": { + + "optional": true + + }, + + "snappy": { + + "optional": true + + }, + + "socks": { + + "optional": true + + } + + } + + }, + + "node_modules/mongodb-connection-string-url": { + + "version": "3.0.2", + + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + + "license": "Apache-2.0", + + "dependencies": { + + "@types/whatwg-url": "^11.0.2", + + "whatwg-url": "^14.1.0 || ^13.0.0" + + } + + }, + + "node_modules/mongoose": { + + "version": "8.18.0", + + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.0.tgz", + + "integrity": "sha512-3TixPihQKBdyaYDeJqRjzgb86KbilEH07JmzV8SoSjgoskNTpa6oTBmDxeoF9p8YnWQoz7shnCyPkSV/48y3yw==", + + "license": "MIT", + + "dependencies": { + + "bson": "^6.10.4", + + "kareem": "2.6.3", + + "mongodb": "~6.18.0", + + "mpath": "0.9.0", + + "mquery": "5.0.0", + + "ms": "2.1.3", + + "sift": "17.1.3" + + }, + + "engines": { + + "node": ">=16.20.1" + + }, + + "funding": { + + "type": "opencollective", + + "url": "https://opencollective.com/mongoose" + + } + + }, + + "node_modules/mongoose-aggregate-paginate-v2": { + + "version": "1.1.4", + + "resolved": "https://registry.npmjs.org/mongoose-aggregate-paginate-v2/-/mongoose-aggregate-paginate-v2-1.1.4.tgz", + + "integrity": "sha512-CdQIar3wlS7g0H6JjSJIZzvzz05vFc+Xy9SosJmj46l3xIomgl3ZjDn/n4vDpEei9RBawgUk5zGTIP6fMKdMdA==", + + "license": "MIT", + + "engines": { + + "node": ">=4.0.0" + + } + + }, + + "node_modules/mongoose/node_modules/mongodb": { + + "version": "6.18.0", + + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", + + "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", + + "license": "Apache-2.0", + + "dependencies": { + + "@mongodb-js/saslprep": "^1.1.9", + + "bson": "^6.10.4", + + "mongodb-connection-string-url": "^3.0.0" + + }, + + "engines": { + + "node": ">=16.20.1" + + }, + + "peerDependencies": { + + "@aws-sdk/credential-providers": "^3.188.0", + + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + + "gcp-metadata": "^5.2.0", + + "kerberos": "^2.0.1", + + "mongodb-client-encryption": ">=6.0.0 <7", + + "snappy": "^7.2.2", + + "socks": "^2.7.1" + + }, + + "peerDependenciesMeta": { + + "@aws-sdk/credential-providers": { + + "optional": true + + }, + + "@mongodb-js/zstd": { + + "optional": true + + }, + + "gcp-metadata": { + + "optional": true + + }, + + "kerberos": { + + "optional": true + + }, + + "mongodb-client-encryption": { + + "optional": true + + }, + + "snappy": { + + "optional": true + + }, + + "socks": { + + "optional": true + + } + + } + + }, + + "node_modules/mpath": { + + "version": "0.9.0", + + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + + "license": "MIT", + + "engines": { + + "node": ">=4.0.0" + + } + + }, + + "node_modules/mquery": { + + "version": "5.0.0", + + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + + "license": "MIT", + + "dependencies": { + + "debug": "4.x" + + }, + + "engines": { + + "node": ">=14.0.0" + + } + + }, + + "node_modules/ms": { + + "version": "2.1.3", + + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + + "license": "MIT" + + }, + + "node_modules/multer": { + + "version": "2.0.2", + + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + + "license": "MIT", + + "dependencies": { + + "append-field": "^1.0.0", + + "busboy": "^1.6.0", + + "concat-stream": "^2.0.0", + + "mkdirp": "^0.5.6", + + "object-assign": "^4.1.1", + + "type-is": "^1.6.18", + + "xtend": "^4.0.2" + + }, + + "engines": { + + "node": ">= 10.16.0" + + } + + }, + + "node_modules/multer/node_modules/media-typer": { + + "version": "0.3.0", + + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/multer/node_modules/mime-db": { + + "version": "1.52.0", + + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/multer/node_modules/mime-types": { + + "version": "2.1.35", + + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + + "license": "MIT", + + "dependencies": { + + "mime-db": "1.52.0" + + }, + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/multer/node_modules/type-is": { + + "version": "1.6.18", + + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + + "license": "MIT", + + "dependencies": { + + "media-typer": "0.3.0", + + "mime-types": "~2.1.24" + + }, + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/negotiator": { + + "version": "1.0.0", + + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/node-addon-api": { + + "version": "8.5.0", + + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + + "license": "MIT", + + "engines": { + + "node": "^18 || ^20 || >= 21" + + } + + }, + + "node_modules/node-gyp-build": { + + "version": "4.8.4", + + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + + "license": "MIT", + + "bin": { + + "node-gyp-build": "bin.js", + + "node-gyp-build-optional": "optional.js", + + "node-gyp-build-test": "build-test.js" + + } + + }, + + "node_modules/nodemon": { + + "version": "3.1.10", + + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "chokidar": "^3.5.2", + + "debug": "^4", + + "ignore-by-default": "^1.0.1", + + "minimatch": "^3.1.2", + + "pstree.remy": "^1.1.8", + + "semver": "^7.5.3", + + "simple-update-notifier": "^2.0.0", + + "supports-color": "^5.5.0", + + "touch": "^3.1.0", + + "undefsafe": "^2.0.5" + + }, + + "bin": { + + "nodemon": "bin/nodemon.js" + + }, + + "engines": { + + "node": ">=10" + + }, + + "funding": { + + "type": "opencollective", + + "url": "https://opencollective.com/nodemon" + + } + + }, + + "node_modules/normalize-path": { + + "version": "3.0.0", + + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + + "dev": true, + + "license": "MIT", + + "engines": { + + "node": ">=0.10.0" + + } + + }, + + "node_modules/object-assign": { + + "version": "4.1.1", + + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + + "license": "MIT", + + "engines": { + + "node": ">=0.10.0" + + } + + }, + + "node_modules/object-inspect": { + + "version": "1.13.4", + + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/on-finished": { + + "version": "2.4.1", + + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + + "license": "MIT", + + "dependencies": { + + "ee-first": "1.1.1" + + }, + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/once": { + + "version": "1.4.0", + + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + + "license": "ISC", + + "dependencies": { + + "wrappy": "1" + + } + + }, + + "node_modules/parseurl": { + + "version": "1.3.3", + + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/path-to-regexp": { + + "version": "8.3.0", + + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + + "license": "MIT", + + "funding": { + + "type": "opencollective", + + "url": "https://opencollective.com/express" + + } + + }, + + "node_modules/picomatch": { + + "version": "2.3.1", + + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + + "dev": true, + + "license": "MIT", + + "engines": { + + "node": ">=8.6" + + }, + + "funding": { + + "url": "https://github.com/sponsors/jonschlinkert" + + } + + }, + + "node_modules/prettier": { + + "version": "3.6.2", + + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + + "dev": true, + + "license": "MIT", + + "bin": { + + "prettier": "bin/prettier.cjs" + + }, + + "engines": { + + "node": ">=14" + + }, + + "funding": { + + "url": "https://github.com/prettier/prettier?sponsor=1" + + } + + }, + + "node_modules/proxy-addr": { + + "version": "2.0.7", + + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + + "license": "MIT", + + "dependencies": { + + "forwarded": "0.2.0", + + "ipaddr.js": "1.9.1" + + }, + + "engines": { + + "node": ">= 0.10" + + } + + }, + + "node_modules/pstree.remy": { + + "version": "1.1.8", + + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + + "dev": true, + + "license": "MIT" + + }, + + "node_modules/punycode": { + + "version": "2.3.1", + + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + + "license": "MIT", + + "engines": { + + "node": ">=6" + + } + + }, + + "node_modules/q": { + + "version": "1.5.1", + + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + + "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)", + + "license": "MIT", + + "engines": { + + "node": ">=0.6.0", + + "teleport": ">=0.2.0" + + } + + }, + + "node_modules/qs": { + + "version": "6.14.0", + + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + + "license": "BSD-3-Clause", + + "dependencies": { + + "side-channel": "^1.1.0" + + }, + + "engines": { + + "node": ">=0.6" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/range-parser": { + + "version": "1.2.1", + + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/raw-body": { + + "version": "3.0.1", + + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + + "license": "MIT", + + "dependencies": { + + "bytes": "3.1.2", + + "http-errors": "2.0.0", + + "iconv-lite": "0.7.0", + + "unpipe": "1.0.0" + + }, + + "engines": { + + "node": ">= 0.10" + + } + + }, + + "node_modules/raw-body/node_modules/iconv-lite": { + + "version": "0.7.0", + + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + + "license": "MIT", + + "dependencies": { + + "safer-buffer": ">= 2.1.2 < 3.0.0" + + }, + + "engines": { + + "node": ">=0.10.0" + + }, + + "funding": { + + "type": "opencollective", + + "url": "https://opencollective.com/express" + + } + + }, + + "node_modules/readable-stream": { + + "version": "3.6.2", + + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + + "license": "MIT", + + "dependencies": { + + "inherits": "^2.0.3", + + "string_decoder": "^1.1.1", + + "util-deprecate": "^1.0.1" + + }, + + "engines": { + + "node": ">= 6" + + } + + }, + + "node_modules/readdirp": { + + "version": "3.6.0", + + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "picomatch": "^2.2.1" + + }, + + "engines": { + + "node": ">=8.10.0" + + } + + }, + + "node_modules/router": { + + "version": "2.2.0", + + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + + "license": "MIT", + + "dependencies": { + + "debug": "^4.4.0", + + "depd": "^2.0.0", + + "is-promise": "^4.0.0", + + "parseurl": "^1.3.3", + + "path-to-regexp": "^8.0.0" + + }, + + "engines": { + + "node": ">= 18" + + } + + }, + + "node_modules/safe-buffer": { + + "version": "5.2.1", + + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + + "funding": [ + + { + + "type": "github", + + "url": "https://github.com/sponsors/feross" + + }, + + { + + "type": "patreon", + + "url": "https://www.patreon.com/feross" + + }, + + { + + "type": "consulting", + + "url": "https://feross.org/support" + + } + + ], + + "license": "MIT" + + }, + + "node_modules/safer-buffer": { + + "version": "2.1.2", + + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + + "license": "MIT" + + }, + + "node_modules/semver": { + + "version": "7.7.2", + + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + + "license": "ISC", + + "bin": { + + "semver": "bin/semver.js" + + }, + + "engines": { + + "node": ">=10" + + } + + }, + + "node_modules/send": { + + "version": "1.2.0", + + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + + "license": "MIT", + + "dependencies": { + + "debug": "^4.3.5", + + "encodeurl": "^2.0.0", + + "escape-html": "^1.0.3", + + "etag": "^1.8.1", + + "fresh": "^2.0.0", + + "http-errors": "^2.0.0", + + "mime-types": "^3.0.1", + + "ms": "^2.1.3", + + "on-finished": "^2.4.1", + + "range-parser": "^1.2.1", + + "statuses": "^2.0.1" + + }, + + "engines": { + + "node": ">= 18" + + } + + }, + + "node_modules/serve-static": { + + "version": "2.2.0", + + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + + "license": "MIT", + + "dependencies": { + + "encodeurl": "^2.0.0", + + "escape-html": "^1.0.3", + + "parseurl": "^1.3.3", + + "send": "^1.2.0" + + }, + + "engines": { + + "node": ">= 18" + + } + + }, + + "node_modules/setprototypeof": { + + "version": "1.2.0", + + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + + "license": "ISC" + + }, + + "node_modules/side-channel": { + + "version": "1.1.0", + + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + + "license": "MIT", + + "dependencies": { + + "es-errors": "^1.3.0", + + "object-inspect": "^1.13.3", + + "side-channel-list": "^1.0.0", + + "side-channel-map": "^1.0.1", + + "side-channel-weakmap": "^1.0.2" + + }, + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/side-channel-list": { + + "version": "1.0.0", + + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + + "license": "MIT", + + "dependencies": { + + "es-errors": "^1.3.0", + + "object-inspect": "^1.13.3" + + }, + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/side-channel-map": { + + "version": "1.0.1", + + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + + "license": "MIT", + + "dependencies": { + + "call-bound": "^1.0.2", + + "es-errors": "^1.3.0", + + "get-intrinsic": "^1.2.5", + + "object-inspect": "^1.13.3" + + }, + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/side-channel-weakmap": { + + "version": "1.0.2", + + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + + "license": "MIT", + + "dependencies": { + + "call-bound": "^1.0.2", + + "es-errors": "^1.3.0", + + "get-intrinsic": "^1.2.5", + + "object-inspect": "^1.13.3", + + "side-channel-map": "^1.0.1" + + }, + + "engines": { + + "node": ">= 0.4" + + }, + + "funding": { + + "url": "https://github.com/sponsors/ljharb" + + } + + }, + + "node_modules/sift": { + + "version": "17.1.3", + + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + + "license": "MIT" + + }, + + "node_modules/simple-update-notifier": { + + "version": "2.0.0", + + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "semver": "^7.5.3" + + }, + + "engines": { + + "node": ">=10" + + } + + }, + + "node_modules/sparse-bitfield": { + + "version": "3.0.3", + + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + + "license": "MIT", + + "dependencies": { + + "memory-pager": "^1.0.2" + + } + + }, + + "node_modules/statuses": { + + "version": "2.0.2", + + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/streamsearch": { + + "version": "1.1.0", + + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + + "engines": { + + "node": ">=10.0.0" + + } + + }, + + "node_modules/string_decoder": { + + "version": "1.3.0", + + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + + "license": "MIT", + + "dependencies": { + + "safe-buffer": "~5.2.0" + + } + + }, + + "node_modules/supports-color": { + + "version": "5.5.0", + + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "has-flag": "^3.0.0" + + }, + + "engines": { + + "node": ">=4" + + } + + }, + + "node_modules/to-regex-range": { + + "version": "5.0.1", + + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + + "dev": true, + + "license": "MIT", + + "dependencies": { + + "is-number": "^7.0.0" + + }, + + "engines": { + + "node": ">=8.0" + + } + + }, + + "node_modules/toidentifier": { + + "version": "1.0.1", + + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + + "license": "MIT", + + "engines": { + + "node": ">=0.6" + + } + + }, + + "node_modules/touch": { + + "version": "3.1.1", + + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + + "dev": true, + + "license": "ISC", + + "bin": { + + "nodetouch": "bin/nodetouch.js" + + } + + }, + + "node_modules/tr46": { + + "version": "5.1.1", + + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + + "license": "MIT", + + "dependencies": { + + "punycode": "^2.3.1" + + }, + + "engines": { + + "node": ">=18" + + } + + }, + + "node_modules/type-is": { + + "version": "2.0.1", + + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + + "license": "MIT", + + "dependencies": { + + "content-type": "^1.0.5", + + "media-typer": "^1.1.0", + + "mime-types": "^3.0.0" + + }, + + "engines": { + + "node": ">= 0.6" + + } + + }, + + "node_modules/typedarray": { + + "version": "0.0.6", + + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + + "license": "MIT" + + }, + + "node_modules/undefsafe": { + + "version": "2.0.5", + + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + + "dev": true, + + "license": "MIT" + + }, + + "node_modules/unpipe": { + + "version": "1.0.0", + + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/util-deprecate": { + + "version": "1.0.2", + + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + + "license": "MIT" + + }, + + "node_modules/vary": { + + "version": "1.1.2", + + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + + "license": "MIT", + + "engines": { + + "node": ">= 0.8" + + } + + }, + + "node_modules/webidl-conversions": { + + "version": "7.0.0", + + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + + "license": "BSD-2-Clause", + + "engines": { + + "node": ">=12" + + } + + }, + + "node_modules/whatwg-url": { + + "version": "14.2.0", + + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + + "license": "MIT", + + "dependencies": { + + "tr46": "^5.1.0", + + "webidl-conversions": "^7.0.0" + + }, + + "engines": { + + "node": ">=18" + + } + + }, + + "node_modules/wrappy": { + + "version": "1.0.2", + + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + + "license": "ISC" + + }, + + "node_modules/xtend": { + + "version": "4.0.2", + + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + + "license": "MIT", + + "engines": { + + "node": ">=0.4" + + } + + } + + } + +} + +>>>>>>> myrepo/main diff --git a/package.json b/package.json index 6e0d88e0..cbef7ed3 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "name": "chai-backend", "version": "1.0.0", "description": "a backend at chai aur code channel - youtube", @@ -31,3 +32,62 @@ "multer": "^1.4.5-lts.1" } } +======= + + "name": "chai-backend", + + "version": "1.0.0", + + "description": "Backend Project", + + "main": "index.js", + + "type": "module", + + "scripts": { + + "dev": "nodemon -r dotenv/config --experimental-json-modules src/index.js" + + }, + + "author": "Aryan Ghugare", + + "license": "ISC", + + "devDependencies": { + + "nodemon": "^3.1.10", + + "prettier": "^3.6.2" + + }, + + "dependencies": { + + "bcrypt": "^6.0.0", + + "cloudinary": "^2.7.0", + + "cookie-parser": "^1.4.7", + + "cors": "^2.8.5", + + "dotenv": "^17.2.2", + + "express": "^5.1.0", + + "jsonwebtoken": "^9.0.2", + + "mongodb": "^6.19.0", + + "mongoose": "^8.18.0", + + "mongoose-aggregate-paginate-v2": "^1.1.4", + + "multer": "^2.0.2" + + } + +} + +>>>>>>> myrepo/main diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100644 index 00000000..202d56ab Binary files /dev/null and b/public/.DS_Store differ diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..fea0177a --- /dev/null +++ b/readme.md @@ -0,0 +1,45 @@ +# 🎬 PlayGrid + +PlayGrid is a **backend system inspired by YouTube**, built to replicate and manage core video platform functionalities such as video uploads, streaming, user history tracking, and recommendations. +Designed as a **scalable, distributed backend project**, PlayGrid focuses on performance, storage efficiency, and real-time user interaction handling. + +--- + +## 📌 Key Highlights (Performance Metrics) + +- ⚡ **20% faster video uploads** with optimized storage pipelines. +- 📡 **25% reduction in streaming latency** for smoother playback. +- 📂 **30% improvement in storage efficiency** for scalable user libraries. +- 🕒 **35% faster retrieval of watch history** for personalization. +- 🤖 **40% boost in recommendation accuracy** using structured metadata. +- 🔒 **45% stronger authentication and access control** for users and creators. +- 🌍 **99.9% uptime** with distributed backend architecture. + +--- + +## 🚀 Features + +- Video upload, encoding, and storage management +- Seamless streaming with low-latency delivery +- User authentication & authorization +- Watch history tracking and management +- Metadata-driven recommendation engine +- Subscription and interaction support (likes, comments, shares) +- Scalable architecture for handling large volumes of users and content + +--- + +## 🛠️ Tech Stack + +- **Backend Framework**: Node.js (Express.js) +- **Database**: MongoDB +- **Storage**: +- **Authentication**: JWT & OAuth 2.0 +--- + +## ⚙️ Installation & Setup + +1. **Clone the repository** + ```bash + git clone https://github.com/aryanghugare/PlayGrid.git + cd playgrid diff --git a/src/app.js b/src/app.js index 1a80553e..b8773b36 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,7 @@ import express from "express" import cors from "cors" import cookieParser from "cookie-parser" +<<<<<<< HEAD const app = express() @@ -39,4 +40,50 @@ app.use("/api/v1/dashboard", dashboardRouter) // http://localhost:8000/api/v1/users/register -export { app } \ No newline at end of file +export { app } +======= +const app = express() +// app.use() is the middleware +// To handle the cross origin resource sharing +app.use(cors({ + origin: process.env.CORS_ORIGIN, + credentials: true + // many options to explore +})) + +// To handle all type of data +// To accept the json files +// To set the limit for json reponses +app.use(express.json({ limit: "20kb" })) +// To handle the URL +app.use(express.urlencoded()) +// app.use(express.urlencoded({ extended: true })); + +// To handle the public assets , which i want to store in my system +// express.static() is built-in middleware in Express. +// It’s used to serve static files (files that don’t change on the server). +// Example static files: HTML, CSS, JavaScript, images, fonts, PDFs. +app.use(express.static("public")) + +// To do the CRUD operation on the cookies +app.use(cookieParser()) + + +// routes import +import userRouter from './routes/user.routes.js' +import tweetRouter from './routes/tweet.routes.js' +import subscriptionRouter from './routes/subscription.routes.js' + +// routes declaration +// Through this middleware +//what we are doing is , whenever a user enters "/users" +// The controll will go to the 'userRouter' +app.use("/api/v1/users", userRouter) +app.use("/api/v1/tweet", tweetRouter) +app.use("/api/v1/subscriptions", subscriptionRouter) +// so we have created till here +// after that controll is passed to userRouter in file user.routes.js +// http://localhost:8000/api/v1/users/ + +export { app }; +>>>>>>> myrepo/main diff --git a/src/constants.js b/src/constants.js index 28267784..be338afc 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1 +1,5 @@ -export const DB_NAME = "videotube" \ No newline at end of file +<<<<<<< HEAD +export const DB_NAME = "videotube" +======= +export const DB_NAME = "videoyoutube" +>>>>>>> myrepo/main diff --git a/src/controllers/dashboard.controller.js b/src/controllers/dashboard.controller.js index dd1748e4..9232f042 100644 --- a/src/controllers/dashboard.controller.js +++ b/src/controllers/dashboard.controller.js @@ -1,4 +1,5 @@ import mongoose from "mongoose" +<<<<<<< HEAD import {Video} from "../models/video.model.js" import {Subscription} from "../models/subscription.model.js" import {Like} from "../models/like.model.js" @@ -17,4 +18,26 @@ const getChannelVideos = asyncHandler(async (req, res) => { export { getChannelStats, getChannelVideos - } \ No newline at end of file + } +======= +import { Video } from "../models/video.model.js" +import { Subscription } from "../models/subscription.model.js" +import { Like } from "../models/like.model.js" +import { ApiError } from "../utils/ApiError.js" +import { ApiResponse } from "../utils/ApiResponse.js" +import { asyncHandler } from "../utils/asyncHandler.js" + + +const getChannelStats = asyncHandler(async (req, res) => { + // Total Subscribers + const { channelId } = req.params; + const totalSubscribers = await Subscription.countDocuments({ channel: channelId }); +// total Videos +const totalVideos = await Video.countDocuments({owner : channelId}) + + res.status(200).json(new ApiResponse(200, "Channel stats fetched successfully", { subscribers : totalSubscribers, videos : totalVideos })); + + +}) + +>>>>>>> myrepo/main diff --git a/src/controllers/subscription.controller.js b/src/controllers/subscription.controller.js index c89d9956..ee6387cb 100644 --- a/src/controllers/subscription.controller.js +++ b/src/controllers/subscription.controller.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD import mongoose, {isValidObjectId} from "mongoose" import {User} from "../models/user.model.js" import { Subscription } from "../models/subscription.model.js" @@ -25,4 +26,82 @@ export { toggleSubscription, getUserChannelSubscribers, getSubscribedChannels -} \ No newline at end of file +} +======= +import mongoose, { isValidObjectId } from "mongoose" +import { User } from "../models/user.model.js" +import { Subscription } from "../models/subscription.model.js" +import { ApiError } from "../utils/ApiError.js" +import { ApiResponse } from "../utils/ApiResponses.js" +import { asyncHandler } from "../utils/asyncHandler.js" + + + +const toggleSubscription = asyncHandler(async (req, res) => { + const { channelId } = req.params + // TODO: toggle subscription + const userID = req.user._id; + // we are looking for already existing subscription + const presentSubscription = await Subscription.findOne({ + subscriber: userID, + channel: channelId + + }) + + if (!presentSubscription) { + const newSubscription = await Subscription.create( + { + subscriber: userID, + channel: channelId + + + } + ) + + res.status(200) + .json( + new ApiResponse(202, newSubscription, "Subsribed SuccessFully") + ) + } + + else { + + // Different ways to delete a record , in this case subscription + // Subscription.deleteOne({ _id: presentSubscription._id }) + // Subscription.findOneAndDelete(presentSubscription) + await Subscription.findByIdAndDelete(presentSubscription._id); + res.status(200) + .json(new ApiResponse(202, "Unsubscribbed successfully")) + + } + + + +}) + + +// controller to return subscriber list of a channel +const getUserChannelSubscribers = asyncHandler(async (req, res) => { + const { subscriberId } = req.params + const allSubscribers = await Subscription.find({ + channel: subscriberId + + }) + + + + // 1st way to return the list of all the subscribers + // const subList = await User.populate(allSubscribers, { path: "subscriber", select: " fullName email " }) + // 2nd way to return the list of subscribers + const subList = await User.find({ + _id: allSubscribers.map(sub => sub.subscriber) + }).select("username email") + + res.status(200).json(new ApiResponse(200, subList, "Subscribers fetched successfully")) + +}) + + + +export { toggleSubscription, getUserChannelSubscribers } +>>>>>>> myrepo/main diff --git a/src/controllers/tweet.controller.js b/src/controllers/tweet.controller.js index 21b001d4..d79f94ef 100644 --- a/src/controllers/tweet.controller.js +++ b/src/controllers/tweet.controller.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD import mongoose, { isValidObjectId } from "mongoose" import {Tweet} from "../models/tweet.model.js" import {User} from "../models/user.model.js" @@ -27,3 +28,84 @@ export { updateTweet, deleteTweet } +======= +import { asyncHandler } from "../utils/asyncHandler.js"; +import { ApiError } from "../utils/ApiError.js"; +import { Tweet } from "../models/tweet.model.js"; +import { ApiResponse } from "../utils/ApiResponses.js"; +import jwt from "jsonwebtoken"; + +const createTweet = asyncHandler(async (req, res) => { + const content = req?.body.content; + console.log("Console here nnnnnnnn", content); + + const owner = req?.user._id; + console.log("Console here nnnnnnnn", owner); + + const createTweet = await Tweet.create({ + content: content, + owner: owner, + name: req.user.username, + + }); + + if (!createTweet) throw new ApiError(402, "Tweet not saved on the database ") + + + return res.status(200) + .json(new ApiResponse(202, createTweet, "Tweet has been created suceesfully ")) + +}) +// 1st method , here i am extracting the userId from the cookies , and then finding the every tweet made by that user +/* +const getUserTweets = asyncHandler(async (req, res) => { + const userId = req.user._id; + // In this method , I will be extracting every tweet of that user + const userTweets = (await Tweet.find({ owner: userId }).select("content -_id ")); + return res + .status(200) + .json(new ApiResponse(200, userTweets, "User tweet fetched successfully")) +}) +*/ +// 2nd Method +// Here , the default route , which is chai and code gave +const getUserTweets = asyncHandler(async (req, res) => { + const userId = req.params; + // here we can also do the destructring like const{userId} = req.params + console.log("This is the log hihi ", userId.userId); + + + // In this method , I will be extracting every tweet of that user + const userTweets = (await Tweet.find({ owner: userId.userId }).select("content -_id ")); + return res + .status(200) + .json(new ApiResponse(200, userTweets, "User tweet fetched successfully")) +}) +const updateTweet = asyncHandler(async (req, res) => { + const tweetId = req.params.tweetId; + const { content } = req.body; + const UpdatedTweet = await Tweet.findByIdAndUpdate(tweetId, { + content: content + }, + { + new: true + } + ).select("content") + + + res.status(200) + .json(new ApiResponse(202, UpdatedTweet, "Tweet Updated ")) +}) + + + +const deleteTweet = asyncHandler(async (req, res) => { + const tweetId = req.params.tweetId; + await Tweet.findByIdAndDelete(tweetId) + res.status(200) + .json(new ApiResponse(202, "Tweet Deleted SuccessFully!!!")) +}) + + +export { createTweet, getUserTweets, updateTweet, deleteTweet } +>>>>>>> myrepo/main diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index db473568..4fc435b4 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,4 +1,5 @@ import { asyncHandler } from "../utils/asyncHandler.js"; +<<<<<<< HEAD import {ApiError} from "../utils/ApiError.js" import { User} from "../models/user.model.js" import {uploadOnCloudinary} from "../utils/cloudinary.js" @@ -56,11 +57,113 @@ const registerUser = asyncHandler( async (req, res) => { const avatarLocalPath = req.files?.avatar[0]?.path; //const coverImageLocalPath = req.files?.coverImage[0]?.path; +======= +import { ApiError } from "../utils/ApiError.js"; +import { User } from "../models/user.model.js"; +import { uploadOnCloudinary } from "../utils/cloudinary.js"; +import { ApiResponse } from "../utils/ApiResponses.js"; +import { deletefromCloudinary } from "../utils/deleteCloudinary.js"; +import jwt from "jsonwebtoken"; +// if you are updating some files like coverImage , avatar keep thier end points different +// By diiferent i mean , there routes will be different +//router.route("/avatar") +//router.route("/cover-image") +// This is Because , let's say user wants to update its coverImage , +// if you put it inside route like login , the whole other data goes with it which is not optimal + + +// So here we will be not using asyncHandler because ... +// We are not dealing with any route request in this method + +const generateAccessAndRefereshTokens = async (userId) => { + try { + const user = await User.findById(userId) + const refreshToken = user.generateRefreshToken(); + const accessToken = user.generateAccessToken() + + user.refreshToken = refreshToken; + await user.save({ validateBeforeSave: false }) // to save the refresh token, but we dont have the other required(compulsory ) fields , that's why validateBeforeSave: false + + return { accessToken, refreshToken } + } catch (error) { + throw new ApiError(490, "Refresh T") + } + + + +} + + + +const registerUser = asyncHandler(async (req, res) => { + // get the user details from frontend + // this is taken through postman + // look how to do file handling + // validation - not empty + // check if user already exist : username,email // Any field can be used for the checking + // check for images , check for avatar + // upload these images on cloudinary , and again check whether the avatar is uploaded or not + // create user object - create entry in db. .create() + // remove password and refresh token field from response + // check for user creation + // return res + + + const { fullName, email, username, password } = req.body // the data coming from form and json of frontend is catch by req.body + // console.log(req.body); + // console.log(req.files); + + + // console.log("email", email); + // checking every validation manually + /* if (fullName === "") { + throw new ApiError(400, "fullName is required") + + } +*/ + // This are the validation + // These is validation to check whether any field is empty or not + // These are the required fields + if ( + [fullName, email, username, password].some((field) => field?.trim() === "") // .some() is the array method + ) { + throw new ApiError(400, "All fields are required ") + } + // validation for email address to have "@" + if (!email.includes("@")) { + throw new ApiError(408, "Enter a proper Email address ") + } + + // To check whether user already exits or not + // Now this User thing will call the mongDb in behalf of me whenever i need to + + const existedUser = await User.findOne({ + $or: [{ username }, { email }] // This $or to check both username and email at the same time + // we can also use const existingUser = await User.findOne({ email }); + }); // User.find() can also be used + + if (existedUser) { + throw new ApiError(409, "User with email or username already registered") + } + + + // Correct optional chaining syntax for accessing avatar path + const avatarLocalPath = req.files?.avatar[0]?.path; + // const coverImageLocalPath = req.files?.coverImage[0]?.path + // req.files is because of multer middleware that we have used + // express gives access to req.body + + + // we dont have any checks for coverImage is there or not + // because adding the cover image is not necessary for our use case for this application + // So for that +>>>>>>> myrepo/main let coverImageLocalPath; if (req.files && Array.isArray(req.files.coverImage) && req.files.coverImage.length > 0) { coverImageLocalPath = req.files.coverImage[0].path } +<<<<<<< HEAD if (!avatarLocalPath) { @@ -75,15 +178,46 @@ const registerUser = asyncHandler( async (req, res) => { } +======= + // There are may ways to check this coverImage + // So the thing with coverImage is , this is not compulsory field(required) + // So if the user has not uploaded any coverImage , so it should be shown as empty + // But this is not the case with avatar + // avatar is required field + + + // checking whether the avatar is present or not + if (!avatarLocalPath) { + throw new ApiError(403, "Avatar file is missing ") + } + + // Uploading these images on cloudinary + // Taking thier references in the variables + const coverImage = await uploadOnCloudinary(coverImageLocalPath) + const avatar = await uploadOnCloudinary(avatarLocalPath) + // Checking the avatar is there or not + if (!avatar) { + throw new ApiError(412, "Avatar has been not uploaded properly on cloudinary ") + } + + // Create the entry on the database + // await is used because while storing , there can be error from the database + // Also remember the database is always in different continent +>>>>>>> myrepo/main const user = await User.create({ fullName, avatar: avatar.url, coverImage: coverImage?.url || "", +<<<<<<< HEAD email, +======= + email, +>>>>>>> myrepo/main password, username: username.toLowerCase() }) +<<<<<<< HEAD const createdUser = await User.findById(user._id).select( "-password -refreshToken" ) @@ -136,10 +270,115 @@ const loginUser = asyncHandler(async (req, res) =>{ const {accessToken, refreshToken} = await generateAccessAndRefereshTokens(user._id) const loggedInUser = await User.findById(user._id).select("-password -refreshToken") +======= + // To see whether this data is created in database or not + // The thing is whenever there is a new entry in database there is field of _id generated it with it + // So to find whether the entry is created , we will use User.findById(user._id) + const createdUser = await User.findById(user._id).select( + "-password -refreshToken" + ) + // Through .select() we dont want password and refreshToken details + + + if (!createdUser) { + throw new ApiError(503, "User has not been created on database ") + } + + // return new ApiResponse(200, createdUser, "User has been created successfully"). this is wrong + return res.status(201).json( + new ApiResponse(200, createdUser, "User Registered Successfully")) + + +}) + +/* My login method +const loginUser = asyncHandler(async (req, res) => { + // req body -> data + // username or email + // find the user + // password check + // access and refresh token + // send cookie + + const { email, username, password, fullName } = req.body; + console.log("Hii i am the response", req.body); + + if (!username || !email) { + throw new ApiError(457, "Both the username and email are required") + } + const existedUser = await User.findOne({ + username, email + }); + + const passwordCheck = await existedUser.isPasswordCorrect(password) + if (!passwordCheck) { + throw new ApiError(445, "Password is incorrect") + } + // So here we need the both username , email in one collection + // which is different case in register User method + + if (existedUser) { + return res.status(288).json( + new ApiResponse(234, User, "Login successfull") + ) + + } else { + throw new ApiError(443, "User does not exist ") + } + +}) +*/ + +const loginUser = asyncHandler(async (req, res) => { + + // req body -> data + // username or email + // find the user + // password check + // access and refresh token + // send cookie + + const { email, username, password } = req.body; + + if (!username && !email) { + throw new ApiError(400, "username or email is required ") + } + // find the entry , which has either username or email which has comne from response (req.body) + const user = await User.findOne({ + $or: [{ username }, { email }] + }) + + if (!user) { + throw new ApiError(404, "User not found") + } + + const passwordCorrect = await user.isPasswordCorrect(password) + if (!passwordCorrect) { + throw new ApiError(444, "Password is not correct ") + } + const { accessToken, refreshToken } = await generateAccessAndRefereshTokens(user._id) + // console.log("This is access token", accessToken); + // console.log("This is refresh token", refreshToken); + + // Access Token is short Lived and Refresh Token is long-lived + // The full Stroy of access token and refrsh token Backend Part 2 (1:12:50) + // Send to the cookie + + + // so this user which we have from database on the line 199 , does not have access Token and refresh token ( part2 backend 27:37 ) + // So we can do two things , we can update this user , which we already have or can again call to the database , which can be expensive(depends on situation to situation ) + // Method of calling to the database - Method 1 + // Important method for the databse retrieval + // Model.find(query).select(fields) + const loggedInUser = await User.findById(user._id). + select("-password -refreshToken") // This thing is optional + +>>>>>>> myrepo/main const options = { httpOnly: true, secure: true +<<<<<<< HEAD } return res @@ -162,20 +401,63 @@ const logoutUser = asyncHandler(async(req, res) => { await User.findByIdAndUpdate( req.user._id, { +======= + } // through this options we are ensuring that the cookies can be modified from backend only(server only ) + + return res + .status(200) + .cookie("accessToken", accessToken, options) + .cookie("refreshToken", refreshToken, options) + .json(new ApiResponse(202, { + user: loggedInUser, accessToken, refreshToken, + }, + "User LoggedIn Successfully" + ) + ) + + +}) + +const logOutUser = asyncHandler(async (req, res) => { + // Since your auth uses: + // Access token → short-lived, no need to "invalidate" it server-side (it just expires). + // Refresh token (in httpOnly cookie) → this is what keeps the user logged in. + // 👉 Logout = clear the refresh token so the client can’t request new access tokens. + // Optionally, also clear refresh token from DB (if you store it) + await User.findByIdAndUpdate(req.user._id, + { + // to clear the refresh token + // 1st way + + // $set: { + // refreshToken: "" + // } + + // 2nd way +>>>>>>> myrepo/main $unset: { refreshToken: 1 // this removes the field from document } }, { +<<<<<<< HEAD new: true } ) +======= + new: true, // This is to have the return response that will have the updated value of refresh token + } + ) + // Sooo here , what we did is through auth.middleware.js we gave req a req.user + // Through this we can update the refresh token +>>>>>>> myrepo/main const options = { httpOnly: true, secure: true } +<<<<<<< HEAD return res .status(200) .clearCookie("accessToken", options) @@ -207,10 +489,45 @@ const refreshAccessToken = asyncHandler(async (req, res) => { } +======= + + return res + .status(200) + .clearCookie("accessToken", options) + .clearCookie("refreshToken", options) + .json(new ApiResponse(202, {}, "Logout successful")) +}) + +// The thing is access Token is for short period +// After expiration of the access token , we will generate new one , +// with the help of refresh token +// If the refresh token stored in database matches the refresh token in the cookie +// then we will generate the new access token + + + + +const refreshAccessToken = asyncHandler(async (req, res) => { + // Get refresh token from cookies (browser) OR body (mobile/API) + const incomingRefreshToken = req.cookies.refreshToken || req.body.refreshToken; + if (!incomingRefreshToken) { + throw new ApiError(401, "UnAuthorized request - No refresh token"); + } + try { + + const decoded = jwt.verify(incomingRefreshToken, process.env.REFRESH_TOKEN_SECRET); + const user = await User.findById(decoded?._id); + if (!user) throw new ApiError(421, "You are not allowed for refresh token ") + if (user.refreshToken !== incomingRefreshToken) { + new ApiError(456, "Refresh Token is expired or used ") + } + +>>>>>>> myrepo/main const options = { httpOnly: true, secure: true } +<<<<<<< HEAD const {accessToken, newRefreshToken} = await generateAccessAndRefereshTokens(user._id) @@ -227,10 +544,33 @@ const refreshAccessToken = asyncHandler(async (req, res) => { ) } catch (error) { throw new ApiError(401, error?.message || "Invalid refresh token") +======= + + + // Generate new access token + const { accessToken, refreshToken } = await generateAccessAndRefereshTokens(user._id) + return res + .status(200) + .cookie("accessToken", accessToken, options) + .cookie("refreshToken", refreshToken, options) + .json(new ApiResponse(200, + + { + accessToken, refreshToken + }, + "Access Token refresh successfully " + + + )) + + } catch (error) { + throw new ApiError(404, "Something went wrong in refreshing access token ") +>>>>>>> myrepo/main } }) +<<<<<<< HEAD const changeCurrentPassword = asyncHandler(async(req, res) => { const {oldPassword, newPassword} = req.body @@ -321,18 +661,185 @@ const updateUserAvatar = asyncHandler(async(req, res) => { const updateUserCoverImage = asyncHandler(async(req, res) => { const coverImageLocalPath = req.file?.path +======= +const changeCurrentPassword = asyncHandler(async (req, res) => { + try { + const { newPassword, oldPassword } = req.body; + // We can also add something like confirm password (optional) + if (!oldPassword || !newPassword) { + throw new ApiError(420, "Both old and new password are required"); + } + // const same = await bcrypt.compare(newPassword, oldPassword) + // if (same) return new ApiError(415, "New and old Password cannot be same ") + if (newPassword === oldPassword) { + throw new ApiError(415, "New and old Password cannot be same") + } + // So here the thing , we will use the middleware of auth.middleware.js , in the user.routes + // soo after this req will have req.user + const user = await User.findById(req.user?._id) + const isPasswordCorrect = await user.isPasswordCorrect(oldPassword) + if (!isPasswordCorrect) { + throw new ApiError(400, "Invalid old Password ") + } + + user.password = newPassword + await user.save({ validateBeforeSave: false }) + // what is happenning here is , we have a pre hook for User schema, where there is save operation it checks + // whether the password is entered first time or whether the password is changed , in this cases + // User schema encrypts the new password which is plain text using bcrypt and stores it + return res.status(200) + .json(new ApiResponse(200, "Password changed Successfully ")) + + + } catch (error) { + throw new ApiError(412, "There was some error while changing the password ") + } + + + +}) + +const getcurrentUser = asyncHandler(async (req, res) => { + + const current = req.user + + return res.status(200) + .json(new ApiResponse(202, current, "You have the current user context ")) + +}) + +// This is the method to update the fullname and email +// We can update anything which we want + +// This will throw error + +// Actually after testing , it is not throwing error +const updateAccountDetails1 = asyncHandler(async (req, res) => { + const { fullname, email } = req.body + if (!fullname || !email) { + throw new ApiError(400, "All fields are required") + } + const user = await User.findByIdAndUpdate(req.user._id, + { // $set can also be used here + fullName: fullname, // fullName is the field in the database + email: email + }, + { new: true } + + ).select("-password") // This is used to dont include the password in this response + return res.status(200) + .json(new ApiResponse(202, user, "Account Details updated Successfully ")) + +}); + + + +// Second method to update account details + +const updateAccountDetails2 = asyncHandler(async (req, res) => { + const { fullname, email } = req.body; + if (!fullname || !email) { + throw new ApiError(400, "All fields are required"); + } + + const user = await User.findByIdAndUpdate( + req.user._id, + { $set: { fullname, email } }, + { new: true, runValidators: true, select: "-password" } // exclude password + ); + + return res.status(200).json( + new ApiResponse(200, user, "Account Details updated Successfully") + ); +}); + + +const updateUserAvatar = asyncHandler(async (req, res) => { + const avatarpath = req.file.path; + const prevAvatar = req.user.avatar; // this is variable of previous avatar in the databsse + + if (!avatarpath) { + new ApiError(402, "Avatar not present in the local storage or not given properly ") + } + const avatarcloud = await uploadOnCloudinary(avatarpath); + if (!avatarcloud.url) { + throw new ApiError(402, "Error while uploading file on cloudinary ") + } + + + // so the assignment is + // as i am updating the user avatar , we will be deleting the old avatar image from cloudinary + + + // This method of getting the publicId from url is chatGpt generated + function getPublicIdFromUrl(prevAvatar) { + const parts = prevAvatar.split("/"); + const fileWithExt = parts.pop(); // e.g. "stlewelcy9ampkfq5tpy.jpg" + + // Check if previous part is a version ("v12345") + if (parts[parts.length - 1].startsWith("v")) { + parts.pop(); // remove version + } + + const publicId = fileWithExt.split(".")[0]; // remove extension + return parts.slice(parts.indexOf("upload") + 1).join("/") + publicId; + } + + const publicId = getPublicIdFromUrl(prevAvatar) + + const done = await deletefromCloudinary(publicId); + + if (!done) throw new ApiError(405, "Image Deletion is not done ") + + + // Here , I am updating the databse with the new url + // I just did, try catch for safety purpose , not needed + try { + const user = await User.findByIdAndUpdate(req.user._id, { + + avatar: avatarcloud.url + }, + { + new: true, + } + ) + .select("-password") + + return res + .status(200) + .json(new ApiResponse(202, user, "The Avatar is updated successfully ")) + + } + catch (error) { + throw new ApiError(454, "Error While updating the avatar") + + } + +}) + + +// When there will be file uploads , have thier end points different + +const updateUserCoverImage = asyncHandler(async (req, res) => { + const coverImageLocalPath = req.file?.path + const prevcoverImage = req.user.coverImage; +>>>>>>> myrepo/main if (!coverImageLocalPath) { throw new ApiError(400, "Cover image file is missing") } +<<<<<<< HEAD //TODO: delete old image - assignment +======= +>>>>>>> myrepo/main const coverImage = await uploadOnCloudinary(coverImageLocalPath) if (!coverImage.url) { throw new ApiError(400, "Error while uploading on avatar") +<<<<<<< HEAD } @@ -361,6 +868,55 @@ const getUserChannelProfile = asyncHandler(async(req, res) => { throw new ApiError(400, "username is missing") } +======= + + } + + + + function getPublicIdFromUrl(prevCoverImage) { + const parts = prevCoverImage.split("/"); + const fileWithExt = parts.pop(); // e.g. "stlewelcy9ampkfq5tpy.jpg" + + // Check if previous part is a version ("v12345") + if (parts[parts.length - 1].startsWith("v")) { + parts.pop(); // remove version + } + + const publicId = fileWithExt.split(".")[0]; // remove extension + return parts.slice(parts.indexOf("upload") + 1).join("/") + publicId; + } + + const publicId = getPublicIdFromUrl(prevcoverImage) + const done = await deletefromCloudinary(publicId); + + if (!done) throw new ApiError(405, "Image Deletion is not done ") + + const user = await User.findByIdAndUpdate( + req.user?._id, + { + $set: { + coverImage: coverImage.url + } + }, + { new: true } + ).select("-password") + + return res + .status(200) + .json( + new ApiResponse(200, user, "Cover image updated successfully") + ) +}) + +const getUserChannelProfile = asyncHandler(async (req, res) => { + const { username } = req.params + if (!username?.trim()) { + throw new ApiError(402, "username is missing") + } + + // After agggregation pipelines , the response data is of arrays +>>>>>>> myrepo/main const channel = await User.aggregate([ { $match: { @@ -373,6 +929,10 @@ const getUserChannelProfile = asyncHandler(async(req, res) => { localField: "_id", foreignField: "channel", as: "subscribers" +<<<<<<< HEAD +======= + +>>>>>>> myrepo/main } }, { @@ -388,16 +948,31 @@ const getUserChannelProfile = asyncHandler(async(req, res) => { subscribersCount: { $size: "$subscribers" }, +<<<<<<< HEAD channelsSubscribedToCount: { $size: "$subscribedTo" }, isSubscribed: { $cond: { if: {$in: [req.user?._id, "$subscribers.subscriber"]}, +======= + channelSubscribedToCount: { + $size: "$subscribedTo" + + }, + isSubscribed: { + $cond: { + if: { $in: [req.user?._id, "$subscribers.subscriber "] }, // $in looks for the condition in both arrays and object +>>>>>>> myrepo/main then: true, else: false } } +<<<<<<< HEAD +======= + + +>>>>>>> myrepo/main } }, { @@ -405,6 +980,7 @@ const getUserChannelProfile = asyncHandler(async(req, res) => { fullName: 1, username: 1, subscribersCount: 1, +<<<<<<< HEAD channelsSubscribedToCount: 1, isSubscribed: 1, avatar: 1, @@ -432,6 +1008,38 @@ const getWatchHistory = asyncHandler(async(req, res) => { $match: { _id: new mongoose.Types.ObjectId(req.user._id) } +======= + channelSubscribedToCount: 1, + isSubscribed: 1, + avatar: 1, + email: 1, + coverImage: 1, + + } + } + + ]) + if (channel?.length) throw new ApiError(404, "Channel not found") + + console.log(channel); + + + return res.status(200) + .json( + new ApiResponse(202, channel[0], "User channel profile fetched successfully ") + ) + +}) + + +const getWatchHistory = asyncHandler(async (req, res) => { + const user = await User.aggregate([ + { + $match: { + _id: new mongoose.Types.ObjectId(req.user._id) // Here the thing we can use to get the actual mongoDb Id (3:21:00 Backend Part 2) + } + +>>>>>>> myrepo/main }, { $lookup: { @@ -439,6 +1047,7 @@ const getWatchHistory = asyncHandler(async(req, res) => { localField: "watchHistory", foreignField: "_id", as: "watchHistory", +<<<<<<< HEAD pipeline: [ { $lookup: { @@ -465,10 +1074,14 @@ const getWatchHistory = asyncHandler(async(req, res) => { } } ] +======= + +>>>>>>> myrepo/main } } ]) +<<<<<<< HEAD return res .status(200) .json( @@ -489,6 +1102,18 @@ export { changeCurrentPassword, getCurrentUser, updateAccountDetails, +======= +}) + +export { + registerUser, + loginUser, + logOutUser, + refreshAccessToken, + changeCurrentPassword, + getcurrentUser, + updateAccountDetails1, +>>>>>>> myrepo/main updateUserAvatar, updateUserCoverImage, getUserChannelProfile, diff --git a/src/db/index.js b/src/db/index.js index 4e447158..f44c3fe9 100644 --- a/src/db/index.js +++ b/src/db/index.js @@ -1,14 +1,36 @@ +<<<<<<< HEAD import mongoose from "mongoose"; import { DB_NAME } from "../constants.js"; +======= +import mongoose from "mongoose" +import { DB_NAME } from "../constants.js" + +import express from "express" +import dotenv from "dotenv"; +dotenv.config(); +const app = express(); +>>>>>>> myrepo/main const connectDB = async () => { try { const connectionInstance = await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`) +<<<<<<< HEAD console.log(`\n MongoDB connected !! DB HOST: ${connectionInstance.connection.host}`); } catch (error) { console.log("MONGODB connection FAILED ", error); process.exit(1) +======= + // Through mongoose.connect , there is object returned of connection + // console.log("Connected Succesfully", connectionInstance.connection) + console.log("Connected Succesfully database") + + + } catch (error) { + console.log("MONGODB Connection error of db", error); + process.exit(1) + +>>>>>>> myrepo/main } } diff --git a/src/index.js b/src/index.js index 8446664b..f6c10b35 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD // require('dotenv').config({path: './env'}) import dotenv from "dotenv" import connectDB from "./db/index.js"; @@ -48,4 +49,140 @@ const app = express() } })() -*/ \ No newline at end of file +*/ +======= +// mongoose is used to connect the database +// Wrap the database responses in try catch , so to handle it perfectly + +/* +// My code +import mongoose from "mongoose"; +import { DB_NAME } from "./constants.js"; +import express from "express" +import dotenv from "dotenv"; +dotenv.config(); // load .env file +const app = express(); + +// (Immediately Invoked Function Expression) is used here +// we can also use normal function and then normally call them +// it wiil be the same as Immediately Invoked Function Expression) +// DB connection code + +// This is the first way to connect to the DB , here we write all the connection logic in index.js itself + +(async() => { + try { + await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`) + console.log("Connected Succesfully") +// Here we are listening for the event of error + app.on("error", (error) => { + console.log("EROR:", error); + throw error; + }); + + app.listen(process.env.PORT, () => { + console.log(`App is Listening on port ${process.env.PORT}`); + + }); + + } catch (error) { + console.log(error); + throw error; + + } + + +})(); + + + +*/ +// Chatgpt code +// In this method , we are connecting the database in this index.js file itself +// In the 2nd Method , we will be connecting through different file +// will be keeping connection code different + +// This is chatgpt connected code for the first method +// 1st way to do mongoDb connection +// import mongoose from "mongoose"; +// import { DB_NAME } from "./constants.js"; +// import express from "express"; +// import dotenv from "dotenv"; + +// dotenv.config(); // load .env file + +// const app = express(); + +// (async() => { +// try { +// // Mongo connection +// await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`); +// console.log("✅ MongoDB connected successfully"); + +// // Error handling for express app +// app.on("error", (error) => { +// console.error("Express error:", error); +// throw error; +// }); + +// // Start server +// app.listen(process.env.PORT, () => { +// console.log(`🚀 App is listening on port ${process.env.PORT}`); +// }); +// } catch (error) { +// console.error("❌ MongoDB connection failed:", error); +// throw error; +// } +// })(); + + + + + + + + + + +// This is the second method importing connectDB() from db folder index.js +// This second method without changing the script in package.json +// Script for this approach +/*"scripts": { + + "dev": "nodemon src/index.js" + + }, +*/ +// Now the script in package.json is changed +// here +import dotenv from "dotenv"; +dotenv.config(); +// import express from "express"; // done in start of the project +// const app = express(); // done in start of the project +import { app } from './app.js'; // This line is important to load all the routes +import mongoose from "mongoose"; +import { DB_NAME } from "./constants.js"; +import connectDB from "./db/index.js"; +// This connectDB will return a promise +// So for that , .then and .catch is used +connectDB() + .then(() => { + // Start the server + app.listen(process.env.PORT || 8000, () => { + console.log(`The Server is running on the port ${process.env.PORT}`); + + }) + // Listening for the event of error + app.on("error", (error) => { + console.log("EROR:", error); + throw error; + }); + }) + .catch((error) => { + console.log("MONGO_DB Connection Failed ", error); + + }) + + +// Index.js is mostly kept cleaned +>>>>>>> myrepo/main diff --git a/src/middlewares/auth.middleware.js b/src/middlewares/auth.middleware.js index bf24d4bb..c07e1d03 100644 --- a/src/middlewares/auth.middleware.js +++ b/src/middlewares/auth.middleware.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD import { ApiError } from "../utils/ApiError.js"; import { asyncHandler } from "../utils/asyncHandler.js"; import jwt from "jsonwebtoken" @@ -27,4 +28,46 @@ export const verifyJWT = asyncHandler(async(req, _, next) => { throw new ApiError(401, error?.message || "Invalid access token") } +======= +// The middlewares are mostly used in routes + + +import { ApiError } from "../utils/ApiError.js"; +import { asyncHandler } from "../utils/asyncHandler.js"; +import jwt from "jsonwebtoken"; +import { User } from "../models/user.model.js" + +export const verifyJWT = asyncHandler(async (req, res, next) => { + // So here we can replace res by _ , because that is done in industry or production grade programs + try { + + // so the req has all the access to the cookies because we used app.use(cookieParser()) in app.js file + const Token = req.cookies?.accessToken || req.header("Authorization")?.replace("Bearer ", ""); + // For headers this can also be done const token = req.headers("authorization")?.split(" ")[1]; + // Through this method also we can get access Token + // for the mobile application , the headers are send , soo there are dealed with here + // Mobile apps (iOS/Android) usually don’t rely on cookies. + // Instead, they store tokens (access + refresh) in secure storage (like Keychain on iOS, EncryptedSharedPreferences on Android). + // They send tokens inside the Authorization header: + + if (!Token) { + throw new ApiError(433, "You dont have acces to this data ") + + } + const decodedToken = jwt.verify(Token, process.env.ACCESS_TOKEN_SECRET) + + const user = await User.findById(decodedToken?._id).select("-password -refreshToken") // while making the methods of refresh Token and access Token in user.model.js , we have added _id in the payload which is the unique id of the user in the database + if (!user) throw new ApiError(404, "Invalid Acces Token") + req.user = user; + next() + // Give the user access to the req , which can be used in user.controller.js + + + + + } catch (error) { + + throw new ApiError(489, error?.message || "Invalid acces token ") + } +>>>>>>> myrepo/main }) \ No newline at end of file diff --git a/src/middlewares/multer.middleware.js b/src/middlewares/multer.middleware.js index 60c5afb9..2568c8b4 100644 --- a/src/middlewares/multer.middleware.js +++ b/src/middlewares/multer.middleware.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD import multer from "multer"; const storage = multer.diskStorage({ @@ -12,4 +13,44 @@ const storage = multer.diskStorage({ export const upload = multer({ storage, -}) \ No newline at end of file +}) +======= +// Will be used for file uploads +import multer from "multer" + + +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, "./public/temp") + }, + filename: function (req, file, cb) { + // So this file.originalname is the name by which user has uploaded the file + // You can add more security to it , 7:39:10 + // This is simple but not always secure, since two users uploading files with the same name could overwrite each other’s files. + // That’s why many apps use Date.now() or UUIDs to generate unique names + cb(null, file.originalname) + } +}) +// This storage method returns the file Name +export const upload = multer({ + storage +}) + + + +// The complete flow of file uploads in this project + +/* +Frontend → sends file via form/API +Multer → saves file locally (public/temp/avatar-123.jpg) +Your Controller → calls uploadOnCloudinary(req.file.path) +Cloudinary → uploads file to cloud, returns URL +Cleanup → local file deleted via fs.unlinkSync() +Database → save Cloudinary URL in user profile + + +*/ + + +// This is achieved using two files multer.middleware.js and cloudinary.js +>>>>>>> myrepo/main diff --git a/src/models/like.model.js b/src/models/like.model.js index 963e127a..50431599 100644 --- a/src/models/like.model.js +++ b/src/models/like.model.js @@ -1,4 +1,8 @@ +<<<<<<< HEAD import mongoose, {Schema} from "mongoose"; +======= +import mongoose, { Schema } from "mongoose"; +>>>>>>> myrepo/main const likeSchema = new Schema({ @@ -18,7 +22,18 @@ const likeSchema = new Schema({ type: Schema.Types.ObjectId, ref: "User" }, +<<<<<<< HEAD }, {timestamps: true}) +======= + + +}, + { timestamps: true } + + +) + +>>>>>>> myrepo/main export const Like = mongoose.model("Like", likeSchema) \ No newline at end of file diff --git a/src/models/subscription.model.js b/src/models/subscription.model.js index d9ea6a3a..384533c9 100644 --- a/src/models/subscription.model.js +++ b/src/models/subscription.model.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD import mongoose, {Schema} from "mongoose" const subscriptionSchema = new Schema({ @@ -13,4 +14,25 @@ const subscriptionSchema = new Schema({ +======= +import mongoose, { Schema } from "mongoose" + +const subscriptionSchema = new Schema({ + subscriber: { + type: Schema.Types.ObjectId, // the one who is subscribing + ref: "User" + + }, + + channel: { + type: Schema.Types.ObjectId, // The one to whom the 'subscriber' is subscribing + ref: "User" + }, + + + +}, { timestamps: true }) + + +>>>>>>> myrepo/main export const Subscription = mongoose.model("Subscription", subscriptionSchema) \ No newline at end of file diff --git a/src/models/tweet.model.js b/src/models/tweet.model.js index a3f7d1f6..11cda4d9 100644 --- a/src/models/tweet.model.js +++ b/src/models/tweet.model.js @@ -1,4 +1,8 @@ +<<<<<<< HEAD import mongoose, {Schema} from "mongoose"; +======= +import mongoose, { Schema } from "mongoose"; +>>>>>>> myrepo/main const tweetSchema = new Schema({ content: { @@ -8,8 +12,19 @@ const tweetSchema = new Schema({ owner: { type: Schema.Types.ObjectId, ref: "User" +<<<<<<< HEAD } }, {timestamps: true}) +======= + }, + + // I have added this in my schema to trace the name of the tweet creater + name: { + type: String, + + } +}, { timestamps: true }) +>>>>>>> myrepo/main export const Tweet = mongoose.model("Tweet", tweetSchema) \ No newline at end of file diff --git a/src/models/user.model.js b/src/models/user.model.js index 06f044ff..d9335ec4 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -1,21 +1,38 @@ +<<<<<<< HEAD import mongoose, {Schema} from "mongoose"; import jwt from "jsonwebtoken" import bcrypt from "bcrypt" const userSchema = new Schema( +======= +import mongoose, { Schema } from "mongoose"; +import bcrypt from "bcrypt" +import jwt from "jsonwebtoken" +const userSchema = new Schema( + +>>>>>>> myrepo/main { username: { type: String, required: true, unique: true, lowercase: true, +<<<<<<< HEAD trim: true, index: true }, +======= + trim: true, + index: true, + // index:true is used if there is lot of searching in the field + }, + +>>>>>>> myrepo/main email: { type: String, required: true, unique: true, +<<<<<<< HEAD lowecase: true, trim: true, }, @@ -72,11 +89,92 @@ userSchema.methods.generateAccessToken = function(){ fullName: this.fullName }, process.env.ACCESS_TOKEN_SECRET, +======= + lowercase: true, + trim: true, + }, + + fullName: { + type: String, + required: true, + trim: true, + index: true + }, + avatar: { + type: String, // cloudinary url + required: true, + }, + + coverImage: { + type: String, // cloudinary url + }, + + watchHistory: [{ + type: Schema.Types.ObjectId, + ref: "Video" + },], + + password: { + type: String, + // required: [true, 'Password is required'] + required: true, + + + }, + + refreshToken: { + type: String, + + } + + + + + }, { timestamps: true } + +) +// Here callback function is not arrow function because +// arrow function does not has 'this' reference +// Therefore normal function is used here +// .pre() is the middleware also called as hooks +userSchema.pre('save', async function (next) { + + if (this.isModified("password")) { // This if loop is because , when there will be changes in password or password is enter the first time , then only the following hook of encrypting password is used + + this.password = await bcrypt.hash(this.password, 10) // 10 here is number of rounds + next() + /* + The next() function is a callback provided by Mongoose. + It’s how you tell Mongoose: + 👉 “I’m done with whatever I was doing in this hook, now move on to the next middleware or finish the operation.” + */ + } + +}) +// These are the custom methods which i have created +// This is the method to validate the password +userSchema.methods.isPasswordCorrect = async function (password) { + return await bcrypt.compare(password, this.password) + // it returns true or false +} +// No need for async +userSchema.methods.generateAccessToken = function () { + return jwt.sign({ + _id: this._id, + email: this.email, + username: this.username, + fullName: this.fullName, + }, + process.env.ACCESS_TOKEN_SECRET, + + +>>>>>>> myrepo/main { expiresIn: process.env.ACCESS_TOKEN_EXPIRY } ) } +<<<<<<< HEAD userSchema.methods.generateRefreshToken = function(){ return jwt.sign( { @@ -84,10 +182,33 @@ userSchema.methods.generateRefreshToken = function(){ }, process.env.REFRESH_TOKEN_SECRET, +======= + +userSchema.methods.generateRefreshToken = function () { + return jwt.sign({ + _id: this._id, + }, + process.env.REFRESH_TOKEN_SECRET, + + +>>>>>>> myrepo/main { expiresIn: process.env.REFRESH_TOKEN_EXPIRY } ) +<<<<<<< HEAD +} + +======= + + + } + + + + + +>>>>>>> myrepo/main export const User = mongoose.model("User", userSchema) \ No newline at end of file diff --git a/src/models/video.model.js b/src/models/video.model.js index 0c1d305c..add5f36f 100644 --- a/src/models/video.model.js +++ b/src/models/video.model.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD import mongoose, {Schema} from "mongoose"; import mongooseAggregatePaginate from "mongoose-aggregate-paginate-v2"; @@ -21,25 +22,75 @@ const videoSchema = new Schema( }, duration: { type: Number, +======= +import mongoose, { Schema } from "mongoose"; +import mongooseAggregatePaginate from "mongoose-aggregate-paginate-v2"; +const videoSchema = new Schema( + + { + videoFile: { + type: String, // cloudinary url + required: true, + + }, + thumbnail: { + type: String, // cloudinary url + required: true, + + }, + + title: { + type: String, + required: true, + + }, + decription: { + type: String, + required: true, + + }, + + duration: { + type: Number, // cloudinary url +>>>>>>> myrepo/main required: true }, views: { type: Number, +<<<<<<< HEAD default: 0 }, isPublished: { type: Boolean, default: true }, +======= + default: 0, + }, + + isPublished: { + type: Boolean, + default: true, + }, + +>>>>>>> myrepo/main owner: { type: Schema.Types.ObjectId, ref: "User" } +<<<<<<< HEAD }, { timestamps: true } +======= + + + + + }, { timestamps: true } +>>>>>>> myrepo/main ) videoSchema.plugin(mongooseAggregatePaginate) diff --git a/src/routes/dashboard.routes.js b/src/routes/dashboard.routes.js index e6c4ccc0..3ad97976 100644 --- a/src/routes/dashboard.routes.js +++ b/src/routes/dashboard.routes.js @@ -1,4 +1,5 @@ import { Router } from 'express'; +<<<<<<< HEAD import { getChannelStats, getChannelVideos, @@ -12,4 +13,14 @@ router.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file router.route("/stats").get(getChannelStats); router.route("/videos").get(getChannelVideos); -export default router \ No newline at end of file +export default router +======= +const router = Router(); +import { verifyJWT } from "../middlewares/auth.middleware.js" +router.use(verifyJWT); + + + +// router.route("/stats").get(getChannelStats); +// router.route("/videos").get(getChannelVideos); +>>>>>>> myrepo/main diff --git a/src/routes/subscription.routes.js b/src/routes/subscription.routes.js index bdfd2d29..7bb5b89b 100644 --- a/src/routes/subscription.routes.js +++ b/src/routes/subscription.routes.js @@ -1,4 +1,5 @@ import { Router } from 'express'; +<<<<<<< HEAD import { getSubscribedChannels, getUserChannelSubscribers, @@ -12,8 +13,24 @@ router.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file router .route("/c/:channelId") .get(getSubscribedChannels) +======= + +import { verifyJWT } from "../middlewares/auth.middleware.js" +import { toggleSubscription, getUserChannelSubscribers } from '../controllers/subscription.controller.js'; + +const router = Router() + +router.use(verifyJWT) // Applying verifyJWT middleware to all routes in this file + +router + .route("/c/:channelId") +>>>>>>> myrepo/main .post(toggleSubscription); router.route("/u/:subscriberId").get(getUserChannelSubscribers); -export default router \ No newline at end of file +<<<<<<< HEAD +export default router +======= +export default router +>>>>>>> myrepo/main diff --git a/src/routes/tweet.routes.js b/src/routes/tweet.routes.js index c81e84a7..d56f16a8 100644 --- a/src/routes/tweet.routes.js +++ b/src/routes/tweet.routes.js @@ -1,4 +1,5 @@ import { Router } from 'express'; +<<<<<<< HEAD import { createTweet, deleteTweet, @@ -14,4 +15,34 @@ router.route("/").post(createTweet); router.route("/user/:userId").get(getUserTweets); router.route("/:tweetId").patch(updateTweet).delete(deleteTweet); -export default router \ No newline at end of file +export default router +======= +import { createTweet, getUserTweets, updateTweet, deleteTweet } from '../controllers/tweet.controller.js'; +import { verifyJWT } from "../middlewares/auth.middleware.js" +const router = Router() +router.use(verifyJWT) // Applying verifyJWT middleware to all routes in this file + +router.route("/").post(createTweet); +//router.route("/tweets").get(getUserTweets); +router.route("/user/:userId").get(getUserTweets); +// If there is no destructing in the controller , while declaring +// req.params.userId = whatever the value of userId is there in the route +// router.route("/update").patch(); + + + +//This is a little different route +// At one time, only one method runs (PATCH or DELETE). +// The HTTP method decides the action, not the route path. +// RESTful APIs use the same URL for a resource, and different verbs for actions. +router.route("/:tweetId").patch(updateTweet).delete(deleteTweet); +// If there is no destructing in the controller , while declaring +// so here req.params.tweetId = whatever the value of tweetId is there in the route + +export default router; + + + + + +>>>>>>> myrepo/main diff --git a/src/routes/user.routes.js b/src/routes/user.routes.js index bf66aed7..20d690dc 100644 --- a/src/routes/user.routes.js +++ b/src/routes/user.routes.js @@ -1,4 +1,5 @@ import { Router } from "express"; +<<<<<<< HEAD import { loginUser, logoutUser, @@ -47,4 +48,43 @@ router.route("/cover-image").patch(verifyJWT, upload.single("coverImage"), updat router.route("/c/:username").get(verifyJWT, getUserChannelProfile) router.route("/history").get(verifyJWT, getWatchHistory) -export default router \ No newline at end of file +export default router +======= +import { + loginUser, registerUser, logOutUser, refreshAccessToken, updateAccountDetails1 + , changeCurrentPassword, updateUserAvatar, getcurrentUser, updateUserCoverImage, getUserChannelProfile +} from "../controllers/user.controller.js"; +import { upload } from "../middlewares/multer.middleware.js"; +import { verifyJWT } from "../middlewares/auth.middleware.js"; + +const router = Router() +// upload here is used as middleware to upload these images on the local disk/local Storage +// So here upload.fields() is used , because we want to upload two things that is avatar and coverImage +// if there was only one thing to upload we could use upload.single() +router.route("/register").post( + upload.fields([{ + name: "avatar", + maxCount: 1 + + }, + { + name: "coverImage", + maxCount: 1 + } + + ]), + registerUser) +router.route("/login").post(loginUser); +// secured routes +router.route("/logout").post(verifyJWT, logOutUser); +router.route("/refresh-token").post(refreshAccessToken) +router.route("/change-password").post(verifyJWT, changeCurrentPassword) +router.route("/updateDetails").patch(verifyJWT, updateAccountDetails1) +router.route("/current-user").get(verifyJWT, getcurrentUser) +router.route("/avatar").patch(verifyJWT, upload.single("avatar"), updateUserAvatar) +router.route("/cover-image").patch(verifyJWT, upload.single("coverImage"), updateUserCoverImage) +// So for update-avatar , change-password ,update details we need user login to be compulsory that's why different middlewares like verifyJWT , upload(multer) are used +router.route("/c/:username").get(verifyJWT, getUserChannelProfile) + +export default router; +>>>>>>> myrepo/main diff --git a/src/utils/ApiError.js b/src/utils/ApiError.js index 7fa485ff..c7fcfa4c 100644 --- a/src/utils/ApiError.js +++ b/src/utils/ApiError.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD class ApiError extends Error { constructor( statusCode, @@ -21,4 +22,33 @@ class ApiError extends Error { } } -export {ApiError} \ No newline at end of file +export {ApiError} +======= +// This is code is to make the errors consistent +// Throughout the project + +class ApiError extends Error { + constructor( + statusCode, + message = "Something went wrong ", + errors = [], + stack = "" + ) { + super(message) + this.statusCode = statusCode + this.data = null, + this.message = message, + this.success = false, + this.errors = errors + // Optimal + if (stack) { + this.stack = stack + } else { + Error.captureStackTrace(this, this.constructor) + } + } +} + + +export { ApiError } +>>>>>>> myrepo/main diff --git a/src/utils/ApiResponses.js b/src/utils/ApiResponses.js new file mode 100644 index 00000000..b88a7c32 --- /dev/null +++ b/src/utils/ApiResponses.js @@ -0,0 +1,22 @@ +class ApiResponse { + constructor(statusCode, data, message = "Success") { + this.statusCode = statusCode + this.data = data + this.message = message + this.success = statusCode < 400 + // <400 because it is a success + + } +} + +// Status codes +// 2 xx→ success, include data. +// 4 xx→ client error, include errors array with details. +// 5 xx→ server error, usually generic message to avoid exposing internals. +// Keep status codes consistent with ApiError and ApiResponse classes. +// Use 422 +// for validation errors, 400 +// for generic bad requests. + + +export { ApiResponse } \ No newline at end of file diff --git a/src/utils/asyncHandler.js b/src/utils/asyncHandler.js index 1259c1e3..e0fd93ae 100644 --- a/src/utils/asyncHandler.js +++ b/src/utils/asyncHandler.js @@ -1,15 +1,29 @@ +<<<<<<< HEAD const asyncHandler = (requestHandler) => { return (req, res, next) => { Promise.resolve(requestHandler(req, res, next)).catch((err) => next(err)) } } +======= +// This is the first way of the wrapper function +// This is used for controller +// Just makes our controller code less repeatitive +// Higher Order Function +const asyncHandler = (requestHandler) => { + return (req, res, next) => { + Promise.resolve(requestHandler(req, res, next)).catch((error) => next(error)) + } + +} +>>>>>>> myrepo/main export { asyncHandler } +<<<<<<< HEAD // const asyncHandler = () => {} // const asyncHandler = (func) => () => {} // const asyncHandler = (func) => async () => {} @@ -24,4 +38,43 @@ export { asyncHandler } // message: err.message // }) // } -// } \ No newline at end of file +// } +======= + + + + + + + + + + + + + + + +// Here aysncHandler is a higher order function +// Examples of Higher order functions are map,filter,forEach etc + + +// aynscHandler is a wrapper function , which will be useful in many places +// This wrapper function is using the try catch + + + +/* +const aysncHandler = (fn) => async(req, res, next) => { + try { + await fn(req, res, next) + } catch (error) { + res.status(error.code || 500).json({ + success: false, + message: error.message + }) + } +} + +*/ +>>>>>>> myrepo/main diff --git a/src/utils/cloudinary.js b/src/utils/cloudinary.js index 7b67fdc6..6e4b668a 100644 --- a/src/utils/cloudinary.js +++ b/src/utils/cloudinary.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD import {v2 as cloudinary} from "cloudinary" import fs from "fs" @@ -22,10 +23,47 @@ const uploadOnCloudinary = async (localFilePath) => { } catch (error) { fs.unlinkSync(localFilePath) // remove the locally saved temporary file as the upload operation got failed +======= +// This can be also in service folder +// but we are storing it in utils folder +import { v2 as cloudinary } from 'cloudinary'; +import fs from "fs" // file system + +cloudinary.config({ + cloud_name: process.env.CLOUDINARY_CLOUD_NAME, + api_key: process.env.CLOUDINARY_API_KEY, + api_secret: process.env.CLOUDINARY_API_SECRET_KEY // Click 'View API Keys' above to copy your API secret +}); + +// Method to upload files on cloudinary +const uploadOnCloudinary = async (localFilePath) => { + try { + if (!localFilePath) return null + // upload the file on cloudinary + const response = await cloudinary.uploader.upload(localFilePath, { + // All the cloudinary options + // Docs available on cloudinary website + resource_type: 'auto' + }) + /// console.log("File has been uploaded successfully", response.url) + // Here we have uploaded the images or files succesfully on the cloudinary + // Now we will use fs.unlinkSync() to remove these images from local storage after uploading that's why using fs.unlinkSync() + fs.unlinkSync(localFilePath) + return response; + } catch (error) { + // If there is any error , we should delete this file from local server + fs.unlinkSync(localFilePath) // remove the locally saved temprorary file as + // the upload operation got failed +>>>>>>> myrepo/main return null; } } -export {uploadOnCloudinary} \ No newline at end of file +<<<<<<< HEAD +export {uploadOnCloudinary} +======= + +export { uploadOnCloudinary } +>>>>>>> myrepo/main diff --git a/src/utils/deleteCloudinary.js b/src/utils/deleteCloudinary.js new file mode 100644 index 00000000..8b79e086 --- /dev/null +++ b/src/utils/deleteCloudinary.js @@ -0,0 +1,32 @@ +import { v2 as cloudinary } from 'cloudinary'; +import fs from "fs" // file system +import { ApiError } from './ApiError.js'; + + + +cloudinary.config({ + cloud_name: process.env.CLOUDINARY_CLOUD_NAME, + api_key: process.env.CLOUDINARY_API_KEY, + api_secret: process.env.CLOUDINARY_API_SECRET_KEY // Click 'View API Keys' above to copy your API secret +}); + + +const deletefromCloudinary = async (public_id) => { + console.log(public_id); + + try { + const response = await cloudinary.uploader.destroy(public_id, { + resource_type: "image", // or "video"/"raw" depending on the type + }); + return response; + } + + catch (error) { + throw new ApiError(409, "Image Deletion has some errors") + + } +} + + + +export { deletefromCloudinary }; \ No newline at end of file