From d2b4ede76f0f5f4d8ecd1e411ed7e0dea4f8b161 Mon Sep 17 00:00:00 2001 From: Lex Studios <233934133+Lex-Studios@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:10:43 +0000 Subject: [PATCH 1/5] feat(#77): Generate OpenAPI spec for backend API - Add swagger-jsdoc and swagger-ui-express dependencies - Create swagger.js configuration with OpenAPI 3.0 spec - Add JSDoc comments to auth, vaccination, and verify routes - Serve Swagger UI at GET /docs endpoint - Document all request/response schemas and error responses --- backend/package-lock.json | 280 +++++++++++++++++++++++++++++- backend/package.json | 2 + backend/src/app.js | 5 + backend/src/routes/auth.js | 86 +++++++++ backend/src/routes/vaccination.js | 143 +++++++++++++++ backend/src/routes/verify.js | 46 +++++ backend/src/swagger.js | 70 ++++++++ 7 files changed, 623 insertions(+), 9 deletions(-) create mode 100644 backend/src/swagger.js diff --git a/backend/package-lock.json b/backend/package-lock.json index 2d4d3b1..10d87ec 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -15,6 +15,8 @@ "express-rate-limit": "^7.2.0", "jsonwebtoken": "^9.0.2", "sql.js": "^1.12.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "winston": "^3.19.0", "zod": "^4.3.6" }, @@ -24,6 +26,68 @@ "supertest": "^6.3.4" } }, + "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/json-schema-ref-parser/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==", + "license": "Python-2.0" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -959,6 +1023,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "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/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -982,6 +1052,13 @@ "@noble/hashes": "^1.1.5" } }, + "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/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -1139,6 +1216,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", @@ -1429,7 +1512,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/bare-addon-resolve": { @@ -1568,7 +1650,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1725,6 +1806,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", @@ -1961,6 +2048,15 @@ "node": ">= 0.8" } }, + "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/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -1975,7 +2071,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/content-disposition": { @@ -2192,6 +2287,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "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/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -2370,6 +2477,15 @@ "node": ">=4" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2669,7 +2785,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -3002,7 +3117,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.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -4031,6 +4145,13 @@ "node": ">=8" } }, + "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", @@ -4043,6 +4164,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", @@ -4067,6 +4195,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -4249,7 +4383,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -4483,7 +4616,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4514,6 +4646,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/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4611,7 +4750,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5491,6 +5629,83 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "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/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.32.5", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.5.tgz", + "integrity": "sha512-7/FQfWe9A4qoyYFdAwy0chD0uDYidDp/ZT9VQ9LZlgD4AnnHJk8/+ytAA1HkJYOPySmK6helPDdJQMlcumt7HA==", + "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/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5726,6 +5941,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5840,7 +6064,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -5874,6 +6097,15 @@ "dev": true, "license": "ISC" }, + "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", @@ -5916,6 +6148,36 @@ "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" + } + }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/backend/package.json b/backend/package.json index 3ffeedd..c307a51 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,8 @@ "express-rate-limit": "^7.2.0", "jsonwebtoken": "^9.0.2", "sql.js": "^1.12.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "winston": "^3.19.0", "zod": "^4.3.6" }, diff --git a/backend/src/app.js b/backend/src/app.js index 177cefd..03af2a2 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -2,9 +2,11 @@ require('dotenv').config(); const config = require('./config'); const express = require('express'); const cors = require('cors'); +const swaggerUi = require('swagger-ui-express'); const logger = require('./logger'); const { initDb } = require('./indexer/db'); const { startPoller, stopPoller } = require('./indexer/poller'); +const swaggerSpec = require('./swagger'); const authRoutes = require('./routes/auth'); const vaccinationRoutes = require('./routes/vaccination'); @@ -17,6 +19,9 @@ const app = express(); app.use(cors()); app.use(express.json({ limit: config.BODY_LIMIT })); +// Swagger UI +app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + // Request logging middleware app.use((req, _res, next) => { logger.info('request', { method: req.method, path: req.path }); diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 1a65ee2..abe3cdf 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -25,6 +25,46 @@ const verifySchema = z.object({ nonce: z.string().min(1, 'nonce is required'), }); +/** + * @swagger + * /auth/sep10: + * post: + * summary: Generate SEP-10 challenge transaction + * tags: + * - Authentication + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * public_key: + * type: string + * description: Stellar public key + * required: + * - public_key + * responses: + * 200: + * description: Challenge transaction generated + * content: + * application/json: + * schema: + * type: object + * properties: + * transaction: + * type: string + * nonce: + * type: string + * 400: + * description: Invalid public key + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 429: + * description: Rate limit exceeded + */ // POST /auth/sep10 — generate challenge router.post('/sep10', sep10Limiter, validate(sep10Schema), async (req, res) => { const { public_key } = req.body; @@ -37,6 +77,52 @@ router.post('/sep10', sep10Limiter, validate(sep10Schema), async (req, res) => { } }); +/** + * @swagger + * /auth/verify: + * post: + * summary: Verify signed SEP-10 challenge and issue JWT + * tags: + * - Authentication + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * transaction: + * type: string + * description: Signed challenge transaction + * nonce: + * type: string + * description: Nonce from challenge + * required: + * - transaction + * - nonce + * responses: + * 200: + * description: JWT issued successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * token: + * type: string + * description: JWT token + * publicKey: + * type: string + * role: + * type: string + * enum: [patient, issuer] + * 401: + * description: Invalid signature or nonce + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ // POST /auth/verify — verify signed challenge, issue JWT router.post('/verify', validate(verifySchema), (req, res) => { const { transaction, nonce } = req.body; diff --git a/backend/src/routes/vaccination.js b/backend/src/routes/vaccination.js index bc674ed..f1c5423 100644 --- a/backend/src/routes/vaccination.js +++ b/backend/src/routes/vaccination.js @@ -29,6 +29,64 @@ const revokeSchema = z.object({ token_id: z.union([z.string(), z.number()]).transform((val) => String(val)), }); +/** + * @swagger + * /vaccination/issue: + * post: + * summary: Issue a vaccination NFT (issuer only) + * tags: + * - Vaccination + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * patient_address: + * type: string + * description: Stellar address of patient + * vaccine_name: + * type: string + * date_administered: + * type: string + * format: date-time + * required: + * - patient_address + * - vaccine_name + * - date_administered + * responses: + * 200: + * description: Vaccination NFT issued successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * tokenId: + * type: string + * transactionHash: + * type: string + * ledger: + * type: number + * timestamp: + * type: string + * format: date-time + * 401: + * description: Unauthorized + * 403: + * description: Forbidden - issuer role required + * 500: + * description: Contract invocation failed + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ // POST /vaccination/issue — mint NFT (issuer only) router.post( '/issue', @@ -77,6 +135,50 @@ router.post( } }); +/** + * @swagger + * /vaccination/revoke: + * post: + * summary: Revoke a vaccination record (issuer only) + * tags: + * - Vaccination + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * token_id: + * type: string + * description: Token ID to revoke + * required: + * - token_id + * responses: + * 200: + * description: Vaccination record revoked + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * token_id: + * type: string + * 401: + * description: Unauthorized + * 403: + * description: Forbidden - issuer role required + * 500: + * description: Contract invocation failed + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ // POST /vaccination/revoke — revoke a vaccination record (issuer or admin only) router.post( '/revoke', @@ -116,6 +218,47 @@ router.post( } ); +/** + * @swagger + * /vaccination/{wallet}: + * get: + * summary: Fetch all vaccination records for a wallet + * tags: + * - Vaccination + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: wallet + * required: true + * schema: + * type: string + * description: Stellar wallet address + * responses: + * 200: + * description: Vaccination records retrieved + * content: + * application/json: + * schema: + * type: object + * properties: + * wallet: + * type: string + * vaccinated: + * type: boolean + * records: + * type: array + * items: + * $ref: '#/components/schemas/VaccinationRecord' + * 401: + * description: Unauthorized + * 500: + * description: Contract query failed + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ // GET /vaccination/:wallet — fetch all records for a wallet router.get('/:wallet', authMiddleware, validateStellarPublicKey('params', 'wallet', 'wallet'), async (req, res) => { const { wallet } = req.params; diff --git a/backend/src/routes/verify.js b/backend/src/routes/verify.js index 531f58d..346541a 100644 --- a/backend/src/routes/verify.js +++ b/backend/src/routes/verify.js @@ -29,6 +29,52 @@ function adaptiveLimiter(req, res, next) { return verifyLimiter(req, res, next); } +/** + * @swagger + * /verify/{wallet}: + * get: + * summary: Public vaccination status verification + * tags: + * - Verification + * security: + * - bearerAuth: [] + * - apiKeyAuth: [] + * parameters: + * - in: path + * name: wallet + * required: true + * schema: + * type: string + * description: Stellar wallet address to verify + * responses: + * 200: + * description: Vaccination status retrieved + * content: + * application/json: + * schema: + * type: object + * properties: + * wallet: + * type: string + * vaccinated: + * type: boolean + * record_count: + * type: number + * records: + * type: array + * items: + * $ref: '#/components/schemas/VaccinationRecord' + * 401: + * description: Unauthorized - JWT or API key required + * 429: + * description: Rate limit exceeded + * 500: + * description: Contract query failed + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ // GET /verify/:wallet — JWT or verifier API key router.get( '/:wallet', diff --git a/backend/src/swagger.js b/backend/src/swagger.js new file mode 100644 index 0000000..3237ddb --- /dev/null +++ b/backend/src/swagger.js @@ -0,0 +1,70 @@ +const swaggerJsdoc = require('swagger-jsdoc'); + +const options = { + definition: { + openapi: '3.0.0', + info: { + title: 'VacciChain Backend API', + version: '1.0.0', + description: 'Blockchain-based vaccination records on Stellar — soulbound, verifiable, tamper-proof.', + }, + servers: [ + { + url: 'http://localhost:4000', + description: 'Development server', + }, + { + url: 'http://backend:4000', + description: 'Docker server', + }, + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + apiKeyAuth: { + type: 'apiKey', + in: 'header', + name: 'X-API-Key', + }, + }, + schemas: { + Error: { + type: 'object', + properties: { + error: { + type: 'string', + }, + }, + }, + VaccinationRecord: { + type: 'object', + properties: { + vaccine_name: { + type: 'string', + }, + date_administered: { + type: 'string', + format: 'date-time', + }, + issuer: { + type: 'string', + }, + timestamp: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + }, + }, + apis: ['./src/routes/*.js'], +}; + +const specs = swaggerJsdoc(options); + +module.exports = specs; From affff9262eaba01e47dbe04daf323970cbae8de5 Mon Sep 17 00:00:00 2001 From: Lex Studios <233934133+Lex-Studios@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:25:07 +0000 Subject: [PATCH 2/5] feat(#79): Create contributor onboarding guide - Expand CONTRIBUTING.md with local setup, branching strategy, PR process, and commit conventions - Add PR template at .github/pull_request_template.md - Add bug report issue template - Document Conventional Commits format with examples - Include code of conduct expectations in CONTRIBUTING.md --- .github/ISSUE_TEMPLATE/bug_report.md | 56 +++++++ .github/pull_request_template.md | 48 ++++++ CONTRIBUTING.md | 226 ++++++++++++++++++++++++++- 3 files changed, 322 insertions(+), 8 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d39b06c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,56 @@ +--- +name: Bug Report +about: Report a bug to help us improve +title: "[BUG] " +labels: bug +assignees: '' + +--- + +## Description + +A clear and concise description of what the bug is. + +## Steps to Reproduce + +Steps to reproduce the behavior: + +1. +2. +3. + +## Expected Behavior + +A clear and concise description of what you expected to happen. + +## Actual Behavior + +A clear and concise description of what actually happened. + +## Environment + +- **OS**: (e.g., macOS, Linux, Windows) +- **Node.js version**: (if applicable) +- **Python version**: (if applicable) +- **Rust version**: (if applicable) +- **Docker version**: (if applicable) + +## Screenshots + +If applicable, add screenshots to help explain your problem. + +## Logs + +If applicable, paste relevant error logs or stack traces: + +``` +[paste logs here] +``` + +## Additional Context + +Add any other context about the problem here. + +## Possible Solution + +If you have a suggestion for how to fix this, please describe it here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..92337c5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,48 @@ +## Description + +Please include a summary of the changes and related context. + +Closes #(issue number) + +## Type of Change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] Documentation update + +## Changes Made + +- +- +- + +## Testing + +Please describe the tests you ran and how to reproduce them: + +- [ ] Unit tests pass +- [ ] Integration tests pass +- [ ] Manual testing completed + +**Test Coverage:** +- + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests passed locally with my changes +- [ ] Any dependent changes have been merged and published + +## Screenshots (if applicable) + +If your changes include UI modifications, please add screenshots here. + +## Additional Notes + +Any additional information that reviewers should know. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60fc161..b14c9b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,224 @@ Thank you for your interest in contributing to VacciChain! This document provides guidelines and instructions for contributing to our project. +## Table of Contents + +- [Local Setup](#local-setup) +- [Branching Strategy](#branching-strategy) +- [Pull Request Process](#pull-request-process) +- [Commit Conventions](#commit-conventions) +- [Code of Conduct](#code-of-conduct) +- [Docker Base Image Pinning](#docker-base-image-pinning) + +## Local Setup + +### Prerequisites + +- Node.js 18+ +- Python 3.11+ +- Rust + `wasm32-unknown-unknown` target +- Soroban CLI +- Docker + Docker Compose +- Freighter Wallet (for testing) + +### Installation Steps + +1. **Clone the repository** + ```bash + git clone https://github.com/dev-fatima-24/VacciChain.git + cd VacciChain + ``` + +2. **Copy environment configuration** + ```bash + cp .env.example .env + # Fill in your Stellar keys and contract IDs + ``` + +3. **Install backend dependencies** + ```bash + cd backend + npm install + cd .. + ``` + +4. **Install frontend dependencies** + ```bash + cd frontend + npm install + cd .. + ``` + +5. **Install Python service dependencies** + ```bash + cd python-service + pip install -r requirements.txt + cd .. + ``` + +6. **Deploy smart contract (if needed)** + ```bash + cd contracts + make build + make deploy + cd .. + ``` + +7. **Run locally** + ```bash + # Terminal 1: Backend + cd backend && npm run dev + + # Terminal 2: Frontend + cd frontend && npm run dev + + # Terminal 3: Python service + cd python-service && uvicorn main:app --port 8001 + ``` + + Or use Docker Compose: + ```bash + docker compose up --build + ``` + +## Branching Strategy + +We follow a **feature branch** workflow: + +- **main**: Production-ready code. Protected branch, requires PR review. +- **Feature branches**: `feature/` or `issues/` + - Example: `feature/openapi-spec` or `issues/77-79-82-84` + - Branch from `main` + - Prefix with issue numbers if addressing GitHub issues + +### Creating a Branch + +```bash +git checkout main +git pull origin main +git checkout -b issues/77-79-82-84 +``` + +## Pull Request Process + +### Before Submitting + +1. **Ensure tests pass** + ```bash + # Backend + cd backend && npm test + + # Contracts + cd contracts && cargo test + + # Python service + cd python-service && pytest + ``` + +2. **Run linters** (if configured) + ```bash + cd backend && npm run lint # if available + ``` + +3. **Update documentation** if your changes affect user-facing features + +4. **Commit your changes** using [Conventional Commits](#commit-conventions) + +### Submitting a PR + +1. **Push your branch** + ```bash + git push -u origin issues/77-79-82-84 + ``` + +2. **Create a pull request** on GitHub + - Use the PR template (auto-populated from `.github/pull_request_template.md`) + - Link related issues: `Closes #77, #79, #82, #84` + - Provide a clear description of changes + - Include testing notes + +3. **Address review feedback** + - Make requested changes + - Push updates to the same branch + - Respond to comments + +4. **Merge** once approved + - Use "Squash and merge" for single-commit PRs + - Use "Create a merge commit" for multi-commit PRs with logical separation + +## Commit Conventions + +We follow **Conventional Commits** for clear, semantic commit messages. + +### Format + +``` +(): + + + +