diff --git a/.gitignore b/.gitignore index 62eadab..a30faea 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ node_modules .env.local /dist/ -/build/ +/build/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5c845cf..a7fa4a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,9 @@ "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.1.0", - "helmet": "^8.1.0", "express-async-handler": "^1.2.0", "express-rate-limiter": "^1.3.1", + "helmet": "^8.1.0", "joi": "^18.0.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.19.1", @@ -20,6 +20,8 @@ "nodemon": "^3.1.10", "readdirp": "^4.1.2", "resend": "^6.1.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "winston": "^3.18.3" }, "devDependencies": { @@ -31,6 +33,50 @@ "prettier": "^3.6.2" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -56,6 +102,15 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@commitlint/cli": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", @@ -592,6 +647,12 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@mongodb-js/saslprep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", @@ -601,6 +662,23 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, "node_modules/@stablelib/base64": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", @@ -634,7 +712,6 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { @@ -643,7 +720,6 @@ "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -688,7 +764,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -766,7 +841,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-ify": { @@ -922,6 +996,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1083,6 +1163,15 @@ "node": ">=12.20" } }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -1213,7 +1302,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -1314,6 +1402,18 @@ "node": ">= 0.8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -1481,7 +1581,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -1776,7 +1875,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -1897,6 +1995,12 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2002,6 +2106,12 @@ "node": ">= 0.8" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2090,6 +2200,27 @@ "node": ">=16" } }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2303,6 +2434,17 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2403,6 +2545,18 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-text-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", @@ -2462,7 +2616,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2638,6 +2791,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2650,6 +2810,13 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -2692,7 +2859,6 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true, "license": "MIT" }, "node_modules/lodash.once": { @@ -2726,7 +2892,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", - "dev": true }, "node_modules/logform": { @@ -2746,21 +2911,6 @@ "node": ">= 12.0.0" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - - "dev": true, - "license": "MIT" - - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3105,7 +3255,6 @@ "wrappy": "1" } }, - "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -3115,22 +3264,13 @@ "fn.name": "1.x.x" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true }, - "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3232,6 +3372,15 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3816,6 +3965,62 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.29.4", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.4.tgz", + "integrity": "sha512-gJFDz/gyLOCQtWwAgqs6Rk78z9ONnqTnlW11gimG9nLap8drKa3AJBKpzIQMIjl5PD2Ix+Tn+mc/tfoT2tgsng==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -3987,13 +4192,6 @@ "punycode": "^2.1.0" } }, - - "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/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -4004,6 +4202,12 @@ "requires-port": "^1.0.0" } }, + "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/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -4016,7 +4220,15 @@ "bin": { "uuid": "dist/bin/uuid" } - + }, + "node_modules/validator": { + "version": "13.15.20", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", + "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } }, "node_modules/vary": { "version": "1.1.2", @@ -4101,18 +4313,6 @@ "node": ">= 12.0.0" } }, - "node_modules/winston/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -4157,6 +4357,15 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -4198,6 +4407,36 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/package.json b/package.json index d58b811..1956f92 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ }, "scripts": { "dev": "nodemon src/index.js", + "generate:swagger": "node -e \"import('./src/utils/swagger.js').then(m => console.log(JSON.stringify(m.default, null, 2)))\" > swagger.json", + "predev": "npm run generate:swagger", "prepare": "husky", "commitlint": "commitlint --edit", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", @@ -31,8 +33,10 @@ "mongoose": "^8.19.1", "morgan": "^1.10.1", "nodemon": "^3.1.10", - "readdirp": "^4.1.2", "winston": "^3.18.3", - "resend": "^6.1.3" + "resend": "^6.1.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "readdirp": "^4.1.2" } } diff --git a/src/app.js b/src/app.js index ed0d99e..ef20115 100644 --- a/src/app.js +++ b/src/app.js @@ -12,6 +12,9 @@ import permissionRoutes from "./routes/permission.routes.js"; import rateLimiter from './middlewares/rateLimiter.js'; import errorHandler from "./middlewares/error.middleware.js"; +import swaggerUi from 'swagger-ui-express'; +import swaggerSpec from '../src/utils/swagger.js'; + dotenv.config(); const app = express(); @@ -25,9 +28,8 @@ app.use( }) ); -// Body parsers and static files -app.use(express.json({ limit: "16kb" })); -app.use(express.urlencoded({ extended: true, limit: "16kb" })); +app.use(express.json({ limit: "100kb" })); +app.use(express.urlencoded({ extended: true, limit: "100kb" })); app.use(express.static("public")); app.use(cookieParser()); @@ -45,6 +47,8 @@ app.use( app.use("/api/auth", authRoutes); app.use(rateLimiter); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + // Routes app.use("/api/roles", roleRoutes); app.use("/api/permissions", permissionRoutes); @@ -57,4 +61,4 @@ app.get("/", (req, res) => { //global error handler app.use(errorHandler); -export { app }; +export { app }; \ No newline at end of file diff --git a/src/controllers/authController.js b/src/controllers/authController.js index 7425e61..05e59b0 100644 --- a/src/controllers/authController.js +++ b/src/controllers/authController.js @@ -4,7 +4,80 @@ import jwt from 'jsonwebtoken' import { sendEmail } from '../utils/sendEmail.js'; import asynkcHandler from 'express-async-handler'; import ApiError from '../utils/ApiError.js'; -//register user + + + +/** + * @openapi + * /api/auth/register: + * post: + * tags: + * - User + * summary: Register a new user + * description: Create a new user account with username, email, fullname, password, and optional role. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - username + * - email + * - fullname + * - password + * properties: + * username: + * type: string + * example: mukesh dhadhariya + * email: + * type: string + * example: mukeshdhadhariya1@gmail.com + * fullname: + * type: string + * example: Mukesh Dhadhariya + * password: + * type: string + * example: password123 + * role: + * type: string + * description: Optional Role ID reference + * example: 64f3c2a7e1f0b2a7b1c0d123 + * responses: + * 201: + * description: User registered successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: User registered successfully + * 400: + * description: Bad request (missing fields or username/email already exists) + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: All fields (username, email, fullname, password) are required + * 500: + * description: Registration failed due to server error + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Registration failed + * error: + * type: string + * example: Error message + */ export const registerUser = asynkcHandler(async (req, res) => { const userData = await registerUserService(req.body); return res.status(201).json({ @@ -13,7 +86,81 @@ export const registerUser = asynkcHandler(async (req, res) => { user: userData }); }); -// login user + + + +/** + * @openapi + * /api/auth/login: + * post: + * tags: + * - Auth + * summary: User login + * description: Authenticate a user with email and password and return JWT token along with refresh token. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - email + * - password + * properties: + * email: + * type: string + * format: email + * example: mukesh@example.com + * password: + * type: string + * example: yourpassword123 + * responses: + * 200: + * description: Login successful + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: Login successful + * token: + * type: string + * example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + * refreshToken: + * type: string + * example: "d1f2e3c4-5678-90ab-cdef-1234567890ab" + * 400: + * description: Missing email or password + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Email and password are required + * 401: + * description: Authentication failed (invalid credentials) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Authentication failed + */ export const loginUser = asynkcHandler(async (req, res) => { const { email, password } = req.body; @@ -27,7 +174,59 @@ export const loginUser = asynkcHandler(async (req, res) => { user: result.user, }); }); -// Forgot password + + +/** + * @swagger + * /api/auth/forgotPassword: + * post: + * summary: Request a password reset link + * tags: [Auth] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - email + * properties: + * email: + * type: string + * format: email + * example: user@example.com + * responses: + * 200: + * description: Password reset link sent + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: "Password reset link sent" + * 400: + * description: Bad request (validation error) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: "Email is required" + * 401: + * description: User not found + * 500: + * description: Server error + */ export const forgotPassword = asynkcHandler(async (req, res) => { const { email } = req.body; @@ -67,7 +266,66 @@ export const forgotPassword = asynkcHandler(async (req, res) => { message: 'Password reset link sent' }) }); -// Reset password + + +/** + * @swagger + * /api/auth/resetPassword/{token}: + * post: + * summary: Reset password using token + * tags: [Auth] + * parameters: + * - in: path + * name: token + * required: true + * description: JWT reset token sent by email + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - password + * properties: + * password: + * type: string + * minLength: 6 + * example: MyN3wP@ssw0rd + * responses: + * 200: + * description: Password reset successful + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: "Password reset successful" + * 400: + * description: Invalid or expired token / missing password + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: "Invalid or expired token" + * 401: + * description: Unauthorized (token mismatch) + * 500: + * description: Server error + */ export const resetPassword = asynkcHandler(async (req, res) => { const { token } = req.params; const { password } = req.body; @@ -93,4 +351,4 @@ export const resetPassword = asynkcHandler(async (req, res) => { await user.save(); res.status(200).json({ success: true, message: 'Password reset successful' }); -}); +}); \ No newline at end of file diff --git a/src/controllers/permission.controller.js b/src/controllers/permission.controller.js index 83b667e..0d33bf6 100644 --- a/src/controllers/permission.controller.js +++ b/src/controllers/permission.controller.js @@ -2,29 +2,154 @@ import asyncHandler from 'express-async-handler'; import * as permissionService from "../services/permission.service.js"; import ApiError from '../utils/ApiError.js'; + +/** + * @swagger + * tags: + * name: Permissions + * description: API for managing permissions + */ + +/** + * @swagger + * /api/permissions: + * post: + * summary: Create a new permission + * tags: [Permissions] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * example: create_user + * description: + * type: string + * example: Allows creation of users + * responses: + * 201: + * description: Permission created successfully + * 400: + * description: Invalid request data + */ export const createPermission = asyncHandler(async (req, res) => { const { name, description } = req.body; const perm = await permissionService.createPermission(name, description); res.status(201).json(perm); }); +/** + * @swagger + * /api/permissions: + * get: + * summary: Get all permissions + * tags: [Permissions] + * responses: + * 200: + * description: List of permissions + * 500: + * description: Server error + */ export const getPermissions = asyncHandler(async (req, res) => { const perms = await permissionService.getPermissions(); res.json(perms); }); + +/** + * @swagger + * /api/permissions/{id}: + * get: + * summary: Get a permission by ID + * tags: [Permissions] + * parameters: + * - in: path + * name: id + * required: true + * description: Permission ID + * schema: + * type: string + * responses: + * 200: + * description: Permission details + * 404: + * description: Permission not found + * 500: + * description: Server error + */ export const getPermissionById = asyncHandler(async (req, res) => { const perm = await permissionService.getPermissionById(req.params.id); if (!perm) throw new ApiError(404, 'Permission not found'); res.json(perm); }); +/** + * @swagger + * /api/permissions/{id}: + * put: + * summary: Update a permission + * tags: [Permissions] + * parameters: + * - in: path + * name: id + * required: true + * description: Permission ID + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * example: edit_user + * description: + * type: string + * example: Allows editing of users + * responses: + * 200: + * description: Permission updated successfully + * 404: + * description: Permission not found + * 400: + * description: Invalid request data + */ export const updatePermission = asyncHandler(async (req, res) => { const perm = await permissionService.updatePermission(req.params.id, req.body); if (!perm) throw new ApiError(404, 'Permission not found'); res.json(perm); }); + +/** + * @swagger + * /api/permissions/{id}: + * delete: + * summary: Delete a permission + * tags: [Permissions] + * parameters: + * - in: path + * name: id + * required: true + * description: Permission ID + * schema: + * type: string + * responses: + * 200: + * description: Permission deleted successfully + * 404: + * description: Permission not found + * 500: + * description: Server error + */ export const deletePermission = asyncHandler(async (req, res) => { const deleted = await permissionService.deletePermission(req.params.id); if (!deleted) throw new ApiError(404, 'Permission not found'); diff --git a/src/controllers/role.controller.js b/src/controllers/role.controller.js index 8753117..4df913b 100644 --- a/src/controllers/role.controller.js +++ b/src/controllers/role.controller.js @@ -1,5 +1,41 @@ import * as roleService from "../services/role.service.js"; +/** + * @swagger + * tags: + * name: Roles + * description: API for managing user roles and their permissions + */ + +/** + * @swagger + * /api/roles: + * post: + * summary: Create a new role + * tags: [Roles] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * example: admin + * permissions: + * type: array + * items: + * type: string + * example: ["64f8a91d1b9d9c123456789a", "64f8a91d1b9d9c123456789b"] + * responses: + * 201: + * description: Role created successfully + * 400: + * description: Invalid request data + */ export const createRole = async (req, res) => { try { const { name, permissions } = req.body; @@ -10,6 +46,18 @@ export const createRole = async (req, res) => { } }; +/** + * @swagger + * /api/roles: + * get: + * summary: Get all roles + * tags: [Roles] + * responses: + * 200: + * description: List of all roles with their permissions + * 500: + * description: Server error + */ export const getRoles = async (req, res) => { try { const roles = await roleService.getRoles(); @@ -19,6 +67,27 @@ export const getRoles = async (req, res) => { } }; +/** + * @swagger + * /api/roles/{id}: + * get: + * summary: Get a role by ID + * tags: [Roles] + * parameters: + * - in: path + * name: id + * required: true + * description: Role ID + * schema: + * type: string + * responses: + * 200: + * description: Role details + * 404: + * description: Role not found + * 500: + * description: Server error + */ export const getRoleById = async (req, res) => { try { const role = await roleService.getRoleById(req.params.id); @@ -29,6 +98,42 @@ export const getRoleById = async (req, res) => { } }; +/** + * @swagger + * /api/roles/{id}: + * put: + * summary: Update a role + * tags: [Roles] + * parameters: + * - in: path + * name: id + * required: true + * description: Role ID + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * example: manager + * permissions: + * type: array + * items: + * type: string + * example: ["64f8a91d1b9d9c123456789a"] + * responses: + * 200: + * description: Role updated successfully + * 404: + * description: Role not found + * 400: + * description: Invalid request data + */ export const updateRole = async (req, res) => { try { const role = await roleService.updateRole(req.params.id, req.body); @@ -39,6 +144,27 @@ export const updateRole = async (req, res) => { } }; +/** + * @swagger + * /api/roles/{id}: + * delete: + * summary: Delete a role + * tags: [Roles] + * parameters: + * - in: path + * name: id + * required: true + * description: Role ID + * schema: + * type: string + * responses: + * 200: + * description: Role deleted successfully + * 404: + * description: Role not found + * 500: + * description: Server error + */ export const deleteRole = async (req, res) => { try { const deleted = await roleService.deleteRole(req.params.id); @@ -49,6 +175,41 @@ export const deleteRole = async (req, res) => { } }; +/** + * @swagger + * /api/roles/{id}/permissions: + * put: + * summary: Assign permissions to a role + * tags: [Roles] + * parameters: + * - in: path + * name: id + * required: true + * description: Role ID + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - permissions + * properties: + * permissions: + * type: array + * items: + * type: string + * example: ["64f8a91d1b9d9c123456789a", "64f8a91d1b9d9c123456789b"] + * responses: + * 200: + * description: Permissions assigned successfully + * 400: + * description: Invalid request data + * 404: + * description: Role not found + */ export const assignPermissions = async (req, res) => { try { const { permissions } = req.body; diff --git a/src/models/Permission.model.js b/src/models/Permission.model.js index 3c717d2..9664408 100644 --- a/src/models/Permission.model.js +++ b/src/models/Permission.model.js @@ -11,4 +11,5 @@ const permissionSchema = new mongoose.Schema({ }, }); -export default mongoose.model('Permission', permissionSchema); +const Permission = mongoose.models.Permission || mongoose.model("Permission", permissionSchema); +export default Permission; \ No newline at end of file diff --git a/src/models/role.model.js b/src/models/role.model.js index f733e92..7129b8e 100644 --- a/src/models/role.model.js +++ b/src/models/role.model.js @@ -14,4 +14,5 @@ const roleSchema = new mongoose.Schema({ ] }); -export default mongoose.model('Role', roleSchema); +const Role = mongoose.models.Role || mongoose.model("Role", roleSchema); +export default Role; \ No newline at end of file diff --git a/src/models/user.model.js b/src/models/user.model.js index af7c9c7..46253f3 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -136,4 +136,5 @@ userschema.methods.clearRefreshToken = function () { }; -export const User=mongoose.model('User',userschema); \ No newline at end of file +const User = mongoose.models.User || mongoose.model("User", userschema); +export { User }; \ No newline at end of file diff --git a/src/services/role.service.js b/src/services/role.service.js index c4eb5e8..59b7602 100644 --- a/src/services/role.service.js +++ b/src/services/role.service.js @@ -1,4 +1,4 @@ -import Role from "../models/Role.model.js"; +import Role from '../models/Role.model.js' import Permission from "../models/Permission.model.js"; export const createRole = async (name, permissions = []) => { diff --git a/src/utils/swagger.js b/src/utils/swagger.js new file mode 100644 index 0000000..302e3b8 --- /dev/null +++ b/src/utils/swagger.js @@ -0,0 +1,52 @@ +import swaggerJSDoc from 'swagger-jsdoc'; +import path from 'path' + +const swaggerOptions = { + definition: { + openapi: '3.0.0', + info: { + title: 'RBAC API Documentation', + version: '1.0.0', + description: + 'Open Source Hackathon (Opcode) project — Role-Based Access Control (RBAC) backend API documentation.\n\n' + + 'This documentation provides details of all available API endpoints, authentication methods, and data models used in the RBAC system.', + contact: { + name: 'Mukesh Dhadhariya', + email: 'mukeshdhadhariya1@gmail.com' + } + }, + servers: [ + { + url: process.env.API_BASE_URL ?? 'http://localhost:5000', + description: 'Local development server' + } + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT' + } + } + }, + security: [ + { + bearerAuth: [] + } + ] + }, + + // Paths to files containing OpenAPI annotations (JSDoc) + apis: [ + path.join(process.cwd(), 'src/routes/**/*.js'), + path.join(process.cwd(), 'src/controllers/**/*.js') + ], + + // Throw errors if Swagger spec generation fails + failOnErrors: true +}; + +const swaggerSpec = swaggerJSDoc(swaggerOptions); + +export default swaggerSpec; diff --git a/swagger.json b/swagger.json new file mode 100644 index 0000000..b2ba38d --- /dev/null +++ b/swagger.json @@ -0,0 +1,634 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "RBAC API Documentation", + "version": "1.0.0", + "description": "Open Source Hackathon (Opcode) project — Role-Based Access Control (RBAC) backend API documentation.\n\nThis documentation provides details of all available API endpoints, authentication methods, and data models used in the RBAC system.", + "contact": { + "name": "Mukesh Dhadhariya", + "email": "mukeshdhadhariya1@gmail.com" + } + }, + "servers": [ + { + "url": "http://localhost:5000", + "description": "Local development server" + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "paths": { + "/api/auth/register": { + "post": { + "tags": [ + "User" + ], + "summary": "Register a new user", + "description": "Create a new user account with username, email, fullname, password, and optional role.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "username", + "email", + "fullname", + "password" + ], + "properties": { + "username": { + "type": "string", + "example": "mukesh dhadhariya" + }, + "email": { + "type": "string", + "example": "mukeshdhadhariya1@gmail.com" + }, + "fullname": { + "type": "string", + "example": "Mukesh Dhadhariya" + }, + "password": { + "type": "string", + "example": "password123" + }, + "role": { + "type": "string", + "description": "Optional Role ID reference", + "example": "64f3c2a7e1f0b2a7b1c0d123" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "User registered successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "User registered successfully" + } + } + } + } + } + }, + "400": { + "description": "Bad request (missing fields or username/email already exists)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "All fields (username, email, fullname, password) are required" + } + } + } + } + } + }, + "500": { + "description": "Registration failed due to server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Registration failed" + }, + "error": { + "type": "string", + "example": "Error message" + } + } + } + } + } + } + } + } + }, + "/api/auth/login": { + "post": { + "tags": [ + "Auth" + ], + "summary": "User login", + "description": "Authenticate a user with email and password and return JWT token along with refresh token.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "mukesh@example.com" + }, + "password": { + "type": "string", + "example": "yourpassword123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Login successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Login successful" + }, + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "refreshToken": { + "type": "string", + "example": "d1f2e3c4-5678-90ab-cdef-1234567890ab" + } + } + } + } + } + }, + "400": { + "description": "Missing email or password", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Email and password are required" + } + } + } + } + } + }, + "401": { + "description": "Authentication failed (invalid credentials)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "Authentication failed" + } + } + } + } + } + } + } + } + }, + "/api/permissions": { + "post": { + "summary": "Create a new permission", + "tags": [ + "Permissions" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "example": "create_user" + }, + "description": { + "type": "string", + "example": "Allows creation of users" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Permission created successfully" + }, + "400": { + "description": "Invalid request data" + } + } + }, + "get": { + "summary": "Get all permissions", + "tags": [ + "Permissions" + ], + "responses": { + "200": { + "description": "List of permissions" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/api/permissions/{id}": { + "get": { + "summary": "Get a permission by ID", + "tags": [ + "Permissions" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "description": "Permission ID", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Permission details" + }, + "404": { + "description": "Permission not found" + }, + "500": { + "description": "Server error" + } + } + }, + "put": { + "summary": "Update a permission", + "tags": [ + "Permissions" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "description": "Permission ID", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "edit_user" + }, + "description": { + "type": "string", + "example": "Allows editing of users" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Permission updated successfully" + }, + "400": { + "description": "Invalid request data" + }, + "404": { + "description": "Permission not found" + } + } + }, + "delete": { + "summary": "Delete a permission", + "tags": [ + "Permissions" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "description": "Permission ID", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Permission deleted successfully" + }, + "404": { + "description": "Permission not found" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/api/roles": { + "post": { + "summary": "Create a new role", + "tags": [ + "Roles" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "example": "admin" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64f8a91d1b9d9c123456789a", + "64f8a91d1b9d9c123456789b" + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Role created successfully" + }, + "400": { + "description": "Invalid request data" + } + } + }, + "get": { + "summary": "Get all roles", + "tags": [ + "Roles" + ], + "responses": { + "200": { + "description": "List of all roles with their permissions" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/api/roles/{id}": { + "get": { + "summary": "Get a role by ID", + "tags": [ + "Roles" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "description": "Role ID", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Role details" + }, + "404": { + "description": "Role not found" + }, + "500": { + "description": "Server error" + } + } + }, + "put": { + "summary": "Update a role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "description": "Role ID", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "manager" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64f8a91d1b9d9c123456789a" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Role updated successfully" + }, + "400": { + "description": "Invalid request data" + }, + "404": { + "description": "Role not found" + } + } + }, + "delete": { + "summary": "Delete a role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "description": "Role ID", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Role deleted successfully" + }, + "404": { + "description": "Role not found" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/api/roles/{id}/permissions": { + "put": { + "summary": "Assign permissions to a role", + "tags": [ + "Roles" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "description": "Role ID", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "permissions": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64f8a91d1b9d9c123456789a", + "64f8a91d1b9d9c123456789b" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Permissions assigned successfully" + }, + "400": { + "description": "Invalid request data" + }, + "404": { + "description": "Role not found" + } + } + } + } + }, + "tags": [ + { + "name": "Permissions", + "description": "API for managing permissions" + }, + { + "name": "Roles", + "description": "API for managing user roles and their permissions" + } + ] +}