diff --git a/package-lock.json b/package-lock.json index e324dee..17c6e2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "waifu.it", - "version": "4.8.0", + "version": "4.8.1", "lockfileVersion": 3, "requires": true, "packages": { @@ -22,7 +22,7 @@ "is-interactive": "^1.0.0", "moment": "^2.29.4", "mongodb": "^3.6.9", - "mongoose": "^5.13.20", + "mongoose": "^8.9.5", "ora": "^5.4.1", "owoify-js": "^2.0.0", "path": "^0.12.7", @@ -70,29 +70,12 @@ "kuler": "^2.0.0" } }, - "node_modules/@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "dependencies": { - "@types/bson": "*", - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "22.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz", - "integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==", - "dependencies": { - "undici-types": "~6.19.8" + "sparse-bitfield": "^3.0.3" } }, "node_modules/@types/triple-beam": { @@ -100,6 +83,19 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, + "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==" + }, + "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==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -208,11 +204,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -1272,9 +1263,12 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "engines": { + "node": ">=12.0.0" + } }, "node_modules/kuler": { "version": "2.0.0", @@ -1341,8 +1335,7 @@ "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==", - "optional": true + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/merge-descriptors": { "version": "1.0.3", @@ -1456,90 +1449,164 @@ } } }, - "node_modules/mongoose": { - "version": "5.13.22", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.22.tgz", - "integrity": "sha512-p51k/c4X/MfqeQ3I1ranlDiggLzNumZrTDD9CeezHwZxt2/btf+YZD7MCe07RAY2NgFYVMayq6jMamw02Jmf9w==", + "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==", "dependencies": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.7.4", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" }, "engines": { - "node": ">=4.0.0" + "node": ">=18" + } + }, + "node_modules/mongodb-connection-string-url/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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongoose": { + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.5.tgz", + "integrity": "sha512-SPhOrgBm0nKV3b+IIHGqpUTOmgVL5Z3OO9AwkFEmvOZznXTvplbomstCnPOGAyungtRXE5pJTgKpKcZTdjeESg==", + "dependencies": { + "bson": "^6.10.1", + "kareem": "2.6.3", + "mongodb": "~6.12.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-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "peerDependencies": { - "mongoose": "*" + "node_modules/mongoose/node_modules/bson": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", + "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", + "engines": { + "node": ">=16.20.1" } }, - "node_modules/mongoose/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mongoose/node_modules/optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", + "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, "engines": { - "node": ">=4" + "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/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", "engines": { "node": ">=4.0.0" } }, "node_modules/mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", "dependencies": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" + "debug": "4.x" }, "engines": { - "node": ">=4.0.0" + "node": ">=14.0.0" } }, "node_modules/mquery/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/mquery/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/mquery/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/ms": { "version": "2.0.0", @@ -1855,6 +1922,14 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -1943,11 +2018,6 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, - "node_modules/regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, "node_modules/request-ip": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", @@ -2160,9 +2230,9 @@ } }, "node_modules/sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" }, "node_modules/signal-exit": { "version": "3.0.7", @@ -2189,16 +2259,10 @@ "node": ">=10" } }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" - }, "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==", - "optional": true, "dependencies": { "memory-pager": "^1.0.2" } @@ -2353,11 +2417,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2561,4 +2620,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index c9b3269..a77f355 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "waifu.it", - "version": "4.8.0", + "version": "4.9.1", "description": "Random API Serving Anime stuff", "author": "Aeryk", "private": true, @@ -28,7 +28,7 @@ "is-interactive": "^1.0.0", "moment": "^2.29.4", "mongodb": "^3.6.9", - "mongoose": "^5.13.20", + "mongoose": "^8.9.5", "ora": "^5.4.1", "owoify-js": "^2.0.0", "path": "^0.12.7", diff --git a/src/controllers/v4/internal/membership.js b/src/controllers/v4/internal/membership.js new file mode 100644 index 0000000..8478321 --- /dev/null +++ b/src/controllers/v4/internal/membership.js @@ -0,0 +1,56 @@ +import createError from 'http-errors'; +import System from '../../../models/schemas/System.js'; + +// Get membership details +const getMembership = async (req, res, next) => { + const key = req.headers.key; + + // Check for valid access key in headers + if (!key || key !== process.env.ACCESS_KEY) { + return res.status(401).json({ + message: 'Unauthorized', + }); + } + + try { + // Check if any data exists + let membershipData = await System.findOne({}, { membership: 1, _id: 0 }); + + // If no data exists, insert sample data (only runs once) + if (!membershipData) { + membershipData = await System.findOne({}, { membership: 1, _id: 0 }); + } + + // Get valid keys dynamically from schema data + const validFields = Object.keys(membershipData.membership); + // Parse query parameters correctly + const queryParams = req.query.q ? req.query.q.split(',').map(param => param.trim()) : []; + // If no query params, return full membership object + if (queryParams.length === 0) { + return res.status(200).json(membershipData); + } + + // Validate query parameters + const selectedFields = queryParams.filter(field => validFields.includes(field)); + + if (selectedFields.length === 0) { + return res.status(400).json({ message: 'Invalid query parameter(s)' }); + } + + // Construct projection object dynamically + const projection = selectedFields.reduce((acc, field) => ({ ...acc, [`membership.${field}`]: 1 }), { _id: 0 }); + + // Fetch only the requested fields + const result = await System.findOne({}, projection); + + if (!result) { + return next(createError(404, 'No membership data found')); + } + + res.status(200).json(result); + } catch (error) { + return next(error); + } +}; + +export { getMembership }; diff --git a/src/controllers/v4/internal/user.js b/src/controllers/v4/internal/user.js index f69a0ac..8ae45f4 100644 --- a/src/controllers/v4/internal/user.js +++ b/src/controllers/v4/internal/user.js @@ -28,36 +28,149 @@ const retrieveUserProfile = async (req, res, next) => { }; /** - * Fetches user profile data based on the provided user ID and Reset Token. + * Processes user actions such as addquota, removequota, updaterole, banuser and updatetoken * * @param {Object} req - Express request object. * @param {Object} res - Express response object. * @param {Function} next - Express next middleware function. - * @returns {Object} - User profile data. + * @returns {Object} - Response with action results or errors. */ -const updateUserToken = async (req, res, next) => { +const processUserAction = async (req, res, next) => { const key = req.headers.key; + // Check for valid access key in headers if (!key || key !== process.env.ACCESS_KEY) { return res.status(401).json({ message: 'Unauthorized', }); } - const user = await Users.findById(req.params.id); - if (!user) { - return res.status(404).json({ message: 'User not found' }); // User not found - } - // Update user's token in the database - await Users.updateOne( - { _id: { $eq: req.params.id } }, - { $set: { token: generateToken(req.params.id, process.env.HMAC_KEY) } }, - ); + const userId = req.params.id; + const { action, amount, reason, executor, expiry } = req.body; // Extract fields from the request body - // This will return the data however it won't be the latest one after updating the token - return res.status(200).json({ - message: 'Token reset successfully.', - }); + try { + // Fetch user by ID + const user = await Users.findById(userId); + if (!user) { + return res.status(404).json({ message: 'User not found' }); // User not found + } + + let updatedUser; + + // Handle different actions + switch (action) { + case 'addquota': + if (!amount || amount <= 0) { + return res.status(400).json({ message: 'Invalid quota amount' }); + } + user.req_quota = (user.req_quota || 0) + Number(amount); + + // Update status history + user.status_history.push({ + _id: user.status_history.length + 1, + timestamp: new Date(), + reason: reason || 'Quota added', + value: `+${amount} quota`, + executor: executor || 'system', + }); + + updatedUser = await user.save(); + break; + + case 'removequota': + if (!amount || amount <= 0) { + return res.status(400).json({ message: 'Invalid quota amount' }); + } + if ((user.req_quota || 0) < amount) { + return res.status(400).json({ message: 'Insufficient quota' }); + } + user.req_quota = (user.req_quota || 0) - Number(amount); + + // Update status history + user.status_history.push({ + _id: user.status_history.length + 1, + timestamp: new Date(), + reason: reason || 'Quota removed', + value: `-${amount} quota`, + executor: executor || 'system', + }); + + updatedUser = await user.save(); + break; + + case 'ban': + if (!reason) { + return res.status(400).json({ message: 'Ban reason is required' }); + } + user.banned = true; + + // Update status history + user.status_history.push({ + _id: user.status_history.length + 1, + timestamp: new Date(), + expiry: expiry || null, + reason, + isBanned: true, + executor: executor || 'system', + }); + + updatedUser = await user.save(); + break; + case 'unban': + if (!reason) { + return res.status(400).json({ message: 'Unban reason is required' }); + } + user.banned = false; + + // Update status history + user.status_history.push({ + _id: user.status_history.length + 1, + timestamp: new Date(), + expiry: expiry || null, + reason, + isBanned: false, + executor: executor || 'system', + }); + + updatedUser = await user.save(); + break; + + case 'updatetoken': + if (!reason) { + return res.status(400).json({ message: 'Token update reason is required' }); + } + const token = generateToken(userId, process.env.HMAC_KEY); + user.token = token; + + // Update status history + user.status_history.push({ + _id: user.status_history.length + 1, + timestamp: new Date(), + reason: reason || 'Token updated', + value: token, + executor: executor || 'system', + }); + + updatedUser = await user.save(); + break; + + default: + return res.status(400).json({ message: `Invalid action: ${action}` }); + } + + // Respond with updated user data + return res.status(200).json({ + success: true, + message: `${action} executed successfully`, + user: updatedUser, + }); + } catch (error) { + // Handle server errors + return res.status(500).json({ + message: 'An error occurred while processing the action', + error: error.message, + }); + } }; /** @@ -178,4 +291,4 @@ const getUser = async (req, res, next) => { } }; -export { retrieveUserProfile, updateUserToken, processUserSessionAndUpdate, getUser }; +export { retrieveUserProfile, processUserAction, processUserSessionAndUpdate, getUser }; diff --git a/src/index.js b/src/index.js index 39c1bb8..861f7aa 100644 --- a/src/index.js +++ b/src/index.js @@ -37,10 +37,7 @@ const setupServer = async () => { * Connecting to the MongoDB database. * @type {mongoose.Connection} */ - const dbConnection = await mongoose.connect(process.env.MONGODB_URI, { - useUnifiedTopology: true, - useNewUrlParser: true, - }); + const dbConnection = await mongoose.connect(process.env.MONGODB_URI, {}); /** * Starting the Express server and logging success message. diff --git a/src/models/schemas/System.js b/src/models/schemas/System.js new file mode 100644 index 0000000..bbdde3d --- /dev/null +++ b/src/models/schemas/System.js @@ -0,0 +1,27 @@ +import mongoose from 'mongoose'; +const { Schema, model } = mongoose; + +const SystemSchema = new Schema({ + _id: String, + membership: { + features: [], + plans: [ + { + _id: String, + name: { type: String, required: true, unique: true }, + monthlyPrice: { type: Number, required: true }, + annualPrice: { type: Number, required: true }, + current: Boolean, + available: Boolean, + features: [ + { + text: String, + status: { type: String, enum: ['available', 'limited', 'unavailable'] }, + }, + ], + }, + ], + }, +}); + +export default model('System', SystemSchema); diff --git a/src/models/schemas/User.js b/src/models/schemas/User.js index 2087f8d..8a57bbf 100644 --- a/src/models/schemas/User.js +++ b/src/models/schemas/User.js @@ -50,6 +50,12 @@ const UserSchema = new mongoose.Schema({ */ status_history: [ { + /** + * Unique identifier for the status change. + * @type {string} + * @required + */ + _id: String, /** * Timestamp of the status change. * @type {Date} @@ -57,17 +63,37 @@ const UserSchema = new mongoose.Schema({ */ timestamp: { type: Date, default: Date.now }, + /** + * Expiry date for the status change. + * @type {Date} + * @default Date.now + */ + expiry: { type: Date }, + /** * The reason for the status change. * @type {string} */ reason: { type: String }, + /** + * The value of the status change either quota or role or subscription or new email. + * @type {string} + * @default 'null' + */ + value: { type: String }, /** * Flag indicating whether the user is banned at this status change. * @type {boolean} */ isBanned: { type: Boolean }, + + /** + * Information about the staff member who performed the action. + * @type {Object} + * @required + */ + executor: String, }, ], diff --git a/src/routes/v4/index.js b/src/routes/v4/index.js index d67da16..6cd08ba 100644 --- a/src/routes/v4/index.js +++ b/src/routes/v4/index.js @@ -1208,6 +1208,22 @@ import statsRoutes from './internal/stats.js'; */ router.use('/stats', statsRoutes); +import membershipRoutes from './internal/membership.js'; + +/** + * @api {use} Mount Membership Routes + * @apiDescription Mount the membership-related routes for handling interactions. + * @apiName UseMembershipRoutes + * @apiGroup Routes + * + * @apiSuccess {Object} routes Membership-related routes mounted on the parent router. + * + * @function createMembershipRoutes + * @description Creates and returns a set of routes for handling interactions related to Membership. + * @returns {Object} Membership-related routes. + */ +router.use('/membership', membershipRoutes); + /** * Exporting the router for use in other parts of the application. * @exports {Router} router - Express Router instance with mounted routes. diff --git a/src/routes/v4/internal/membership.js b/src/routes/v4/internal/membership.js new file mode 100644 index 0000000..73512f2 --- /dev/null +++ b/src/routes/v4/internal/membership.js @@ -0,0 +1,40 @@ +import { Router } from 'express'; +import { getMembership } from '../../../controllers/v4/internal/membership.js'; +import createRateLimiter from '../../../middlewares/rateLimit.js'; + +const router = Router(); + +router + .route('/') + /** + * @api {get} v4/membership Get Membership Details + * @apiDescription Retrieve membership details, including plans and features. + * @apiName getMembership + * @apiGroup Membership + * @apiPermission user + * + * @apiHeader {String} Authorization System access token. + * + * @apiParam {String} [q] Optional query parameter to filter results. + * @apiParamExample {json} Request Example: + * GET /membership?q=plans&features + * + * @apiSuccess {Object} membership Membership object. + * @apiSuccessExample {json} Success Response: + * { + * "membership": { + * "plans": [...], + * "features": [...] + * } + * } + * + * @apiError (Unauthorized 401) Unauthorized Only authenticated users can access the data. + * @apiError (Forbidden 403) Forbidden Only authorized users can access the data. + * @apiError (Too Many Requests 429) TooManyRequests The client has exceeded the allowed number of requests within the time window. + * @apiError (Bad Request 400) BadRequest Invalid query parameter(s) provided. + * @apiError (Internal Server Error 500) InternalServerError An error occurred while processing the request. + */ + .get(createRateLimiter(), getMembership); + +// Export the router +export default router; diff --git a/src/routes/v4/internal/user.js b/src/routes/v4/internal/user.js index c000a0a..7a20d69 100644 --- a/src/routes/v4/internal/user.js +++ b/src/routes/v4/internal/user.js @@ -1,7 +1,7 @@ import { Router } from 'express'; import { retrieveUserProfile, - updateUserToken, + processUserAction, processUserSessionAndUpdate, getUser, } from '../../../controllers/v4/internal/user.js'; @@ -18,7 +18,7 @@ router * @apiGroup UserManagement * @apiPermission user * - * @apiHeader {String} Authorization User's access token. + * @apiHeader {String} Key Internal access token * * @apiSuccess {Object} userDetails User's details. * @apiSuccess {String} userDetails.username User's username. @@ -48,7 +48,7 @@ router * @apiGroup UserManagement * @apiPermission sudo * - * @apiHeader {String} Authorization User's access token. + * @apiHeader {String} Key Internal access token * * @apiParam {String} id User's unique identifier. * @@ -70,28 +70,34 @@ router */ .get(createRateLimiter(), retrieveUserProfile) /** - * @api {patch} v4/user/profile/:id Get User Profile and Update reset the existing token - * @apiDescription Update the token for a specific user - * @apiName updateUserToken + * @api {patch} v4/user/profile/:id Perform User Action (addquota, removequota, ban, unban, updatetoken) + * @apiDescription Processes various user actions including adding/removing quota, banning/unbanning users, and updating user token. + * @apiName processUserAction * @apiGroup UserManagement * @apiPermission sudo * - * @apiHeader {String} Authorization User's access token. + * @apiHeader {String} Key Internal access token * * @apiParam {String} id User's unique identifier. - * - * @apiSuccess {Object} message - * @apiError (Unauthorized 401) Unauthorized Only authenticated users can access the data. - * @apiError (Forbidden 403) Forbidden Only authorized users can access the data. - * @apiError (Too Many Requests 429) TooManyRequests The client has exceeded the allowed number of requests within the time window. - * @apiError (Internal Server Error 500) InternalServerError An error occurred while processing the rate limit. + * @apiParam {String} action Action to be performed (e.g., addquota, removequota, ban, unban, updatetoken). + * @apiParam {String} [amount] Amount of quota to add or remove (required for addquota/removequota). + * @apiParam {String} [reason] Reason for the action (required for ban, unban, and updatetoken). + * @apiParam {String} [executor] Executor of the action (optional). + * @apiParam {String} [expiry] Expiry of the ban (optional). + * + * @apiSuccess {Object} message Success message with details of the performed action. + * @apiSuccess {Object} user Updated user data after the action. + * @apiError (Unauthorized 401) Unauthorized Only authenticated users can perform actions. + * @apiError (Forbidden 403) Forbidden Only authorized users can perform certain actions. + * @apiError (Bad Request 400) BadRequest Invalid parameters for the specified action. + * @apiError (Internal Server Error 500) InternalServerError An error occurred while processing the action. * * @api {function} createRateLimiter * @apiDescription Creates a rate limiter middleware to control the frequency of requests. * @apiSuccess {function} middleware Express middleware function that handles rate limiting. - * */ - .patch(createRateLimiter(), updateUserToken); + + .patch(createRateLimiter(), processUserAction); // Export the router export default router;