From 80fe9c1b255a5907fe3394e7f8007b8b691abd73 Mon Sep 17 00:00:00 2001 From: mukeshdhadhariya Date: Tue, 14 Oct 2025 20:09:23 +0530 Subject: [PATCH 1/2] feat(api): add Swagger documentation and basic API endpoints --- .env.example | 3 +- package-lock.json | 269 ++++++++++++++++++++++++++++- package.json | 6 +- src/app.js | 9 +- src/controllers/user.controller.js | 144 +++++++++++++++ src/routes/user.route.js | 11 ++ src/utils/swagger.js | 52 ++++++ swagger.json | 168 ++++++++++++++++++ 8 files changed, 650 insertions(+), 12 deletions(-) create mode 100644 src/controllers/user.controller.js create mode 100644 src/routes/user.route.js create mode 100644 src/utils/swagger.js create mode 100644 swagger.json diff --git a/.env.example b/.env.example index 5a4f6a6..daf2849 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,5 @@ MONGO_URI=mongodb://localhost:27017/rbac JWT_SECRET=your-secret-key CORS_URL= -REFRESH_TOKEN_SECRET= \ No newline at end of file +REFRESH_TOKEN_SECRET= +API_BASE_URL= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 33b5acf..ea0dacf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "express": "^5.1.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.19.1", - "nodemon": "^3.1.10" + "nodemon": "^3.1.10", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@commitlint/cli": "^19.1.0", @@ -23,6 +25,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.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -601,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.1", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.1.tgz", @@ -610,6 +662,13 @@ "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/@types/conventional-commits-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", @@ -630,7 +689,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": { @@ -749,8 +807,7 @@ "node_modules/argparse": { "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 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-ify": { "version": "1.0.0", @@ -880,6 +937,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", @@ -969,6 +1032,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "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", @@ -1192,6 +1264,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", @@ -1637,7 +1721,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" @@ -1844,6 +1927,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", @@ -1942,6 +2031,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", @@ -2149,6 +2259,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", @@ -2294,7 +2415,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -2456,6 +2576,13 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, + "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", @@ -2468,6 +2595,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", @@ -2506,8 +2640,7 @@ "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, "node_modules/lodash.once": { "version": "4.1.1", @@ -2900,6 +3033,13 @@ "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", @@ -2996,6 +3136,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", @@ -3494,6 +3643,62 @@ "node": ">=4" } }, + "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", @@ -3637,6 +3842,15 @@ "punycode": "^2.1.0" } }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3730,6 +3944,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "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", @@ -3768,6 +3991,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 7534390..919966b 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", @@ -25,6 +27,8 @@ "express": "^5.1.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.19.1", - "nodemon": "^3.1.10" + "nodemon": "^3.1.10", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" } } diff --git a/src/app.js b/src/app.js index 010fcdc..f3314a3 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,9 @@ import express from "express"; import cors from "cors" import cookieparser from "cookie-parser" +import swaggerUi from 'swagger-ui-express'; +import swaggerSpec from '../src/utils/swagger.js' +import UserRouter from './routes/user.route.js' const app=express() @@ -9,11 +12,13 @@ app.use(cors({ credentials:true })) -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()) +app.use('/api-docs',swaggerUi.serve,swaggerUi.setup(swaggerSpec)) +app.use('/api/v1/user',UserRouter) export {app} \ No newline at end of file diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js new file mode 100644 index 0000000..356f041 --- /dev/null +++ b/src/controllers/user.controller.js @@ -0,0 +1,144 @@ +import { User } from "../models/user.model.js"; + + + + +/** + * @openapi + * /api/v1/user/health: + * get: + * tags: + * - Health + * summary: Check if the API is working + * description: Simple health check endpoint to verify API is up and running. + * responses: + * 200: + * description: API is healthy + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: API is working good + * status: + * type: intger + * example: 200 + */ +const CheckHealth= (req, res) => { + console.log('api is working') + return res.status(200).json({ + message: 'API is working good', + status: 200 + }); +} + +/** + * @openapi + * /api/v1/user/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 + */ +const UserRegistration=async (req, res) => { + try { + const { username, email, fullname, password,role} = req.body; + + if( + [username,email,fullname,password].some((field)=> field?.trim()==="") + ){ + return res.status(400).json({ + message: "All fields (username, email, fullname, password) are required" + }); + } + + const existingUser = await User.findOne({ + $or: [{ username }, { email }] + }); + if (existingUser) return res.status(400).json({ message: 'Username or email already exists' }); + + const user=await User.create({ + username:username.toLowerCase(), + email, + fullname, + password, + role:role || null + }) + + await user.save(); + + res.status(201).json({ message: 'User registered successfully'}); + } catch (error) { + res.status(500).json({ message: 'Registration failed', error: error.message }); + } +} + +export { + CheckHealth, + UserRegistration +} \ No newline at end of file diff --git a/src/routes/user.route.js b/src/routes/user.route.js new file mode 100644 index 0000000..d7ed320 --- /dev/null +++ b/src/routes/user.route.js @@ -0,0 +1,11 @@ +import { Router } from "express"; +import { CheckHealth ,UserRegistration} from "../controllers/user.controller.js"; +const router = Router(); + + +router.route('/health').get(CheckHealth) + + +router.route('/register').post(UserRegistration) + +export default router; 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..a0bf1ec --- /dev/null +++ b/swagger.json @@ -0,0 +1,168 @@ +{ + "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/v1/user/health": { + "get": { + "tags": [ + "Health" + ], + "summary": "Check if the API is working", + "description": "Simple health check endpoint to verify API is up and running.", + "responses": { + "200": { + "description": "API is healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "API is working good" + }, + "status": { + "type": "intger", + "example": 200 + } + } + } + } + } + } + } + } + }, + "/api/v1/user/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" + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} From c3be1059ae44a23bb75693ab557f1f21dc626680 Mon Sep 17 00:00:00 2001 From: mukeshdhadhariya Date: Thu, 16 Oct 2025 18:46:57 +0530 Subject: [PATCH 2/2] feat(api): add Swagger documentation and basic API endpoints --- src/app.js | 32 +- src/controllers/authController.js | 253 ++++------- src/controllers/permission.controller.js | 122 +++++ src/controllers/role.controller.js | 161 +++++++ src/models/Permission.model.js | 3 +- src/models/Role.model.js | 3 +- src/models/user.model.js | 3 +- src/routes/authRoutes.js | 3 +- src/services/authService.js | 2 +- src/services/role.service.js | 2 +- swagger.json | 555 +++++++++++++++++++---- 11 files changed, 871 insertions(+), 268 deletions(-) diff --git a/src/app.js b/src/app.js index 63c6ce6..e29ee85 100644 --- a/src/app.js +++ b/src/app.js @@ -1,9 +1,3 @@ -import express from 'express'; -import cors from 'cors'; -import cookieparser from 'cookie-parser'; -import swaggerUi from 'swagger-ui-express'; -import swaggerSpec from '../src/utils/swagger.js'; -import authRoutes from './routes/authRoutes.js'; import express from "express"; import cors from "cors"; import cookieParser from "cookie-parser"; @@ -12,18 +6,13 @@ import rbacRoutes from './routes/rbacRoutes.js'; import dotenv from "dotenv"; import roleRoutes from "./routes/role.routes.js"; import permissionRoutes from "./routes/permission.routes.js"; +import swaggerUi from 'swagger-ui-express'; +import swaggerSpec from '../src/utils/swagger.js'; + dotenv.config(); const app = express(); -app.use( - cors({ - origin: process.env.CORS_URL, - credentials: true, - }) -); -const app = express(); - // Middleware setup app.use( cors({ @@ -32,21 +21,14 @@ app.use( }) ); -app.use(express.json({ limit: '100kb' })); -app.use(express.urlencoded({ extended: true, limit: '100kb' })); -app.use(express.static('public')); -app.use(cookieparser()); - -app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); -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()); -// Routes -app.use('/api/auth', authRoutes); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); -export { app }; +// Routes app.use("/api/roles", roleRoutes); app.use("/api/permissions", permissionRoutes); app.use("/api/auth", authRoutes) diff --git a/src/controllers/authController.js b/src/controllers/authController.js index 18d1fae..c45ec7b 100644 --- a/src/controllers/authController.js +++ b/src/controllers/authController.js @@ -1,129 +1,5 @@ import { registerUserService,loginUserService } from '../services/authService.js'; -export const registerUser = async (req, res) => { - try { - const userData = await registerUserService(req.body); - return res.status(201).json({ - success: true, - message: 'User registered successfully', - user: userData - }); - } catch (error) { - console.error('Error in registerUser:', error.message); - res.status(400).json({ success: false, message: error.message }); - } -}; - -export const loginUser = async (req, res) => { -import { loginUser } from '../services/authService.js'; -import { User } from '../models/user.model.js'; - -/** - * @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 login = async (req, res) => { - try { - const { email, password } = req.body; - const result = await loginUserService({ email, password }); - - if (!email?.trim() || !password?.trim()) { - return res - .status(400) - .json({ success: false, message: 'Email and password are required' }); - } - - const { token, refreshToken } = await loginUser(email, password); - - return res.status(200).json({ - success: true, - message: 'Login successful', - token: result.accessToken, - user: result.user, - message: 'Login successful', - token, - refreshToken, - }); - } catch (error) { - console.error('Error in loginUser:', error); - const status = error.statusCode || 400; - return res.status(status).json({ success: false, message: error.message || 'Login failed' }); - } catch (err) { - return res.status(401).json({ - success: false, - message: err.message || 'Authentication failed', - }); - } -}; - /** * @openapi * /api/auth/health: @@ -226,41 +102,106 @@ export const CheckHealth = (req, res) => { * type: string * example: Error message */ -export const UserRegistration = async (req, res) => { +export const registerUser = async (req, res) => { try { - const { username, email, fullname, password, role } = req.body; - - if ( - [username, email, fullname, password].some(field => field?.trim() === '') - ) { - return res.status(400).json({ - message: - 'All fields (username, email, fullname, password) are required', - }); - } - - const existingUser = await User.findOne({ - $or: [{ username }, { email }], - }); - if (existingUser) - return res - .status(400) - .json({ message: 'Username or email already exists' }); - - const user = await User.create({ - username: username.toLowerCase(), - email, - fullname, - password, - role: role || null, + const userData = await registerUserService(req.body); + return res.status(201).json({ + success: true, + message: 'User registered successfully', + user: userData }); + } catch (error) { + console.error('Error in registerUser:', error.message); + res.status(400).json({ success: false, message: error.message }); + } +}; - await user.save(); +/** + * @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 = async (req, res) => { + try { + const { email, password } = req.body; + const result = await loginUserService({ email, password }); - res.status(201).json({ message: 'User registered successfully' }); + return res.status(200).json({ + success: true, + message: 'Login successful', + token: result.accessToken, + user: result.user, + }); } catch (error) { - res - .status(500) - .json({ message: 'Registration failed', error: error.message }); + console.error('Error in loginUser:', error); + const status = error.statusCode || 400; + return res.status(status).json({ success: false, message: error.message || 'Login failed' }); } -}; +}; \ No newline at end of file diff --git a/src/controllers/permission.controller.js b/src/controllers/permission.controller.js index c4ffefa..827a832 100644 --- a/src/controllers/permission.controller.js +++ b/src/controllers/permission.controller.js @@ -1,5 +1,39 @@ import * as permissionService from "../services/permission.service.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 = async (req, res) => { try { const { name, description } = req.body; @@ -10,6 +44,18 @@ export const createPermission = async (req, res) => { } }; +/** + * @swagger + * /api/permissions: + * get: + * summary: Get all permissions + * tags: [Permissions] + * responses: + * 200: + * description: List of permissions + * 500: + * description: Server error + */ export const getPermissions = async (req, res) => { try { const perms = await permissionService.getPermissions(); @@ -19,6 +65,27 @@ export const getPermissions = async (req, res) => { } }; +/** + * @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 = async (req, res) => { try { const perm = await permissionService.getPermissionById(req.params.id); @@ -29,6 +96,40 @@ export const getPermissionById = async (req, res) => { } }; +/** + * @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 = async (req, res) => { try { const perm = await permissionService.updatePermission(req.params.id, req.body); @@ -39,6 +140,27 @@ export const updatePermission = async (req, res) => { } }; +/** + * @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 = async (req, res) => { try { const deleted = await permissionService.deletePermission(req.params.id); 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 0b150be..f1868bf 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -97,4 +97,5 @@ userschema.methods.refreshAccessToken = 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/routes/authRoutes.js b/src/routes/authRoutes.js index 16b86df..49ce87d 100644 --- a/src/routes/authRoutes.js +++ b/src/routes/authRoutes.js @@ -1,9 +1,10 @@ import express from 'express'; -import { registerUser,loginUser } from '../controllers/authController.js'; +import { registerUser,loginUser, CheckHealth } from '../controllers/authController.js'; const router = express.Router(); router.post('/register', registerUser); router.post('/login', loginUser); +router.get('/health',CheckHealth) export default router; diff --git a/src/services/authService.js b/src/services/authService.js index 3f462dc..e25309e 100644 --- a/src/services/authService.js +++ b/src/services/authService.js @@ -1,5 +1,5 @@ import { User } from '../models/user.model.js'; -import Role from '../models/role.model.js'; +import Role from '../models/Role.model.js'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; 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/swagger.json b/swagger.json index 47d8fc4..f770c3e 100644 --- a/swagger.json +++ b/swagger.json @@ -30,6 +30,139 @@ } ], "paths": { + "/api/auth/health": { + "get": { + "tags": [ + "Health" + ], + "summary": "Check if the API is working", + "description": "Simple health check endpoint to verify API is up and running.", + "responses": { + "200": { + "description": "API is healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "API is working good" + }, + "status": { + "type": "intger", + "example": 200 + } + } + } + } + } + } + } + } + }, + "/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": [ @@ -134,44 +267,171 @@ } } }, - "/api/auth/health": { + "/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": [ - "Health" + "Permissions" ], - "summary": "Check if the API is working", - "description": "Simple health check endpoint to verify API is up and running.", "responses": { "200": { - "description": "API is healthy", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "API is working good" - }, - "status": { - "type": "intger", - "example": 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/auth/register": { + "/api/roles": { "post": { + "summary": "Create a new role", "tags": [ - "User" + "Roles" ], - "summary": "Register a new user", - "description": "Create a new user account with username, email, fullname, password, and optional role.", "requestBody": { "required": true, "content": { @@ -179,32 +439,22 @@ "schema": { "type": "object", "required": [ - "username", - "email", - "fullname", - "password" + "name" ], "properties": { - "username": { - "type": "string", - "example": "mukesh dhadhariya" - }, - "email": { - "type": "string", - "example": "mukeshdhadhariya1@gmail.com" - }, - "fullname": { - "type": "string", - "example": "Mukesh Dhadhariya" - }, - "password": { + "name": { "type": "string", - "example": "password123" + "example": "admin" }, - "role": { - "type": "string", - "description": "Optional Role ID reference", - "example": "64f3c2a7e1f0b2a7b1c0d123" + "permissions": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64f8a91d1b9d9c123456789a", + "64f8a91d1b9d9c123456789b" + ] } } } @@ -213,60 +463,203 @@ }, "responses": { "201": { - "description": "User registered successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "User registered successfully" - } + "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": "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" - } - } - } - } + "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": "Registration failed due to server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Registration failed" + "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" }, - "error": { - "type": "string", - "example": "Error message" - } + "example": [ + "64f8a91d1b9d9c123456789a", + "64f8a91d1b9d9c123456789b" + ] } } } } } + }, + "responses": { + "200": { + "description": "Permissions assigned successfully" + }, + "400": { + "description": "Invalid request data" + }, + "404": { + "description": "Role not found" + } } } } }, - "tags": [] + "tags": [ + { + "name": "Permissions", + "description": "API for managing permissions" + }, + { + "name": "Roles", + "description": "API for managing user roles and their permissions" + } + ] }