From d07940a229f399543fb91cb90154833c989d57a6 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Sun, 22 Sep 2024 15:40:37 -0600 Subject: [PATCH 01/11] added autogenerating swagger docs --- package-lock.json | 677 ++++- packages/backend/jest.config.js | 2 +- packages/backend/package.json | 13 +- .../src/Controllers/elections.controllers.ts | 2 +- packages/backend/src/Express/index.d.ts | 22 + packages/backend/src/IRequest.ts | 5 +- .../backend/src/OpenApi/generateOpenApi.ts | 4 + packages/backend/src/OpenApi/openapi.json | 2477 +++++++++++++++++ packages/backend/src/OpenApi/swagger.ts | 27 + .../backend/src/Routes/elections.routes.js | 74 - .../backend/src/Routes/elections.routes.ts | 674 +++++ packages/backend/src/Routes/routes.yaml | 0 packages/backend/src/app.ts | 4 +- packages/backend/tsconfig.json | 6 +- packages/shared/fixSchemaRefs.ts | 31 + packages/shared/package.json | 9 +- packages/shared/schema.json | 1715 ++++++++++++ packages/shared/src/domain_model/Election.ts | 2 +- 18 files changed, 5504 insertions(+), 240 deletions(-) create mode 100644 packages/backend/src/Express/index.d.ts create mode 100644 packages/backend/src/OpenApi/generateOpenApi.ts create mode 100644 packages/backend/src/OpenApi/openapi.json create mode 100644 packages/backend/src/OpenApi/swagger.ts delete mode 100644 packages/backend/src/Routes/elections.routes.js create mode 100644 packages/backend/src/Routes/elections.routes.ts create mode 100644 packages/backend/src/Routes/routes.yaml create mode 100644 packages/shared/fixSchemaRefs.ts create mode 100644 packages/shared/schema.json diff --git a/package-lock.json b/package-lock.json index f9fefe1d..3aecb953 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,46 @@ "node": ">=6.0.0" } }, + "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==", + "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==", + "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==" + }, + "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==", + "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/@aws-crypto/crc32": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", @@ -1697,9 +1737,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1711,9 +1748,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2959,7 +2993,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2976,8 +3009,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.23", @@ -2989,6 +3021,11 @@ "@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==" + }, "node_modules/@mui/base": { "version": "5.0.0-beta.38", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.38.tgz", @@ -4434,34 +4471,22 @@ "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4677,6 +4702,11 @@ "pretty-format": "^27.0.0" } }, + "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==" + }, "node_modules/@types/luxon": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", @@ -4694,9 +4724,9 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/node": { - "version": "16.18.84", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.84.tgz", - "integrity": "sha512-mtn6ixzrUK5IMf6gyyMVUsm0TIeF3IYpUr3i0HHTuPJVbdZ6kc93poZ+wCkFNtxXoP/tyGrdVPOL6/WqGXjfXw==" + "version": "16.18.108", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.108.tgz", + "integrity": "sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==" }, "node_modules/@types/parse-json": { "version": "4.0.2", @@ -4814,6 +4844,22 @@ "@types/superagent": "*" } }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz", + "integrity": "sha512-UVSiGYXa5IzdJJG3hrc86e8KdZWLYxyEsVoUI4iPXc7CO4VZ3AfNP8d/8+hrDRIqz+HAaSMtZSqAsF3Nq2X/Dg==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -5345,6 +5391,11 @@ "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==" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5480,7 +5531,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -5689,10 +5739,7 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "node_modules/cron-parser": { "version": "4.9.0", @@ -5979,9 +6026,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=0.3.1" } @@ -6071,8 +6115,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -6191,7 +6234,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, "engines": { "node": ">=6" } @@ -6979,7 +7021,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7427,7 +7468,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -8738,6 +8778,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "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==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8748,6 +8793,11 @@ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" }, + "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==" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -8779,6 +8829,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "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==" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -8863,8 +8918,7 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/makeerror": { "version": "1.0.12", @@ -9183,6 +9237,12 @@ "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==", + "peer": true + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -9291,6 +9351,11 @@ "node": ">= 0.8" } }, + "node_modules/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -10100,7 +10165,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10322,6 +10386,14 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10688,7 +10760,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10877,6 +10948,91 @@ "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==", + "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/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "engines": { + "node": ">= 6" + } + }, + "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": "Glob versions prior to v9 are no longer supported", + "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-jsdoc/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==", + "engines": { + "node": ">= 6" + } + }, + "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==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", + "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==" + }, + "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==", + "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", @@ -11024,9 +11180,6 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11069,9 +11222,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=0.4.0" } @@ -11079,10 +11229,7 @@ "node_modules/ts-node/node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "node_modules/tslib": { "version": "2.0.1", @@ -11168,11 +11315,48 @@ "node": ">=14.17" } }, + "node_modules/typescript-json-schema": { + "version": "0.65.1", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.65.1.tgz", + "integrity": "sha512-tuGH7ff2jPaUYi6as3lHyHcKpSmXIqN7/mu50x3HlYn0EHzLpmt3nplZ7EuhUkO0eqDRc9GqWNkfjgBPIS9kxg==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^18.11.9", + "glob": "^7.1.7", + "path-equal": "^1.2.5", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~5.5.0", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/@types/node": { + "version": "18.19.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz", + "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/universalify": { "version": "2.0.1", @@ -11257,10 +11441,7 @@ "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/v8-to-istanbul": { "version": "9.2.0", @@ -11282,6 +11463,14 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -11474,7 +11663,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -11563,7 +11751,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -11586,7 +11773,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -11604,7 +11790,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -11613,9 +11798,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=6" } @@ -11631,6 +11813,34 @@ "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==", + "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==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "packages/backend": { "name": "@equal-vote/star-vote-backend", "version": "1.0.0", @@ -11645,7 +11855,7 @@ "@types/express": "^4.17.13", "@types/jest": "^27.4.1", "@types/luxon": "^3.3.0", - "@types/node": "^16.11.26", + "@types/node": "^16.18.108", "@types/pg": "^8.10.2", "@types/socket.io": "^3.0.2", "axios": "^1.6.8", @@ -11665,10 +11875,14 @@ "pg-format": "^1.0.4", "qs": "^6.10.3", "seedrandom": "^3.0.5", - "socket.io": "^4.7.5" + "socket.io": "^4.7.5", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@types/supertest": "^2.0.12", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.6", "jest": "^29.7.0", "rimraf": "^5.0.5", "supertest": "^6.2.2", @@ -11953,7 +12167,8 @@ "@equal-vote/star-vote-backend": "^1.0.0", "@equal-vote/star-vote-frontend": "^0.1.0", "socket.io": "^4.7.5", - "socket.io-client": "^4.7.5" + "socket.io-client": "^4.7.5", + "typescript-json-schema": "^0.65.1" }, "devDependencies": { "rimraf": "^5.0.5", @@ -11980,6 +12195,40 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@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==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "@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==" + }, + "@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==" + }, + "@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "requires": { + "@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" + } + }, "@aws-crypto/crc32": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", @@ -13439,9 +13688,6 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -13450,9 +13696,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -13639,10 +13882,12 @@ "@types/express": "^4.17.13", "@types/jest": "^27.4.1", "@types/luxon": "^3.3.0", - "@types/node": "^16.11.26", + "@types/node": "^16.18.108", "@types/pg": "^8.10.2", "@types/socket.io": "^3.0.2", "@types/supertest": "^2.0.12", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.6", "axios": "^1.6.8", "cookie-parser": "^1.4.6", "cors": "^2.8.5", @@ -13664,6 +13909,8 @@ "seedrandom": "^3.0.5", "socket.io": "^4.7.5", "supertest": "^6.2.2", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "ts-jest": "^29.1.2", "tsx": "^4.7.1", "typescript": "5.3.x" @@ -13830,7 +14077,8 @@ "rimraf": "^5.0.5", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "typescript-json-schema": "^0.65.1" } }, "@esbuild/aix-ppc64": { @@ -14495,8 +14743,7 @@ "@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" }, "@jridgewell/set-array": { "version": "1.1.2", @@ -14507,8 +14754,7 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { "version": "0.3.23", @@ -14520,6 +14766,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@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==" + }, "@mui/base": { "version": "5.0.0-beta.38", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.38.tgz", @@ -15677,34 +15928,22 @@ "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" }, "@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, "@types/babel__core": { "version": "7.20.5", @@ -15920,6 +16159,11 @@ "pretty-format": "^27.0.0" } }, + "@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==" + }, "@types/luxon": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", @@ -15937,9 +16181,9 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "@types/node": { - "version": "16.18.84", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.84.tgz", - "integrity": "sha512-mtn6ixzrUK5IMf6gyyMVUsm0TIeF3IYpUr3i0HHTuPJVbdZ6kc93poZ+wCkFNtxXoP/tyGrdVPOL6/WqGXjfXw==" + "version": "16.18.108", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.108.tgz", + "integrity": "sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==" }, "@types/parse-json": { "version": "4.0.2", @@ -16056,6 +16300,22 @@ "@types/superagent": "*" } }, + "@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "@types/swagger-ui-express": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz", + "integrity": "sha512-UVSiGYXa5IzdJJG3hrc86e8KdZWLYxyEsVoUI4iPXc7CO4VZ3AfNP8d/8+hrDRIqz+HAaSMtZSqAsF3Nq2X/Dg==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -16452,6 +16712,11 @@ "set-function-length": "^1.2.1" } }, + "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==" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -16537,7 +16802,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -16697,10 +16961,7 @@ "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "cron-parser": { "version": "4.9.0", @@ -16894,10 +17155,7 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, "diff-sequences": { "version": "27.5.1", @@ -16966,8 +17224,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -17063,8 +17320,7 @@ "escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==" }, "escape-html": { "version": "1.0.3", @@ -17634,8 +17890,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.2.4", @@ -17942,8 +18197,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-fn": { "version": "2.1.0", @@ -18931,6 +19185,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "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==" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -18941,6 +19200,11 @@ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -18972,6 +19236,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -19037,8 +19306,7 @@ "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "makeerror": { "version": "1.0.12", @@ -19259,6 +19527,12 @@ "mimic-fn": "^2.1.0" } }, + "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==", + "peer": true + }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -19331,6 +19605,11 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -19915,8 +20194,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "resolve": { "version": "1.22.8", @@ -20053,6 +20331,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -20359,7 +20642,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -20494,6 +20776,65 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "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==", + "requires": { + "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" + }, + "dependencies": { + "commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "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" + } + }, + "yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==" + } + } + }, + "swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "requires": { + "@apidevtools/swagger-parser": "10.0.3" + } + }, + "swagger-ui-dist": { + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", + "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==" + }, + "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==", + "requires": { + "swagger-ui-dist": ">=5.0.0" + } + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -20592,9 +20933,6 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "optional": true, - "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20614,18 +20952,12 @@ "acorn-walk": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==" }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" } } }, @@ -20683,11 +21015,40 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==" }, + "typescript-json-schema": { + "version": "0.65.1", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.65.1.tgz", + "integrity": "sha512-tuGH7ff2jPaUYi6as3lHyHcKpSmXIqN7/mu50x3HlYn0EHzLpmt3nplZ7EuhUkO0eqDRc9GqWNkfjgBPIS9kxg==", + "requires": { + "@types/json-schema": "^7.0.9", + "@types/node": "^18.11.9", + "glob": "^7.1.7", + "path-equal": "^1.2.5", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~5.5.0", + "yargs": "^17.1.1" + }, + "dependencies": { + "@types/node": { + "version": "18.19.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz", + "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==", + "requires": { + "undici-types": "~5.26.4" + } + }, + "typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==" + } + } + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "universalify": { "version": "2.0.1", @@ -20736,10 +21097,7 @@ "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "v8-to-istanbul": { "version": "9.2.0", @@ -20760,6 +21118,11 @@ } } }, + "validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -20920,7 +21283,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -20972,8 +21334,7 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "3.1.1", @@ -20990,7 +21351,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -21004,21 +21364,36 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "requires": { + "commander": "^9.4.1", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true + } + } } } } diff --git a/packages/backend/jest.config.js b/packages/backend/jest.config.js index 2ff03bc4..dc348eea 100644 --- a/packages/backend/jest.config.js +++ b/packages/backend/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export { transform: { '^.+\\.ts?$': [ 'ts-jest', diff --git a/packages/backend/package.json b/packages/backend/package.json index a80106c3..ac581ee4 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -7,12 +7,13 @@ "test": "jest --forceExit --detectOpenHandles", "start": "npm run build && node ./build/src/index.js", "dev": "tsx watch ./src", - "build": "tsc --project ./", + "build": "tsc --project ./ && npm run generate:openapi", "clean": "npx rimraf build node_modules ../node_modules", "migrate:latest": "node ./build/src/Migrators/migrate-to-latest.js", "migrate:up": "node ./build/src/Migrators/migrate-up.js", "migrate:down": "node ./build/src/Migrators/migrate-down.js", - "db_sandbox": "npm run-script build && node ./build/src/test/database_sandbox.js" + "db_sandbox": "npm run-script build && node ./build/src/test/database_sandbox.js", + "generate:openapi": "ts-node ./src/OpenApi/generateOpenApi.ts" }, "keywords": [], "author": "", @@ -27,7 +28,7 @@ "@types/express": "^4.17.13", "@types/jest": "^27.4.1", "@types/luxon": "^3.3.0", - "@types/node": "^16.11.26", + "@types/node": "^16.18.108", "@types/pg": "^8.10.2", "@types/socket.io": "^3.0.2", "axios": "^1.6.8", @@ -47,10 +48,14 @@ "pg-format": "^1.0.4", "qs": "^6.10.3", "seedrandom": "^3.0.5", - "socket.io": "^4.7.5" + "socket.io": "^4.7.5", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@types/supertest": "^2.0.12", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.6", "jest": "^29.7.0", "rimraf": "^5.0.5", "supertest": "^6.2.2", diff --git a/packages/backend/src/Controllers/elections.controllers.ts b/packages/backend/src/Controllers/elections.controllers.ts index b8c2dba4..795e297a 100644 --- a/packages/backend/src/Controllers/elections.controllers.ts +++ b/packages/backend/src/Controllers/elections.controllers.ts @@ -156,7 +156,7 @@ const returnElection = async (req: any, res: any, next: any) => { res.json({ election: election, voterAuth: { authorized_voter: voterAuthorization.authorized_voter, has_voted: voterAuthorization.has_voted, required: voterAuthorization.required, roles: req.user_auth.roles, permissions: req.user_auth.permissions } }) } -module.exports = { +export { returnElection, getElectionByID, electionSpecificAuth, diff --git a/packages/backend/src/Express/index.d.ts b/packages/backend/src/Express/index.d.ts new file mode 100644 index 00000000..4d56eaeb --- /dev/null +++ b/packages/backend/src/Express/index.d.ts @@ -0,0 +1,22 @@ +import { Election } from '@equal-vote/star-vote-shared/domain_model/Election'; +import { roles } from '@equal-vote/star-vote-shared/domain_model/roles'; +import { permission, permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; + +type p = keyof typeof permissions +export {} +declare global { + namespace Express { + interface Request { + contextId?: string; + logPrefix?: string; + election: Election; + user?: any; + user_auth: { + roles: roles[]; + permissions: p[] + } + authorized_voter?: boolean; + has_voted?: boolean; + } + } +} diff --git a/packages/backend/src/IRequest.ts b/packages/backend/src/IRequest.ts index 3b5a158e..3a711a04 100644 --- a/packages/backend/src/IRequest.ts +++ b/packages/backend/src/IRequest.ts @@ -9,7 +9,6 @@ type p = keyof typeof permissions export interface IRequest extends Request { contextId?: string; logPrefix?: string; - election?: Election; user?: any; } @@ -19,8 +18,8 @@ export interface IElectionRequest extends IRequest { roles: roles[]; permissions: p[] } - authorized_voter?: Boolean; - has_voted?: Boolean; + authorized_voter?: boolean; + has_voted?: boolean; } diff --git a/packages/backend/src/OpenApi/generateOpenApi.ts b/packages/backend/src/OpenApi/generateOpenApi.ts new file mode 100644 index 00000000..66b2b7c3 --- /dev/null +++ b/packages/backend/src/OpenApi/generateOpenApi.ts @@ -0,0 +1,4 @@ +import fs from 'fs'; +import openapiSpecification from './swagger'; + +fs.writeFileSync('./src/OpenApi/openapi.json', JSON.stringify(openapiSpecification, null, 2)); diff --git a/packages/backend/src/OpenApi/openapi.json b/packages/backend/src/OpenApi/openapi.json new file mode 100644 index 00000000..b67709be --- /dev/null +++ b/packages/backend/src/OpenApi/openapi.json @@ -0,0 +1,2477 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "API Documentation", + "version": "1.0.0" + }, + "components": { + "schemas": { + "Ballot": { + "properties": { + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "date_submitted": { + "type": "number" + }, + "election_id": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/BallotAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "status": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "user_id": { + "type": "string" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/Vote" + }, + "type": "array" + } + }, + "type": "object" + }, + "BallotAction": { + "properties": { + "action_type": { + "type": "string" + }, + "actor": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + }, + "type": "object" + }, + "Candidate": { + "properties": { + "bio": { + "type": "string" + }, + "candidate_id": { + "type": "string" + }, + "candidate_name": { + "type": "string" + }, + "candidate_url": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "party": { + "type": "string" + }, + "partyUrl": { + "type": "string" + }, + "party_url": { + "type": "string" + }, + "photo_filename": { + "type": "string" + } + }, + "type": "object" + }, + "Credentials": { + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "type": "object" + }, + "Election": { + "properties": { + "admin_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "audit_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_key": { + "type": "string" + }, + "claim_key_hash": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "credential_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "election_id": { + "type": "string" + }, + "end_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "frontend_url": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "owner_id": { + "type": "string" + }, + "races": { + "items": { + "$ref": "#/components/schemas/Race" + }, + "type": "array" + }, + "settings": { + "$ref": "#/components/schemas/ElectionSettings" + }, + "start_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "state": { + "$ref": "#/components/schemas/ElectionState" + }, + "support_email": { + "type": "string" + }, + "title": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "type": "object" + }, + "ElectionResults": { + "anyOf": [ + { + "properties": { + "results": { + "$ref": "#/components/schemas/starResults" + }, + "votingMethod": { + "const": "STAR", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/allocatedScoreResults" + }, + "votingMethod": { + "const": "STAR_PR", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/approvalResults" + }, + "votingMethod": { + "const": "Approval", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/rankedRobinResults" + }, + "votingMethod": { + "const": "RankedRobin", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/irvResults" + }, + "votingMethod": { + "const": "IRV", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/pluralityResults" + }, + "votingMethod": { + "const": "Plurality", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/irvResults" + }, + "votingMethod": { + "const": "STV", + "type": "string" + } + }, + "type": "object" + } + ] + }, + "ElectionRoll": { + "properties": { + "address": { + "type": "string" + }, + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "election_id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "email_data": { + "properties": { + "inviteResponse": {}, + "reminderResponse": {} + }, + "type": "object" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/ElectionRollAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "registration": {}, + "state": { + "$ref": "#/components/schemas/ElectionRollState" + }, + "submitted": { + "type": "boolean" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "voter_id": { + "type": "string" + } + }, + "type": "object" + }, + "ElectionRollAction": { + "properties": { + "action_type": { + "type": "string" + }, + "actor": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + }, + "type": "object" + }, + "ElectionRollState": { + "enum": [ + "approved", + "flagged", + "registered", + "invalid" + ], + "type": "string" + }, + "ElectionSettings": { + "properties": { + "ballot_updates": { + "type": "boolean" + }, + "break_ties_randomly": { + "type": "boolean" + }, + "invitation": { + "enum": [ + "address", + "email" + ], + "type": "string" + }, + "max_rankings": { + "type": "number" + }, + "public_results": { + "type": "boolean" + }, + "random_candidate_order": { + "type": "boolean" + }, + "reminders": { + "type": "boolean" + }, + "require_instruction_confirmation": { + "type": "boolean" + }, + "term_type": { + "enum": [ + "election", + "poll" + ], + "type": "string" + }, + "time_zone": { + "type": "string" + }, + "voter_access": { + "enum": [ + "closed", + "open", + "registration" + ], + "type": "string" + }, + "voter_authentication": { + "$ref": "#/components/schemas/authentication" + } + }, + "type": "object" + }, + "ElectionState": { + "enum": [ + "archived", + "closed", + "draft", + "finalized", + "open" + ], + "type": "string" + }, + "Email": { + "type": "string" + }, + "NewBallot": { + "properties": { + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "date_submitted": { + "type": "number" + }, + "election_id": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/BallotAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "status": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "user_id": { + "type": "string" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/Vote" + }, + "type": "array" + } + }, + "type": "object" + }, + "NewElection": { + "properties": { + "admin_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "audit_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_key": { + "type": "string" + }, + "claim_key_hash": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "credential_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "election_id": { + "type": "string" + }, + "end_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "frontend_url": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "owner_id": { + "type": "string" + }, + "races": { + "items": { + "$ref": "#/components/schemas/Race" + }, + "type": "array" + }, + "settings": { + "$ref": "#/components/schemas/ElectionSettings" + }, + "start_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "state": { + "$ref": "#/components/schemas/ElectionState" + }, + "support_email": { + "type": "string" + }, + "title": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "type": "object" + }, + "NewPassword": { + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + }, + "type": "object" + }, + "Omit": { + "type": "object" + }, + "Omit_1": { + "type": "object" + }, + "Partial>": { + "type": "object" + }, + "PartialBy": { + "allOf": [ + { + "$ref": "#/components/schemas/Omit_1" + }, + { + "$ref": "#/components/schemas/Partial>" + } + ] + }, + "Race": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/Candidate" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "num_winners": { + "type": "number" + }, + "precincts": { + "items": { + "additionalProperties": false, + "patternProperties": { + "^[0-9]+$": { + "type": "string" + } + }, + "properties": { + "length": { + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + }, + "race_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "voting_method": { + "$ref": "#/components/schemas/VotingMethod" + } + }, + "type": "object" + }, + "Score": { + "properties": { + "candidate_id": { + "type": "string" + }, + "score": { + "type": "number" + } + }, + "type": "object" + }, + "TermType": { + "enum": [ + "election", + "poll" + ], + "type": "string" + }, + "Uid": { + "type": "string" + }, + "Vote": { + "properties": { + "race_id": { + "type": "string" + }, + "scores": { + "items": { + "$ref": "#/components/schemas/Score" + }, + "type": "array" + } + }, + "type": "object" + }, + "VoterAuth": { + "properties": { + "authorized_voter": { + "type": "boolean" + }, + "has_voted": { + "type": "boolean" + }, + "permissions": { + "items": { + "enum": [ + "canAddToElectionRoll", + "canApproveElectionRoll", + "canDeleteAllBallots", + "canDeleteElection", + "canDeleteElectionRoll", + "canEditBallot", + "canEditElection", + "canEditElectionRoles", + "canEditElectionRoll", + "canEditElectionState", + "canFlagBallot", + "canFlagElectionRoll", + "canInvalidateBallot", + "canInvalidateElectionRoll", + "canSendEmails", + "canUnflagElectionRoll", + "canViewBallot", + "canViewBallots", + "canViewElection", + "canViewElectionRoll", + "canViewElectionRollIDs", + "canViewPreliminaryResults" + ], + "type": "string" + }, + "type": "array" + }, + "required": { + "type": "string" + }, + "roles": { + "items": { + "$ref": "#/components/schemas/roles" + }, + "type": "array" + } + }, + "type": "object" + }, + "VotingMethod": { + "enum": [ + "Approval", + "IRV", + "Plurality", + "RankedRobin", + "STAR", + "STAR_PR", + "STV" + ], + "type": "string" + }, + "allocatedScoreResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/allocatedScoreSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "type": "array" + } + }, + "type": "object" + }, + "allocatedScoreSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "noPreferenceStars": { + "items": { + "type": "number" + }, + "type": "array" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "spentAboves": { + "items": { + "type": "number" + }, + "type": "array" + }, + "splitPoints": { + "items": { + "type": "number" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + }, + "weight_on_splits": { + "items": { + "type": "number" + }, + "type": "array" + }, + "weightedScoresByRound": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + } + }, + "type": "object" + }, + "approvalResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/approvalSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "approvalSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "authentication": { + "properties": { + "address": { + "type": "boolean" + }, + "email": { + "type": "boolean" + }, + "ip_address": { + "type": "boolean" + }, + "phone": { + "type": "boolean" + }, + "registration_api_endpoint": { + "type": "string" + }, + "registration_data": { + "items": [ + { + "$ref": "#/components/schemas/registration_field" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + "voter_id": { + "type": "boolean" + } + }, + "type": "object" + }, + "ballot": { + "items": { + "type": "number" + }, + "type": "array" + }, + "ballots": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "candidate": { + "properties": { + "index": { + "type": "number" + }, + "name": { + "type": "string" + }, + "tieBreakOrder": { + "type": "number" + } + }, + "type": "object" + }, + "fiveStarCount": { + "properties": { + "candidate": { + "$ref": "#/components/schemas/candidate" + }, + "counts": { + "type": "number" + } + }, + "type": "object" + }, + "genericResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/genericSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "genericSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "irvResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "exhaustedVoteCounts": { + "items": { + "type": "number" + }, + "type": "array" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "overVoteCounts": { + "items": { + "type": "number" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/irvRoundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/rankedRobinSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "voteCounts": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + } + }, + "type": "object" + }, + "irvRoundResults": { + "properties": { + "eliminated": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "winners": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "irvSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "permission": { + "items": { + "$ref": "#/components/schemas/roles" + }, + "type": "array" + }, + "pluralityResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/pluralitySummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "pluralitySummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankedRobinResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/rankedRobinSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "rankedRobinSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "registration_field": { + "properties": { + "field_name": { + "type": "string" + }, + "field_type": { + "enum": [ + "photo", + "text" + ], + "type": "string" + }, + "help_text": { + "type": "string" + } + }, + "type": "object" + }, + "roles": { + "enum": [ + "owner", + "admin", + "auditor", + "system_admin", + "credentialer" + ], + "type": "string" + }, + "roundResults": { + "properties": { + "logs": { + "items": { + "$ref": "#/components/schemas/tabulatorLog" + }, + "type": "array" + }, + "runner_up": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "winners": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "score": { + "type": "number" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "starResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/starSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "starSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "noPreferenceStars": { + "items": { + "type": "number" + }, + "type": "array" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "tabulatorLog": { + "anyOf": [ + { + "$ref": "#/components/schemas/tabulatorLogObject" + }, + { + "type": "string" + } + ] + }, + "tabulatorLogObject": { + "additionalProperties": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": [ + "string", + "number" + ] + } + ] + }, + "properties": { + "key": { + "type": "string" + } + }, + "type": "object" + }, + "tieBreakType": { + "enum": [ + "five_star", + "none", + "random", + "score" + ], + "type": "string" + }, + "totalScore": { + "properties": { + "index": { + "type": "number" + }, + "score": { + "type": "number" + } + }, + "type": "object" + }, + "voter": { + "properties": { + "csvRow": { + "type": "number" + } + }, + "type": "object" + } + } + }, + "paths": { + "/Election/{id}": { + "get": { + "summary": "Get election by ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "The election description by ID", + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{_id}/exists": { + "get": { + "summary": "Check if election exists by ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election exists" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/ballot": { + "post": { + "summary": "Return election ballot", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election ballot returned" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/register": { + "post": { + "summary": "Register a voter for an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Voter registered" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/ballots": { + "get": { + "summary": "Get ballots by election ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "List of ballots" + }, + "404": { + "description": "Election not found" + } + } + }, + "delete": { + "summary": "Delete all ballots for an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "All ballots deleted" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/ballot/{ballot_id}": { + "get": { + "summary": "Get ballot by ballot ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + }, + { + "in": "path", + "name": "ballot_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The ballot ID" + } + ], + "responses": { + "200": { + "description": "Ballot details" + }, + "404": { + "description": "Ballot not found" + } + } + } + }, + "/Election/{id}/rolls": { + "get": { + "summary": "Get rolls by election ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "List of rolls" + }, + "404": { + "description": "Election not found" + } + } + }, + "post": { + "summary": "Add a new roll to an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll added" + }, + "404": { + "description": "Election not found" + } + } + }, + "put": { + "summary": "Edit an election roll", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll edited" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/{voter_id}": { + "get": { + "summary": "Get roll by voter ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + }, + { + "in": "path", + "name": "voter_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The voter ID" + } + ], + "responses": { + "200": { + "description": "Roll details" + }, + "404": { + "description": "Roll not found" + } + } + } + }, + "/Election/{id}/rolls/approve": { + "post": { + "summary": "Approve an election roll", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll approved" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/flag": { + "post": { + "summary": "Flag an election roll", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll flagged" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/invalidate": { + "post": { + "summary": "Invalidate an election roll", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll invalidated" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/unflag": { + "post": { + "summary": "Unflag an election roll", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll unflagged" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Elections": { + "get": { + "summary": "Get all elections", + "tags": [ + "Elections" + ], + "responses": { + "200": { + "description": "List of elections" + } + } + }, + "post": { + "summary": "Create a new election", + "tags": [ + "Elections" + ], + "responses": { + "200": { + "description": "Election created" + } + } + } + }, + "/GlobalElectionStats": { + "get": { + "summary": "Get global election statistics", + "tags": [ + "Elections" + ], + "responses": { + "200": { + "description": "Global election statistics" + } + } + } + }, + "/Election/{id}/edit": { + "post": { + "summary": "Edit an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election edited" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/roles": { + "put": { + "summary": "Edit election roles", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roles edited" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/ElectionResult/{id}": { + "get": { + "summary": "Get election results by ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election results" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/vote": { + "post": { + "summary": "Cast a vote in an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Vote cast" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/finalize": { + "post": { + "summary": "Finalize an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election finalized" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/setPublicResults": { + "post": { + "summary": "Set public results for an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Public results set" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/archive": { + "post": { + "summary": "Archive an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election archived" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/sendInvites": { + "post": { + "summary": "Send invitations for an election", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Invitations sent" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/sendInvite/{voter_id}": { + "post": { + "summary": "Send an invitation to a specific voter", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + }, + { + "in": "path", + "name": "voter_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The voter ID" + } + ], + "responses": { + "200": { + "description": "Invitation sent" + }, + "404": { + "description": "Election or voter not found" + } + } + } + }, + "/Sandbox": { + "post": { + "summary": "Get sandbox results", + "tags": [ + "Elections" + ], + "responses": { + "200": { + "description": "Sandbox results" + } + } + } + }, + "/images": { + "post": { + "summary": "Upload an image", + "tags": [ + "Elections" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Image uploaded" + } + } + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/backend/src/OpenApi/swagger.ts b/packages/backend/src/OpenApi/swagger.ts new file mode 100644 index 00000000..6a51c347 --- /dev/null +++ b/packages/backend/src/OpenApi/swagger.ts @@ -0,0 +1,27 @@ +import swaggerJsdoc from 'swagger-jsdoc'; +import schema from '@equal-vote/star-vote-shared/schema.json'; // Adjust the import path to your schema file + +const options = { + encoding: 'utf-8', + failOnErrors: true, + verbose: true, + format: 'json', + swaggerDefinition: { + openapi: '3.0.0', + info: { + title: 'API Documentation', + version: '1.0.0', + }, + components: { + schemas: { + ...schema.components.schemas, + }, + }, + }, + + apis: ['./src/Routes/*routes.ts'], // Adjust the path to your route files +}; + +const openapiSpecification = swaggerJsdoc(options); + +export default openapiSpecification; \ No newline at end of file diff --git a/packages/backend/src/Routes/elections.routes.js b/packages/backend/src/Routes/elections.routes.js deleted file mode 100644 index c517a910..00000000 --- a/packages/backend/src/Routes/elections.routes.js +++ /dev/null @@ -1,74 +0,0 @@ -const express = require('express'); -const router = express.Router(); - -const electionController = require('../Controllers/elections.controllers') -const { registerVoter } = require('../Controllers/registerVoterController') -const authController = require('../Controllers/auth.controllers') -const { deleteElection } = require('../Controllers/deleteElectionController') -const changeElectionRollStateController = require('../Controllers/changeElectionRollController') -const { editElectionRoll } = require('../Controllers/editElectionRollController') -const { addElectionRoll } = require('../Controllers/addElectionRollController') -const { getRollsByElectionID, getByVoterID } = require('../Controllers/getElectionRollController') -const createElectionController = require('../Controllers/createElectionController'); -const { castVoteController } = require('../Controllers/castVoteController'); -const { finalizeElection } = require('../Controllers/finalizeElectionController') -const { setPublicResults } = require('../Controllers/setPublicResultsController') -const { getElectionResults } = require('../Controllers/getElectionResultsController') -const { getBallotsByElectionID } = require('../Controllers/getBallotsByElectionIDController') -const { getAnonymizedBallotsByElectionID } = require('../Controllers/getAnonymizedBallotsByElectionIDController') -const { deleteAllBallotsForElectionID } = require('../Controllers/deleteAllBallotsForElectionIDController') -const { getBallotByBallotID } = require('../Controllers/getBallotByBallotID') -const { editElection } = require('../Controllers/editElectionController') -const { getSandboxResults } = require('../Controllers/sandboxController') -const { getElections, getGlobalElectionStats } = require('../Controllers/getElectionsController') -const { editElectionRoles } = require('../Controllers/editElectionRolesController') -const { permissions } = require('@equal-vote/star-vote-shared/domain_model/permissions'); -const { ElectionRollState } = require('@equal-vote/star-vote-shared/domain_model/ElectionRoll'); -const { uploadImageController, upload } = require('../Controllers/uploadImageController') -const { archiveElection } = require('../Controllers/archiveElectionController') -const { sendInvitationsController, sendInvitationController } = require('../Controllers/sendInvitesController') -const asyncHandler = require('express-async-handler') - -router.get('/Election/:id', asyncHandler(electionController.returnElection)) -// using _id so that it doesn't trigger async handers in routes.params -router.get('/Election/:_id/exists', asyncHandler(electionController.electionExistsByID)) -router.delete('/Election/:id', asyncHandler(deleteElection)) -router.post('/Election/:id/ballot', asyncHandler(electionController.returnElection)) -router.post('/Election/:id/register',asyncHandler(registerVoter)) -router.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) -router.get('/Election/:id/anonymizedBallots', asyncHandler(getAnonymizedBallotsByElectionID)) -router.delete('/Election/:id/ballots', asyncHandler(deleteAllBallotsForElectionID)) -router.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBallotID)) -router.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) -router.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) -router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) -router.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) -router.post('/Election/:id/rolls/approve', asyncHandler(changeElectionRollStateController.approveElectionRoll)) -router.post('/Election/:id/rolls/flag', asyncHandler(changeElectionRollStateController.flagElectionRoll)) -router.post('/Election/:id/rolls/invalidate', asyncHandler(changeElectionRollStateController.invalidateElectionRoll)) -router.post('/Election/:id/rolls/unflag', asyncHandler(changeElectionRollStateController.uninvalidateElectionRoll)) -router.get('/Elections', asyncHandler(getElections)) -router.post('/Elections/', asyncHandler(createElectionController.createElectionController)) -router.get('/GlobalElectionStats', asyncHandler(getGlobalElectionStats)) - -router.post('/Election/:id/edit', asyncHandler(editElection)) -router.put('/Election/:id/roles', asyncHandler(editElectionRoles)) -router.get('/ElectionResult/:id', asyncHandler(getElectionResults)) -router.post('/Election/:id/vote', asyncHandler(castVoteController)) -router.post('/Election/:id/finalize',asyncHandler(finalizeElection)) -router.post('/Election/:id/setPublicResults',asyncHandler(setPublicResults)) -router.post('/Election/:id/archive', asyncHandler(archiveElection)) -router.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsController)) -router.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationController)) - -router.post('/Sandbox',asyncHandler(getSandboxResults)) - -router.post('/images',upload.single("file"), asyncHandler(uploadImageController)) - -//router.param('id', asyncHandler(electionController.getElectionByID)) -router.param('id', asyncHandler(electionController.getElectionByID)) -router.param('id', asyncHandler(electionController.electionSpecificAuth)) -router.param('id', asyncHandler(electionController.electionPostAuthMiddleware)) - -module.exports = router - diff --git a/packages/backend/src/Routes/elections.routes.ts b/packages/backend/src/Routes/elections.routes.ts new file mode 100644 index 00000000..fe1e6ec4 --- /dev/null +++ b/packages/backend/src/Routes/elections.routes.ts @@ -0,0 +1,674 @@ +const router = express.Router(); + +import express from 'express'; +import { returnElection, + getElectionByID, + electionSpecificAuth, + electionPostAuthMiddleware, + electionExistsByID} from '../Controllers/elections.controllers'; +import { registerVoter } from '../Controllers/registerVoterController'; +import { + getUser, + isLoggedIn, + assertOwnership, + hasPermission + } from '../Controllers/auth.controllers'; +import { deleteElection } from '../Controllers/deleteElectionController'; +import { + changeElectionRollState, + approveElectionRoll, + flagElectionRoll, + invalidateElectionRoll, + uninvalidateElectionRoll +} from '../Controllers/changeElectionRollController'; +import { editElectionRoll } from '../Controllers/editElectionRollController'; +import { addElectionRoll } from '../Controllers/addElectionRollController'; +import { getRollsByElectionID, getByVoterID } from '../Controllers/getElectionRollController'; +import {createElectionController} from '../Controllers/createElectionController'; +import { castVoteController } from '../Controllers/castVoteController'; +import { finalizeElection } from '../Controllers/finalizeElectionController'; +import { setPublicResults } from '../Controllers/setPublicResultsController'; +import { getElectionResults } from '../Controllers/getElectionResultsController'; +import { getBallotsByElectionID } from '../Controllers/getBallotsByElectionIDController'; +import { deleteAllBallotsForElectionID } from '../Controllers/deleteAllBallotsForElectionIDController'; +import { getBallotByBallotID } from '../Controllers/getBallotByBallotID'; +import { editElection } from '../Controllers/editElectionController'; +import { getSandboxResults } from '../Controllers/sandboxController'; +import { getElections, getGlobalElectionStats } from '../Controllers/getElectionsController'; +import { editElectionRoles } from '../Controllers/editElectionRolesController'; +import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; +import { ElectionRollState } from '@equal-vote/star-vote-shared/domain_model/ElectionRoll'; +import { uploadImageController, upload } from '../Controllers/uploadImageController'; +import { archiveElection } from '../Controllers/archiveElectionController'; +import { sendInvitationsController, sendInvitationController } from '../Controllers/sendInvitesController'; +import asyncHandler from 'express-async-handler'; +import { IElectionRequest } from '../IRequest'; + + + + +/** + * @swagger + * /Election/{id}: + * get: + * summary: Get election by ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: The election description by ID + * content: + * application/json: + * schema: + * type: object + * $ref: '#/components/schemas/Election' + * 404: + * description: Election not found + */ +router.get('/Election/:id', asyncHandler(returnElection)); + +/** + * @swagger + * /Election/{_id}/exists: + * get: + * summary: Check if election exists by ID + * tags: [Elections] + * parameters: + * - in: path + * name: _id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election exists + * content: + * application/json: + * schema: + * type: object + * properties: + * exists: + * type: boolean + * + * 404: + * description: Election not found + */ +router.get('/Election/:_id/exists', asyncHandler(electionExistsByID)) + +/** + * @swagger + * /Election/{id}/ballot: + * post: + * summary: Return election ballot + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election ballot returned + * content: + * application/json: + * schema: + * type: object + * properties: + * ballot: + * type: object + * $ref: '#/components/schemas/NewBallot' + * + * 404: + * description: Election not found + */ +router.post('/Election/:id/ballot', asyncHandler(returnElection)) + +/** + * @swagger + * /Election/{id}/register: + * post: + * summary: Register a voter for an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Voter registered + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/ballots: + * get: + * summary: Get ballots by election ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: List of ballots + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/ballots: + * delete: + * summary: Delete all ballots for an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: All ballots deleted + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/ballot/{ballot_id}: + * get: + * summary: Get ballot by ballot ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * - in: path + * name: ballot_id + * schema: + * type: string + * required: true + * description: The ballot ID + * responses: + * 200: + * description: Ballot details + * 404: + * description: Ballot not found + */ + +/** + * @swagger + * /Election/{id}/rolls: + * get: + * summary: Get rolls by election ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: List of rolls + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/rolls/{voter_id}: + * get: + * summary: Get roll by voter ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * - in: path + * name: voter_id + * schema: + * type: string + * required: true + * description: The voter ID + * responses: + * 200: + * description: Roll details + * 404: + * description: Roll not found + */ + +/** + * @swagger + * /Election/{id}/rolls: + * post: + * summary: Add a new roll to an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll added + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/rolls: + * put: + * summary: Edit an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll edited + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/rolls/approve: + * post: + * summary: Approve an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll approved + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/rolls/flag: + * post: + * summary: Flag an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll flagged + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/rolls/invalidate: + * post: + * summary: Invalidate an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll invalidated + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/rolls/unflag: + * post: + * summary: Unflag an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll unflagged + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Elections: + * get: + * summary: Get all elections + * tags: [Elections] + * responses: + * 200: + * description: List of elections + */ + +/** + * @swagger + * /Elections: + * post: + * summary: Create a new election + * tags: [Elections] + * responses: + * 200: + * description: Election created + */ + +/** + * @swagger + * /GlobalElectionStats: + * get: + * summary: Get global election statistics + * tags: [Elections] + * responses: + * 200: + * description: Global election statistics + */ + +/** + * @swagger + * /Election/{id}/edit: + * post: + * summary: Edit an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election edited + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/roles: + * put: + * summary: Edit election roles + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roles edited + * 404: + * description: Election not found + */ + +/** + * @swagger + * /ElectionResult/{id}: + * get: + * summary: Get election results by ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election results + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/vote: + * post: + * summary: Cast a vote in an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Vote cast + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/finalize: + * post: + * summary: Finalize an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election finalized + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/setPublicResults: + * post: + * summary: Set public results for an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Public results set + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/archive: + * post: + * summary: Archive an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election archived + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/sendInvites: + * post: + * summary: Send invitations for an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Invitations sent + * 404: + * description: Election not found + */ + +/** + * @swagger + * /Election/{id}/sendInvite/{voter_id}: + * post: + * summary: Send an invitation to a specific voter + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * - in: path + * name: voter_id + * schema: + * type: string + * required: true + * description: The voter ID + * responses: + * 200: + * description: Invitation sent + * 404: + * description: Election or voter not found + */ + +/** + * @swagger + * /Sandbox: + * post: + * summary: Get sandbox results + * tags: [Elections] + * responses: + * 200: + * description: Sandbox results + */ + +/** + * @swagger + * /images: + * post: + * summary: Upload an image + * tags: [Elections] + * requestBody: + * content: + * multipart/form-data: + * schema: + * type: object + * properties: + * file: + * type: string + * format: binary + * responses: + * 200: + * description: Image uploaded + */ + +// using _id so that it doesn't trigger async handers in routes.params +router.delete('/Election/:id', asyncHandler(deleteElection)); +router.post('/Election/:id/register',asyncHandler(registerVoter)) +router.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) +router.delete('/Election/:id/ballots', asyncHandler(deleteAllBallotsForElectionID)) +router.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBallotID)) +router.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) +router.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) +router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) +router.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) +router.post('/Election/:id/rolls/approve', asyncHandler(approveElectionRoll)) +router.post('/Election/:id/rolls/flag', asyncHandler(flagElectionRoll)) +router.post('/Election/:id/rolls/invalidate', asyncHandler(invalidateElectionRoll)) +router.post('/Election/:id/rolls/unflag', asyncHandler(uninvalidateElectionRoll)) +router.get('/Elections', asyncHandler(getElections)) +router.post('/Elections/', asyncHandler(createElectionController.createElectionController)) +router.get('/GlobalElectionStats', asyncHandler(getGlobalElectionStats)) + +router.post('/Election/:id/edit', asyncHandler(editElection)) +router.put('/Election/:id/roles', asyncHandler(editElectionRoles)) +router.get('/ElectionResult/:id', asyncHandler(getElectionResults)) +router.post('/Election/:id/vote', asyncHandler(castVoteController)) +router.post('/Election/:id/finalize',asyncHandler(finalizeElection)) +router.post('/Election/:id/setPublicResults',asyncHandler(setPublicResults)) +router.post('/Election/:id/archive', asyncHandler(archiveElection)) +router.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsController)) +router.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationController)) + +router.post('/Sandbox',asyncHandler(getSandboxResults)) + +router.post('/images',upload.single("file"), asyncHandler(uploadImageController)) + +//router.param('id', asyncHandler(electionController.getElectionByID)) +router.param('id', asyncHandler(getElectionByID)) +router.param('id', asyncHandler(electionSpecificAuth)) +router.param('id', asyncHandler(electionPostAuthMiddleware)) + +export default router; diff --git a/packages/backend/src/Routes/routes.yaml b/packages/backend/src/Routes/routes.yaml new file mode 100644 index 00000000..e69de29b diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 962dc751..7287f291 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -12,6 +12,8 @@ import { errorCatch } from './errorCatchMiddleware' import registerEvents from './Routes/registerEvents'; import { setupSockets } from './socketHandler'; import { getMetaTags } from './Util'; +import swaggerUi from 'swagger-ui-express'; +import openapi from './OpenApi/openapi.json' const { getUserToken } = require('./Controllers/getUserTokenController') const authController = require('./Controllers/auth.controllers') @@ -45,7 +47,7 @@ export default function makeApp() { //Routes app.use('/API',authController.getUser, electionRouter) // app.use('/debug',debugRouter) - + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapi)); app.post('/API/Token', asyncHandler(getUserToken)); // NOTE: I've removed express.static because it doesn't allow me to inject meta tags diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index ff79a5f8..e8ec7509 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -10,13 +10,15 @@ "moduleResolution": "nodenext", "strict": true, "types": [ - "jest" + "jest", + "node" ], "lib": [ "ES6", "DOM", "ES2021.String", // Added for replaceAll - ] + ], + "resolveJsonModule": true, }, "include": ["./src/**/*"] } diff --git a/packages/shared/fixSchemaRefs.ts b/packages/shared/fixSchemaRefs.ts new file mode 100644 index 00000000..3491d07a --- /dev/null +++ b/packages/shared/fixSchemaRefs.ts @@ -0,0 +1,31 @@ +const fs = require('fs'); + +// Load the generated schema +const schemaPath = './schema.json'; +const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8')); + +// Function to recursively replace $ref values +function replaceRefs(obj) { + if (typeof obj === 'object' && obj !== null) { + for (const key in obj) { + if (key === '$ref' && typeof obj[key] === 'string') { + obj[key] = obj[key].replace('#/definitions/', '#/components/schemas/'); + } else { + replaceRefs(obj[key]); + } + } + } +} + +// Replace $ref paths +replaceRefs(schema); + +// Move definitions to components.schemas +schema.components = schema.components || {}; +schema.components.schemas = schema.definitions; +delete schema.definitions; + +// Save the updated schema +fs.writeFileSync(schemaPath, JSON.stringify(schema, null, 2)); + +console.log('Schema references updated successfully.'); diff --git a/packages/shared/package.json b/packages/shared/package.json index b876fdb5..1e8aeac7 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -7,7 +7,8 @@ "scripts": { "test": "npm install && npm test", "clean": "npx rimraf dist node_modules ../node_modules", - "build": "tsc --project ./" + "build": "tsc --project ./ && npm run generate:schema", + "generate:schema": "typescript-json-schema tsconfig.json \"*\" --out schema.json && node ./fixSchemaRefs.ts" }, "keywords": [], "author": "", @@ -35,12 +36,16 @@ "./domain_model/*": { "types": "./dist/types/domain_model/*.d.ts", "default": "./dist/domain_model/*.js" + }, + "./schema.json": { + "default": "./schema.json" } }, "dependencies": { "@equal-vote/star-vote-backend": "^1.0.0", "@equal-vote/star-vote-frontend": "^0.1.0", "socket.io": "^4.7.5", - "socket.io-client": "^4.7.5" + "socket.io-client": "^4.7.5", + "typescript-json-schema": "^0.65.1" } } diff --git a/packages/shared/schema.json b/packages/shared/schema.json new file mode 100644 index 00000000..a75e4ce8 --- /dev/null +++ b/packages/shared/schema.json @@ -0,0 +1,1715 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "components": { + "schemas": { + "Ballot": { + "properties": { + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "date_submitted": { + "type": "number" + }, + "election_id": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/BallotAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "status": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "user_id": { + "type": "string" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/Vote" + }, + "type": "array" + } + }, + "type": "object" + }, + "BallotAction": { + "properties": { + "action_type": { + "type": "string" + }, + "actor": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + }, + "type": "object" + }, + "Candidate": { + "properties": { + "bio": { + "type": "string" + }, + "candidate_id": { + "type": "string" + }, + "candidate_name": { + "type": "string" + }, + "candidate_url": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "party": { + "type": "string" + }, + "partyUrl": { + "type": "string" + }, + "party_url": { + "type": "string" + }, + "photo_filename": { + "type": "string" + } + }, + "type": "object" + }, + "Credentials": { + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "type": "object" + }, + "Election": { + "properties": { + "admin_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "audit_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_key": { + "type": "string" + }, + "claim_key_hash": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "credential_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "election_id": { + "type": "string" + }, + "end_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "frontend_url": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "owner_id": { + "type": "string" + }, + "races": { + "items": { + "$ref": "#/components/schemas/Race" + }, + "type": "array" + }, + "settings": { + "$ref": "#/components/schemas/ElectionSettings" + }, + "start_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "state": { + "$ref": "#/components/schemas/ElectionState" + }, + "support_email": { + "type": "string" + }, + "title": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "type": "object" + }, + "ElectionResults": { + "anyOf": [ + { + "properties": { + "results": { + "$ref": "#/components/schemas/starResults" + }, + "votingMethod": { + "const": "STAR", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/allocatedScoreResults" + }, + "votingMethod": { + "const": "STAR_PR", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/approvalResults" + }, + "votingMethod": { + "const": "Approval", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/rankedRobinResults" + }, + "votingMethod": { + "const": "RankedRobin", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/irvResults" + }, + "votingMethod": { + "const": "IRV", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/pluralityResults" + }, + "votingMethod": { + "const": "Plurality", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/irvResults" + }, + "votingMethod": { + "const": "STV", + "type": "string" + } + }, + "type": "object" + } + ] + }, + "ElectionRoll": { + "properties": { + "address": { + "type": "string" + }, + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "election_id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "email_data": { + "properties": { + "inviteResponse": {}, + "reminderResponse": {} + }, + "type": "object" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/ElectionRollAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "registration": {}, + "state": { + "$ref": "#/components/schemas/ElectionRollState" + }, + "submitted": { + "type": "boolean" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "voter_id": { + "type": "string" + } + }, + "type": "object" + }, + "ElectionRollAction": { + "properties": { + "action_type": { + "type": "string" + }, + "actor": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + }, + "type": "object" + }, + "ElectionRollState": { + "enum": [ + "approved", + "flagged", + "registered", + "invalid" + ], + "type": "string" + }, + "ElectionSettings": { + "properties": { + "ballot_updates": { + "type": "boolean" + }, + "break_ties_randomly": { + "type": "boolean" + }, + "invitation": { + "enum": [ + "address", + "email" + ], + "type": "string" + }, + "max_rankings": { + "type": "number" + }, + "public_results": { + "type": "boolean" + }, + "random_candidate_order": { + "type": "boolean" + }, + "reminders": { + "type": "boolean" + }, + "require_instruction_confirmation": { + "type": "boolean" + }, + "term_type": { + "enum": [ + "election", + "poll" + ], + "type": "string" + }, + "time_zone": { + "type": "string" + }, + "voter_access": { + "enum": [ + "closed", + "open", + "registration" + ], + "type": "string" + }, + "voter_authentication": { + "$ref": "#/components/schemas/authentication" + } + }, + "type": "object" + }, + "ElectionState": { + "enum": [ + "archived", + "closed", + "draft", + "finalized", + "open" + ], + "type": "string" + }, + "Email": { + "type": "string" + }, + "NewBallot": { + "properties": { + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "date_submitted": { + "type": "number" + }, + "election_id": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/BallotAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "status": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "user_id": { + "type": "string" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/Vote" + }, + "type": "array" + } + }, + "type": "object" + }, + "NewElection": { + "properties": { + "admin_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "audit_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_key": { + "type": "string" + }, + "claim_key_hash": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "credential_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "election_id": { + "type": "string" + }, + "end_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "frontend_url": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "owner_id": { + "type": "string" + }, + "races": { + "items": { + "$ref": "#/components/schemas/Race" + }, + "type": "array" + }, + "settings": { + "$ref": "#/components/schemas/ElectionSettings" + }, + "start_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "state": { + "$ref": "#/components/schemas/ElectionState" + }, + "support_email": { + "type": "string" + }, + "title": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "type": "object" + }, + "NewPassword": { + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + }, + "type": "object" + }, + "Omit": { + "type": "object" + }, + "Omit_1": { + "type": "object" + }, + "Partial>": { + "type": "object" + }, + "PartialBy": { + "allOf": [ + { + "$ref": "#/components/schemas/Omit_1" + }, + { + "$ref": "#/components/schemas/Partial>" + } + ] + }, + "Race": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/Candidate" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "num_winners": { + "type": "number" + }, + "precincts": { + "items": { + "additionalProperties": false, + "patternProperties": { + "^[0-9]+$": { + "type": "string" + } + }, + "properties": { + "length": { + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + }, + "race_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "voting_method": { + "$ref": "#/components/schemas/VotingMethod" + } + }, + "type": "object" + }, + "Score": { + "properties": { + "candidate_id": { + "type": "string" + }, + "score": { + "type": "number" + } + }, + "type": "object" + }, + "TermType": { + "enum": [ + "election", + "poll" + ], + "type": "string" + }, + "Uid": { + "type": "string" + }, + "Vote": { + "properties": { + "race_id": { + "type": "string" + }, + "scores": { + "items": { + "$ref": "#/components/schemas/Score" + }, + "type": "array" + } + }, + "type": "object" + }, + "VoterAuth": { + "properties": { + "authorized_voter": { + "type": "boolean" + }, + "has_voted": { + "type": "boolean" + }, + "permissions": { + "items": { + "enum": [ + "canAddToElectionRoll", + "canApproveElectionRoll", + "canDeleteAllBallots", + "canDeleteElection", + "canDeleteElectionRoll", + "canEditBallot", + "canEditElection", + "canEditElectionRoles", + "canEditElectionRoll", + "canEditElectionState", + "canFlagBallot", + "canFlagElectionRoll", + "canInvalidateBallot", + "canInvalidateElectionRoll", + "canSendEmails", + "canUnflagElectionRoll", + "canViewBallot", + "canViewBallots", + "canViewElection", + "canViewElectionRoll", + "canViewElectionRollIDs", + "canViewPreliminaryResults" + ], + "type": "string" + }, + "type": "array" + }, + "required": { + "type": "string" + }, + "roles": { + "items": { + "$ref": "#/components/schemas/roles" + }, + "type": "array" + } + }, + "type": "object" + }, + "VotingMethod": { + "enum": [ + "Approval", + "IRV", + "Plurality", + "RankedRobin", + "STAR", + "STAR_PR", + "STV" + ], + "type": "string" + }, + "allocatedScoreResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/allocatedScoreSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "type": "array" + } + }, + "type": "object" + }, + "allocatedScoreSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "noPreferenceStars": { + "items": { + "type": "number" + }, + "type": "array" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "spentAboves": { + "items": { + "type": "number" + }, + "type": "array" + }, + "splitPoints": { + "items": { + "type": "number" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + }, + "weight_on_splits": { + "items": { + "type": "number" + }, + "type": "array" + }, + "weightedScoresByRound": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + } + }, + "type": "object" + }, + "approvalResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/approvalSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "approvalSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "authentication": { + "properties": { + "address": { + "type": "boolean" + }, + "email": { + "type": "boolean" + }, + "ip_address": { + "type": "boolean" + }, + "phone": { + "type": "boolean" + }, + "registration_api_endpoint": { + "type": "string" + }, + "registration_data": { + "items": [ + { + "$ref": "#/components/schemas/registration_field" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + "voter_id": { + "type": "boolean" + } + }, + "type": "object" + }, + "ballot": { + "items": { + "type": "number" + }, + "type": "array" + }, + "ballots": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "candidate": { + "properties": { + "index": { + "type": "number" + }, + "name": { + "type": "string" + }, + "tieBreakOrder": { + "type": "number" + } + }, + "type": "object" + }, + "fiveStarCount": { + "properties": { + "candidate": { + "$ref": "#/components/schemas/candidate" + }, + "counts": { + "type": "number" + } + }, + "type": "object" + }, + "genericResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/genericSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "genericSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "irvResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "exhaustedVoteCounts": { + "items": { + "type": "number" + }, + "type": "array" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "overVoteCounts": { + "items": { + "type": "number" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/irvRoundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/rankedRobinSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "voteCounts": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + } + }, + "type": "object" + }, + "irvRoundResults": { + "properties": { + "eliminated": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "winners": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "irvSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "permission": { + "items": { + "$ref": "#/components/schemas/roles" + }, + "type": "array" + }, + "pluralityResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/pluralitySummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "pluralitySummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankedRobinResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/rankedRobinSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "rankedRobinSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "registration_field": { + "properties": { + "field_name": { + "type": "string" + }, + "field_type": { + "enum": [ + "photo", + "text" + ], + "type": "string" + }, + "help_text": { + "type": "string" + } + }, + "type": "object" + }, + "roles": { + "enum": [ + "owner", + "admin", + "auditor", + "system_admin", + "credentialer" + ], + "type": "string" + }, + "roundResults": { + "properties": { + "logs": { + "items": { + "$ref": "#/components/schemas/tabulatorLog" + }, + "type": "array" + }, + "runner_up": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "winners": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "score": { + "type": "number" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "starResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/starSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "type": "object" + }, + "starSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "noPreferenceStars": { + "items": { + "type": "number" + }, + "type": "array" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "type": "object" + }, + "tabulatorLog": { + "anyOf": [ + { + "$ref": "#/components/schemas/tabulatorLogObject" + }, + { + "type": "string" + } + ] + }, + "tabulatorLogObject": { + "additionalProperties": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": [ + "string", + "number" + ] + } + ] + }, + "properties": { + "key": { + "type": "string" + } + }, + "type": "object" + }, + "tieBreakType": { + "enum": [ + "five_star", + "none", + "random", + "score" + ], + "type": "string" + }, + "totalScore": { + "properties": { + "index": { + "type": "number" + }, + "score": { + "type": "number" + } + }, + "type": "object" + }, + "voter": { + "properties": { + "csvRow": { + "type": "number" + } + }, + "type": "object" + } + } + } +} \ No newline at end of file diff --git a/packages/shared/src/domain_model/Election.ts b/packages/shared/src/domain_model/Election.ts index 176447d7..2f9f65c1 100644 --- a/packages/shared/src/domain_model/Election.ts +++ b/packages/shared/src/domain_model/Election.ts @@ -22,7 +22,7 @@ export interface Election { settings: ElectionSettings; auth_key?: string; claim_key_hash?: string; - is_public?: Boolean; + is_public?: boolean; create_date: Date | string; // Date this object was created update_date: Date | string; // Date this object was last updated head: boolean;// Head version of this object From 8258cd410bcd9bde17cfc48a62d2b3324fe1c656 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Sun, 22 Sep 2024 17:53:23 -0600 Subject: [PATCH 02/11] finished documenting backend --- .../Controllers/addElectionRollController.ts | 2 +- .../Controllers/archiveElectionController.ts | 4 +- .../src/Controllers/auth.controllers.ts | 2 +- .../src/Controllers/ballots.controllers.ts | 2 +- .../src/Controllers/castVoteController.ts | 2 +- .../changeElectionRollController.ts | 2 +- .../Controllers/createElectionController.ts | 2 +- ...deleteAllBallotsForElectionIDController.ts | 2 +- .../Controllers/deleteElectionController.ts | 4 +- .../src/Controllers/editElectionController.ts | 2 +- .../editElectionRolesController.ts | 2 +- .../Controllers/editElectionRollController.ts | 2 +- .../Controllers/finalizeElectionController.ts | 4 +- .../src/Controllers/getBallotByBallotID.ts | 2 +- .../getBallotsByElectionIDController.ts | 2 +- .../getElectionResultsController.ts | 2 +- .../Controllers/getElectionRollController.ts | 2 +- .../src/Controllers/getElectionsController.ts | 2 +- .../src/Controllers/getUserTokenController.ts | 2 +- .../Controllers/registerVoterController.ts | 2 +- .../src/Controllers/sandboxController.ts | 2 +- .../src/Controllers/sendInvitesController.ts | 4 +- .../Controllers/setPublicResultsController.ts | 4 +- .../src/Controllers/uploadImageController.ts | 4 +- packages/backend/src/Express/index.d.ts | 3 + packages/backend/src/OpenApi/openapi.json | 477 +++++++++++++++++- packages/backend/src/Routes/debug.routes.js | 2 +- .../backend/src/Routes/elections.routes.ts | 464 +++++++++++++---- packages/backend/src/Routes/routes.yaml | 0 packages/backend/src/Tabulators/ParseData.ts | 4 +- packages/backend/src/app.ts | 2 +- 31 files changed, 843 insertions(+), 169 deletions(-) delete mode 100644 packages/backend/src/Routes/routes.yaml diff --git a/packages/backend/src/Controllers/addElectionRollController.ts b/packages/backend/src/Controllers/addElectionRollController.ts index b46fdf78..2c35dee6 100644 --- a/packages/backend/src/Controllers/addElectionRollController.ts +++ b/packages/backend/src/Controllers/addElectionRollController.ts @@ -68,6 +68,6 @@ const addElectionRoll = async (req: IElectionRequest, res: Response, next: NextF return next() } -module.exports = { +export { addElectionRoll, } diff --git a/packages/backend/src/Controllers/archiveElectionController.ts b/packages/backend/src/Controllers/archiveElectionController.ts index 60d4407e..70ac5d1b 100644 --- a/packages/backend/src/Controllers/archiveElectionController.ts +++ b/packages/backend/src/Controllers/archiveElectionController.ts @@ -31,9 +31,9 @@ const archiveElection = async (req: IElectionRequest, res: Response, next: NextF throw new BadRequest(failMsg) } - return res.json({ election: updatedElection }) + res.json({ election: updatedElection }) } -module.exports = { +export { archiveElection, } \ No newline at end of file diff --git a/packages/backend/src/Controllers/auth.controllers.ts b/packages/backend/src/Controllers/auth.controllers.ts index 6112b6c5..63d2e149 100644 --- a/packages/backend/src/Controllers/auth.controllers.ts +++ b/packages/backend/src/Controllers/auth.controllers.ts @@ -51,7 +51,7 @@ const assertOwnership = (req: any, res: any, next: any) => { next() } -module.exports = { +export { getUser, isLoggedIn, assertOwnership, diff --git a/packages/backend/src/Controllers/ballots.controllers.ts b/packages/backend/src/Controllers/ballots.controllers.ts index 376b0d59..cdedee70 100644 --- a/packages/backend/src/Controllers/ballots.controllers.ts +++ b/packages/backend/src/Controllers/ballots.controllers.ts @@ -25,6 +25,6 @@ const ballotByID = async (req: any, res: any, next: any) => { } -module.exports = { +export { ballotByID } \ No newline at end of file diff --git a/packages/backend/src/Controllers/castVoteController.ts b/packages/backend/src/Controllers/castVoteController.ts index 21523fda..04e53411 100644 --- a/packages/backend/src/Controllers/castVoteController.ts +++ b/packages/backend/src/Controllers/castVoteController.ts @@ -159,7 +159,7 @@ function assertVoterMayVote(voterAuthorization:any, ctx:ILoggingContext ): void{ Logger.debug(ctx, "Voter authorized"); } -module.exports = { +export { castVoteController, handleCastVoteEvent } \ No newline at end of file diff --git a/packages/backend/src/Controllers/changeElectionRollController.ts b/packages/backend/src/Controllers/changeElectionRollController.ts index 602756ff..47adbc7d 100644 --- a/packages/backend/src/Controllers/changeElectionRollController.ts +++ b/packages/backend/src/Controllers/changeElectionRollController.ts @@ -66,7 +66,7 @@ const changeElectionRollState = async (req: IElectionRequest, newState: Election } } -module.exports = { +export { changeElectionRollState, approveElectionRoll, flagElectionRoll, diff --git a/packages/backend/src/Controllers/createElectionController.ts b/packages/backend/src/Controllers/createElectionController.ts index 09370a49..6be9ea37 100644 --- a/packages/backend/src/Controllers/createElectionController.ts +++ b/packages/backend/src/Controllers/createElectionController.ts @@ -38,6 +38,6 @@ const createAndCheckElection = async ( return newElection; }; -module.exports = { +export { createElectionController } \ No newline at end of file diff --git a/packages/backend/src/Controllers/deleteAllBallotsForElectionIDController.ts b/packages/backend/src/Controllers/deleteAllBallotsForElectionIDController.ts index e2f60646..5ba4130b 100644 --- a/packages/backend/src/Controllers/deleteAllBallotsForElectionIDController.ts +++ b/packages/backend/src/Controllers/deleteAllBallotsForElectionIDController.ts @@ -34,7 +34,7 @@ const deleteAllBallotsForElectionID = async (req: IElectionRequest, res: Respons res.json({ success: innerDeleteAllBallotsForElectionID(req) }) } -module.exports = { +export { deleteAllBallotsForElectionID, innerDeleteAllBallotsForElectionID } diff --git a/packages/backend/src/Controllers/deleteElectionController.ts b/packages/backend/src/Controllers/deleteElectionController.ts index 670454ad..1fad3fa9 100644 --- a/packages/backend/src/Controllers/deleteElectionController.ts +++ b/packages/backend/src/Controllers/deleteElectionController.ts @@ -23,9 +23,9 @@ const deleteElection = async (req: IElectionRequest, res: Response, next: NextFu throw new BadRequest(msg) } Logger.info(req, `Deleted election ${electionId}`); - return res.status(200) + res.status(200) } -module.exports = { +export { deleteElection } \ No newline at end of file diff --git a/packages/backend/src/Controllers/editElectionController.ts b/packages/backend/src/Controllers/editElectionController.ts index b77e8fe8..f94eeac9 100644 --- a/packages/backend/src/Controllers/editElectionController.ts +++ b/packages/backend/src/Controllers/editElectionController.ts @@ -44,6 +44,6 @@ const editElection = async (req: IElectionRequest, res: Response, next: NextFunc res.json({ election: req.election, voterAuth: { authorized_voter: req.authorized_voter, has_voted: req.has_voted, roles: req.user_auth.roles, permissions: req.user_auth.permissions } }) } -module.exports = { +export { editElection } diff --git a/packages/backend/src/Controllers/editElectionRolesController.ts b/packages/backend/src/Controllers/editElectionRolesController.ts index c6bb65fc..2050b14b 100644 --- a/packages/backend/src/Controllers/editElectionRolesController.ts +++ b/packages/backend/src/Controllers/editElectionRolesController.ts @@ -38,6 +38,6 @@ const editElectionRoles = async (req: IElectionRequest, res: Response, next: Nex res.status(200).json({election: req.election}) } -module.exports = { +export { editElectionRoles } diff --git a/packages/backend/src/Controllers/editElectionRollController.ts b/packages/backend/src/Controllers/editElectionRollController.ts index d466e0dc..3417bfbd 100644 --- a/packages/backend/src/Controllers/editElectionRollController.ts +++ b/packages/backend/src/Controllers/editElectionRollController.ts @@ -33,6 +33,6 @@ const editElectionRoll = async (req: IElectionRequest, res: Response, next: Next res.status(200).json(electionRollEntry) } -module.exports = { +export { editElectionRoll, } diff --git a/packages/backend/src/Controllers/finalizeElectionController.ts b/packages/backend/src/Controllers/finalizeElectionController.ts index e6c71a48..8d0d6dd2 100644 --- a/packages/backend/src/Controllers/finalizeElectionController.ts +++ b/packages/backend/src/Controllers/finalizeElectionController.ts @@ -50,9 +50,9 @@ const finalizeElection = async (req: IElectionRequest, res: Response, next: Next await sendBatchEmailInvites(req, electionRoll, updatedElection) } } - return res.json({ election: updatedElection }) + res.json({ election: updatedElection }) } -module.exports = { +export { finalizeElection, } \ No newline at end of file diff --git a/packages/backend/src/Controllers/getBallotByBallotID.ts b/packages/backend/src/Controllers/getBallotByBallotID.ts index 66886b06..1fd76a93 100644 --- a/packages/backend/src/Controllers/getBallotByBallotID.ts +++ b/packages/backend/src/Controllers/getBallotByBallotID.ts @@ -27,6 +27,6 @@ const getBallotByBallotID = async (req: IElectionRequest, res: Response, next: N res.json({ ballot: ballot }) } -module.exports = { +export { getBallotByBallotID } diff --git a/packages/backend/src/Controllers/getBallotsByElectionIDController.ts b/packages/backend/src/Controllers/getBallotsByElectionIDController.ts index 61470590..81215ab2 100644 --- a/packages/backend/src/Controllers/getBallotsByElectionIDController.ts +++ b/packages/backend/src/Controllers/getBallotsByElectionIDController.ts @@ -24,6 +24,6 @@ const getBallotsByElectionID = async (req: IElectionRequest, res: Response, next res.json({ election: req.election, ballots: ballots }) } -module.exports = { +export { getBallotsByElectionID } diff --git a/packages/backend/src/Controllers/getElectionResultsController.ts b/packages/backend/src/Controllers/getElectionResultsController.ts index 1fdb4067..eb70b797 100644 --- a/packages/backend/src/Controllers/getElectionResultsController.ts +++ b/packages/backend/src/Controllers/getElectionResultsController.ts @@ -66,6 +66,6 @@ const getElectionResults = async (req: IElectionRequest, res: Response, next: Ne ) } -module.exports = { +export { getElectionResults } diff --git a/packages/backend/src/Controllers/getElectionRollController.ts b/packages/backend/src/Controllers/getElectionRollController.ts index 1ba4f159..204638f0 100644 --- a/packages/backend/src/Controllers/getElectionRollController.ts +++ b/packages/backend/src/Controllers/getElectionRollController.ts @@ -44,7 +44,7 @@ const getByVoterID = async (req: IElectionRequest, res: Response, next: NextFunc next() } -module.exports = { +export { getRollsByElectionID, getByVoterID } diff --git a/packages/backend/src/Controllers/getElectionsController.ts b/packages/backend/src/Controllers/getElectionsController.ts index 740f8abd..10203bc2 100644 --- a/packages/backend/src/Controllers/getElectionsController.ts +++ b/packages/backend/src/Controllers/getElectionsController.ts @@ -89,7 +89,7 @@ const getGlobalElectionStats = async (req: IRequest, res: Response, next: NextFu res.json(innerGetGlobalElectionStats(req)); } -module.exports = { +export { getElections, innerGetGlobalElectionStats, getGlobalElectionStats, diff --git a/packages/backend/src/Controllers/getUserTokenController.ts b/packages/backend/src/Controllers/getUserTokenController.ts index be323078..663a4b58 100644 --- a/packages/backend/src/Controllers/getUserTokenController.ts +++ b/packages/backend/src/Controllers/getUserTokenController.ts @@ -8,6 +8,6 @@ const getUserToken = async (req: Request, res: Response, next: NextFunction) => res.json(data) } -module.exports = { +export { getUserToken, } \ No newline at end of file diff --git a/packages/backend/src/Controllers/registerVoterController.ts b/packages/backend/src/Controllers/registerVoterController.ts index 0f515195..21dbf81a 100644 --- a/packages/backend/src/Controllers/registerVoterController.ts +++ b/packages/backend/src/Controllers/registerVoterController.ts @@ -73,6 +73,6 @@ const registerVoter = async (req: IElectionRequest, res: Response, next: NextFun return next() } -module.exports = { +export { registerVoter } diff --git a/packages/backend/src/Controllers/sandboxController.ts b/packages/backend/src/Controllers/sandboxController.ts index ccca9d43..40cbd6f0 100644 --- a/packages/backend/src/Controllers/sandboxController.ts +++ b/packages/backend/src/Controllers/sandboxController.ts @@ -30,6 +30,6 @@ const getSandboxResults = async (req: Request, res: Response, next: NextFunction ); } -module.exports = { +export { getSandboxResults } \ No newline at end of file diff --git a/packages/backend/src/Controllers/sendInvitesController.ts b/packages/backend/src/Controllers/sendInvitesController.ts index 6d16f9c1..536e4206 100644 --- a/packages/backend/src/Controllers/sendInvitesController.ts +++ b/packages/backend/src/Controllers/sendInvitesController.ts @@ -60,7 +60,7 @@ const sendInvitationsController = async (req: IElectionRequest, res: Response, n await sendBatchEmailInvites(req, electionRollFiltered, election) - return res.json({}) + res.json({}) } async function sendBatchEmailInvites(req: any, electionRoll: ElectionRoll[], election: Election) { @@ -168,7 +168,7 @@ async function sendInvitation(ctx: any, election:Election, electionRoll: Electio } } -module.exports = { +export { sendInvitationsController, sendInvitationController, handleSendInviteEvent, diff --git a/packages/backend/src/Controllers/setPublicResultsController.ts b/packages/backend/src/Controllers/setPublicResultsController.ts index a24fef7b..92d67063 100644 --- a/packages/backend/src/Controllers/setPublicResultsController.ts +++ b/packages/backend/src/Controllers/setPublicResultsController.ts @@ -28,9 +28,9 @@ const setPublicResults = async (req: IElectionRequest, res: Response, next: Next throw new BadRequest(failMsg) } - return res.json({ election: updatedElection }) + res.json({ election: updatedElection }) } -module.exports = { +export { setPublicResults, } \ No newline at end of file diff --git a/packages/backend/src/Controllers/uploadImageController.ts b/packages/backend/src/Controllers/uploadImageController.ts index ba6806d4..021246f0 100644 --- a/packages/backend/src/Controllers/uploadImageController.ts +++ b/packages/backend/src/Controllers/uploadImageController.ts @@ -65,13 +65,13 @@ const uploadImageController = async (req: ImageRequest, res: Response, next: Nex const uploadResult = await parallelUploads3.done(); const photo_filename = uploadResult.Location; Logger.info(req, `File uploaded successfully. ${photo_filename}`); - return res.json({ photo_filename }); + res.json({ photo_filename }); } catch (e: any) { throw new InternalServerError(e); } } -module.exports = { +export { uploadImageController, upload, } diff --git a/packages/backend/src/Express/index.d.ts b/packages/backend/src/Express/index.d.ts index 4d56eaeb..59f490ed 100644 --- a/packages/backend/src/Express/index.d.ts +++ b/packages/backend/src/Express/index.d.ts @@ -4,6 +4,8 @@ import { permission, permissions } from '@equal-vote/star-vote-shared/domain_mod type p = keyof typeof permissions export {} +//apparently this is expresses official recommedation for extending types in typescript. I had to add +// this to get elections.routes.ts to work. declare global { namespace Express { interface Request { @@ -17,6 +19,7 @@ declare global { } authorized_voter?: boolean; has_voted?: boolean; + file: any } } } diff --git a/packages/backend/src/OpenApi/openapi.json b/packages/backend/src/OpenApi/openapi.json index b67709be..d6793859 100644 --- a/packages/backend/src/OpenApi/openapi.json +++ b/packages/backend/src/OpenApi/openapi.json @@ -1750,6 +1750,31 @@ "description": "Election not found" } } + }, + "delete": { + "summary": "Delete election by ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election deleted" + }, + "404": { + "description": "Election not found" + } + } } }, "/Election/{_id}/exists": { @@ -1761,7 +1786,7 @@ "parameters": [ { "in": "path", - "name": "_id", + "name": "id", "schema": { "type": "string" }, @@ -1771,7 +1796,19 @@ ], "responses": { "200": { - "description": "Election exists" + "description": "Election exists", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "exists": { + "type": "boolean" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -1798,7 +1835,20 @@ ], "responses": { "200": { - "description": "Election ballot returned" + "description": "Election ballot returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballot": { + "type": "object", + "$ref": "#/components/schemas/NewBallot" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -1825,7 +1875,24 @@ ], "responses": { "200": { - "description": "Voter registered" + "description": "Voter registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "NewElectionRoll": { + "type": "object", + "$ref": "#/components/schemas/NewElectionRoll" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -1852,7 +1919,26 @@ ], "responses": { "200": { - "description": "List of ballots" + "description": "List of ballots", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "ballots": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Ballot" + } + } + } + } + } + } }, "404": { "description": "Election not found" @@ -1877,7 +1963,20 @@ ], "responses": { "200": { - "description": "All ballots deleted" + "description": "All ballots deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "If the deletion was successful" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -1913,7 +2012,20 @@ ], "responses": { "200": { - "description": "Ballot details" + "description": "Ballot details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballot": { + "type": "object", + "$ref": "#/components/schemas/Ballot" + } + } + } + } + } }, "404": { "description": "Ballot not found" @@ -1940,7 +2052,20 @@ ], "responses": { "200": { - "description": "List of rolls" + "description": "List of rolls", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -1965,7 +2090,24 @@ ], "responses": { "200": { - "description": "Roll added" + "description": "Roll added", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "NewElectionRoll": { + "type": "object", + "$ref": "#/components/schemas/NewElectionRoll" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -1990,7 +2132,20 @@ ], "responses": { "200": { - "description": "Roll edited" + "description": "Roll edited", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2026,7 +2181,20 @@ ], "responses": { "200": { - "description": "Roll details" + "description": "Roll details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } }, "404": { "description": "Roll not found" @@ -2150,7 +2318,71 @@ ], "responses": { "200": { - "description": "List of elections" + "description": "List of elections", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "elections_as_official": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + } + ] + }, + "elections_as_unsubmitted_voter": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + }, + { + "type": "undefined" + } + ] + }, + "elections_as_submitted_voter": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + } + ] + }, + "open_elections": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + } + ] + } + } + } + } + } } } }, @@ -2161,7 +2393,20 @@ ], "responses": { "200": { - "description": "Election created" + "description": "Election created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } } } } @@ -2174,7 +2419,24 @@ ], "responses": { "200": { - "description": "Global election statistics" + "description": "Global election statistics", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "elections": { + "type": "number", + "description": "Number of elections" + }, + "votes": { + "type": "number", + "description": "Number of votes" + } + } + } + } + } } } } @@ -2198,7 +2460,43 @@ ], "responses": { "200": { - "description": "Election edited" + "description": "Election edited", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "voterAuth": { + "type": "object", + "properties": { + "authorized_voter": { + "type": "boolean" + }, + "has_voted": { + "type": "boolean" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/roles" + } + }, + "permissions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/permissions" + } + } + } + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2225,7 +2523,20 @@ ], "responses": { "200": { - "description": "Roles edited" + "description": "Roles edited", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2252,7 +2563,24 @@ ], "responses": { "200": { - "description": "Election results" + "description": "Election results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "results": { + "type": "object", + "$ref": "#/components/schemas/ElectionResults" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2279,7 +2607,20 @@ ], "responses": { "200": { - "description": "Vote cast" + "description": "Vote cast", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballot": { + "type": "object", + "$ref": "#/components/schemas/Ballot" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2306,7 +2647,20 @@ ], "responses": { "200": { - "description": "Election finalized" + "description": "Election finalized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2333,7 +2687,20 @@ ], "responses": { "200": { - "description": "Public results set" + "description": "Public results set", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2360,7 +2727,20 @@ ], "responses": { "200": { - "description": "Election archived" + "description": "Election archived", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } }, "404": { "description": "Election not found" @@ -2423,7 +2803,20 @@ ], "responses": { "200": { - "description": "Invitation sent" + "description": "Invitation sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } }, "404": { "description": "Election or voter not found" @@ -2439,7 +2832,28 @@ ], "responses": { "200": { - "description": "Sandbox results" + "description": "Sandbox results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "object", + "$ref": "#/components/schemas/ElectionResults" + }, + "nWinners": { + "type": "number", + "description": "Number of winners" + }, + "candidates": { + "type": "array", + "items": "string" + } + } + } + } + } } } } @@ -2467,7 +2881,22 @@ }, "responses": { "200": { - "description": "Image uploaded" + "description": "Image uploaded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "photo_filename": { + "typeOf": [ + "string", + null + ] + } + } + } + } + } } } } diff --git a/packages/backend/src/Routes/debug.routes.js b/packages/backend/src/Routes/debug.routes.js index 6638758b..d85bfab7 100644 --- a/packages/backend/src/Routes/debug.routes.js +++ b/packages/backend/src/Routes/debug.routes.js @@ -22,4 +22,4 @@ router.get('/test', (req, res) => { ) }) -module.exports = router \ No newline at end of file +export default router \ No newline at end of file diff --git a/packages/backend/src/Routes/elections.routes.ts b/packages/backend/src/Routes/elections.routes.ts index fe1e6ec4..7935c792 100644 --- a/packages/backend/src/Routes/elections.routes.ts +++ b/packages/backend/src/Routes/elections.routes.ts @@ -45,7 +45,6 @@ import asyncHandler from 'express-async-handler'; import { IElectionRequest } from '../IRequest'; - /** * @swagger @@ -69,11 +68,13 @@ import { IElectionRequest } from '../IRequest'; * type: object * $ref: '#/components/schemas/Election' * 404: - * description: Election not found - */ + * description: Election not found +*/ + router.get('/Election/:id', asyncHandler(returnElection)); -/** + +/** * @swagger * /Election/{_id}/exists: * get: @@ -81,7 +82,7 @@ router.get('/Election/:id', asyncHandler(returnElection)); * tags: [Elections] * parameters: * - in: path - * name: _id + * name: id * schema: * type: string * required: true @@ -103,6 +104,27 @@ router.get('/Election/:id', asyncHandler(returnElection)); router.get('/Election/:_id/exists', asyncHandler(electionExistsByID)) /** + * @swagger + * /Election/{id}: + * delete: + * summary: Delete election by ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election deleted + * 404: + * description: Election not found +*/ +router.delete('/Election/:id', asyncHandler(deleteElection)) + +/** * @swagger * /Election/{id}/ballot: * post: @@ -125,14 +147,14 @@ router.get('/Election/:_id/exists', asyncHandler(electionExistsByID)) * properties: * ballot: * type: object - * $ref: '#/components/schemas/NewBallot' + * $ref: '#/components/schemas/NewBallot' * * 404: * description: Election not found */ router.post('/Election/:id/ballot', asyncHandler(returnElection)) -/** +/** * @swagger * /Election/{id}/register: * post: @@ -148,11 +170,24 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Voter registered + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * NewElectionRoll: + * type: object + * $ref: '#/components/schemas/NewElectionRoll' * 404: * description: Election not found */ +router.post('/Election/:id/register',asyncHandler(registerVoter)) -/** + +/** * @swagger * /Election/{id}/ballots: * get: @@ -168,11 +203,25 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: List of ballots + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * ballots: + * type: array + * items: + * $ref: '#/components/schemas/Ballot' * 404: * description: Election not found - */ +*/ +router.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) -/** + +/** * @swagger * /Election/{id}/ballots: * delete: @@ -188,11 +237,20 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: All ballots deleted + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: If the deletion was successful * 404: - * description: Election not found - */ + * description: Election not found */ +router.delete('/Election/:id/ballots', asyncHandler(deleteAllBallotsForElectionID)) -/** + +/** * @swagger * /Election/{id}/ballot/{ballot_id}: * get: @@ -214,11 +272,23 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Ballot details + * content: + * application/json: + * schema: + * type: object + * properties: + * ballot: + * type: object + * $ref: '#/components/schemas/Ballot' + * * 404: - * description: Ballot not found - */ + * description: Ballot not found +*/ +router.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBallotID)) -/** + + +/** * @swagger * /Election/{id}/rolls: * get: @@ -234,11 +304,22 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: List of rolls + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * 404: - * description: Election not found - */ + * description: Election not found +*/ +router.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) -/** + + +/** * @swagger * /Election/{id}/rolls/{voter_id}: * get: @@ -260,11 +341,21 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Roll details + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * 404: - * description: Roll not found - */ + * description: Roll not found +*/ +router.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) -/** + +/** * @swagger * /Election/{id}/rolls: * post: @@ -280,11 +371,23 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Roll added + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * NewElectionRoll: + * type: object + * $ref: '#/components/schemas/NewElectionRoll' * 404: * description: Election not found */ +router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) -/** +/** * @swagger * /Election/{id}/rolls: * put: @@ -300,11 +403,19 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Roll edited + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * 404: - * description: Election not found - */ + * description: Election not found */ + router.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) -/** + /** * @swagger * /Election/{id}/rolls/approve: * post: @@ -321,10 +432,10 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * 200: * description: Roll approved * 404: - * description: Election not found - */ + * description: Election not found */ + router.post('/Election/:id/rolls/approve', asyncHandler(approveElectionRoll)) -/** + /** * @swagger * /Election/{id}/rolls/flag: * post: @@ -341,11 +452,10 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * 200: * description: Roll flagged * 404: - * description: Election not found - */ + * description: Election not found */ + router.post('/Election/:id/rolls/flag', asyncHandler(flagElectionRoll)) -/** - * @swagger + /** @swagger * /Election/{id}/rolls/invalidate: * post: * summary: Invalidate an election roll @@ -361,10 +471,11 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * 200: * description: Roll invalidated * 404: - * description: Election not found - */ - -/** + * description: Election not found */ + router.post('/Election/:id/rolls/invalidate', asyncHandler(invalidateElectionRoll)) + +/** + * * @swagger * /Election/{id}/rolls/unflag: * post: @@ -381,10 +492,10 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * 200: * description: Roll unflagged * 404: - * description: Election not found - */ + * description: Election not found */ + router.post('/Election/:id/rolls/unflag', asyncHandler(uninvalidateElectionRoll)) -/** +/** * @swagger * /Elections: * get: @@ -393,9 +504,40 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: List of elections - */ - -/** + * content: + * application/json: + * schema: + * type: object + * properties: + * elections_as_official: + * oneOf: + * - type: array + * items: + * $ref: '#/components/schemas/Election' + * - type: null + * elections_as_unsubmitted_voter: + * oneOf: + * - type: array + * items: + * $ref: '#/components/schemas/Election' + * - type: null + * - type: undefined + * elections_as_submitted_voter: + * oneOf: + * - type: array + * items: + * $ref: '#/components/schemas/Election' + * - type: null + * open_elections: + * oneOf: + * - type: array + * items: + * $ref: '#/components/schemas/Election' + * - type: null + */ + router.get('/Elections', asyncHandler(getElections)) + +/** * @swagger * /Elections: * post: @@ -404,9 +546,18 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Election created - */ - -/** + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * */ + router.post('/Elections/', asyncHandler(createElectionController)) + /** + * * @swagger * /GlobalElectionStats: * get: @@ -415,9 +566,20 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Global election statistics - */ - -/** + * content: + * application/json: + * schema: + * type: object + * properties: + * elections: + * type: number + * description: Number of elections + * votes: + * type: number + * description: Number of votes + * */ + router.get('/GlobalElectionStats', asyncHandler(getGlobalElectionStats)) +/** * @swagger * /Election/{id}/edit: * post: @@ -433,11 +595,34 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Election edited + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * voterAuth: + * type: object + * properties: + * authorized_voter: + * type: boolean + * has_voted: + * type: boolean + * roles: + * type: array + * items: + * $ref: '#/components/schemas/roles' + * permissions: + * type: array + * items: + * $ref: '#/components/schemas/permissions' * 404: - * description: Election not found - */ + * description: Election not found */ + router.post('/Election/:id/edit', asyncHandler(editElection)) -/** + /** * @swagger * /Election/{id}/roles: * put: @@ -453,11 +638,19 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Roles edited + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' * 404: - * description: Election not found - */ - -/** + * description: Election not found */ + router.put('/Election/:id/roles', asyncHandler(editElectionRoles)) + +/** * @swagger * /ElectionResult/{id}: * get: @@ -473,11 +666,22 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Election results + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * results: + * type: object + * $ref: '#/components/schemas/ElectionResults' * 404: - * description: Election not found - */ - -/** + * description: Election not found */ + router.get('/ElectionResult/:id', asyncHandler(getElectionResults)) + +/** * @swagger * /Election/{id}/vote: * post: @@ -493,11 +697,20 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Vote cast + * content: + * application/json: + * schema: + * type: object + * properties: + * ballot: + * type: object + * $ref: '#/components/schemas/Ballot' * 404: - * description: Election not found - */ + * description: Election not found */ + router.post('/Election/:id/vote', asyncHandler(castVoteController)) -/** + +/** * @swagger * /Election/{id}/finalize: * post: @@ -513,11 +726,19 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Election finalized + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' * 404: - * description: Election not found - */ + * description: Election not found */ +router.post('/Election/:id/finalize',asyncHandler(finalizeElection)) -/** +/** * @swagger * /Election/{id}/setPublicResults: * post: @@ -533,11 +754,19 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Public results set + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' * 404: - * description: Election not found - */ + * description: Election not found */ +router.post('/Election/:id/setPublicResults',asyncHandler(setPublicResults)) -/** + /** * @swagger * /Election/{id}/archive: * post: @@ -553,11 +782,20 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Election archived + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' * 404: - * description: Election not found - */ + * description: Election not found +*/ +router.post('/Election/:id/archive', asyncHandler(archiveElection)) -/** +/** * @swagger * /Election/{id}/sendInvites: * post: @@ -574,10 +812,9 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * 200: * description: Invitations sent * 404: - * description: Election not found - */ - -/** + * description: Election not found */ +router.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsController)) +/** * @swagger * /Election/{id}/sendInvite/{voter_id}: * post: @@ -599,11 +836,19 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Invitation sent + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * 404: - * description: Election or voter not found - */ + * description: Election or voter not found */ +router.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationController)) -/** + /** * @swagger * /Sandbox: * post: @@ -612,9 +857,23 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Sandbox results - */ - -/** + * content: + * application/json: + * schema: + * type: object + * properties: + * results: + * type: object + * $ref: '#/components/schemas/ElectionResults' + * nWinners: + * type: number + * description: Number of winners + * candidates: + * type: array + * items: string */ + router.post('/Sandbox',asyncHandler(getSandboxResults)) + +/** * @swagger * /images: * post: @@ -632,39 +891,22 @@ router.post('/Election/:id/ballot', asyncHandler(returnElection)) * responses: * 200: * description: Image uploaded + * content: + * application/json: + * schema: + * type: object + * properties: + * photo_filename: + * typeOf: + * - string + * - null */ +router.post('/images',upload.single("file"), asyncHandler(uploadImageController)) -// using _id so that it doesn't trigger async handers in routes.params -router.delete('/Election/:id', asyncHandler(deleteElection)); -router.post('/Election/:id/register',asyncHandler(registerVoter)) -router.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) -router.delete('/Election/:id/ballots', asyncHandler(deleteAllBallotsForElectionID)) -router.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBallotID)) -router.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) -router.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) -router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) -router.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) -router.post('/Election/:id/rolls/approve', asyncHandler(approveElectionRoll)) -router.post('/Election/:id/rolls/flag', asyncHandler(flagElectionRoll)) -router.post('/Election/:id/rolls/invalidate', asyncHandler(invalidateElectionRoll)) -router.post('/Election/:id/rolls/unflag', asyncHandler(uninvalidateElectionRoll)) -router.get('/Elections', asyncHandler(getElections)) -router.post('/Elections/', asyncHandler(createElectionController.createElectionController)) -router.get('/GlobalElectionStats', asyncHandler(getGlobalElectionStats)) - -router.post('/Election/:id/edit', asyncHandler(editElection)) -router.put('/Election/:id/roles', asyncHandler(editElectionRoles)) -router.get('/ElectionResult/:id', asyncHandler(getElectionResults)) -router.post('/Election/:id/vote', asyncHandler(castVoteController)) -router.post('/Election/:id/finalize',asyncHandler(finalizeElection)) -router.post('/Election/:id/setPublicResults',asyncHandler(setPublicResults)) -router.post('/Election/:id/archive', asyncHandler(archiveElection)) -router.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsController)) -router.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationController)) -router.post('/Sandbox',asyncHandler(getSandboxResults)) + + -router.post('/images',upload.single("file"), asyncHandler(uploadImageController)) //router.param('id', asyncHandler(electionController.getElectionByID)) router.param('id', asyncHandler(getElectionByID)) diff --git a/packages/backend/src/Routes/routes.yaml b/packages/backend/src/Routes/routes.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/backend/src/Tabulators/ParseData.ts b/packages/backend/src/Tabulators/ParseData.ts index d7ec827c..295c5ebe 100644 --- a/packages/backend/src/Tabulators/ParseData.ts +++ b/packages/backend/src/Tabulators/ParseData.ts @@ -22,7 +22,7 @@ function getStarBallotValidity(ballot: ballot) { return { isValid: true, isUnderVote: isUnderVote } } -module.exports = function ParseData(data: ballot[], validityCheck = getStarBallotValidity): IparsedData { +export function ParseData(data: ballot[], validityCheck = getStarBallotValidity): IparsedData { // Initialize arrays const scores: ballot[] = []; const validVotes: voter[] = []; @@ -53,4 +53,4 @@ module.exports = function ParseData(data: ballot[], validityCheck = getStarBallo }; } -// module.exports = ParseData \ No newline at end of file +// export \ No newline at end of file diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 7287f291..7ce09888 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -1,6 +1,6 @@ require('dotenv').config(); import express from 'express'; -var electionRouter = require('./Routes/elections.routes') +import electionRouter from './Routes/elections.routes' // var debugRouter = require('./Routes/debug.routes') import cors from 'cors'; From e0743de0ecf2f27a2bd01ca281ef8226d2bafb64 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Sun, 22 Sep 2024 22:45:05 -0600 Subject: [PATCH 03/11] organize controllers --- .../{ => Ballot}/ballots.controllers.ts | 8 +- .../{ => Ballot}/castVoteController.ts | 20 +- ...deleteAllBallotsForElectionIDController.ts | 8 +- .../getBallotByBallotIDController.ts} | 6 +- .../getBallotsByElectionIDController.ts | 8 +- .../backend/src/Controllers/Ballot/index.ts | 5 + .../archiveElectionController.ts | 8 +- .../createElectionController.ts | 10 +- .../deleteElectionController.ts | 12 +- .../{ => Election}/editElectionController.ts | 10 +- .../editElectionRolesController.ts | 10 +- .../{ => Election}/elections.controllers.ts | 10 +- .../finalizeElectionController.ts | 12 +- .../getElectionResultsController.ts | 10 +- .../{ => Election}/getElectionsController.ts | 6 +- .../backend/src/Controllers/Election/index.ts | 12 + .../{ => Election}/sandboxController.ts | 4 +- .../{ => Election}/sendInvitesController.ts | 10 +- .../setPublicResultsController.ts | 8 +- .../{ => Roll}/addElectionRollController.ts | 10 +- .../changeElectionRollController.ts | 10 +- .../{ => Roll}/editElectionRollController.ts | 10 +- .../{ => Roll}/getElectionRollController.ts | 8 +- .../backend/src/Controllers/Roll/index.ts | 6 + .../{ => Roll}/registerVoterController.ts | 8 +- .../Controllers/{ => Roll}/voterRollUtils.ts | 10 +- .../{ => User}/auth.controllers.ts | 8 +- .../{ => User}/getUserTokenController.ts | 2 +- .../backend/src/Controllers/User/index.ts | 2 + packages/backend/src/Controllers/index.ts | 6 + packages/backend/src/Routes/ballot.routes.ts | 169 ++++++ packages/backend/src/Routes/debug.routes.js | 2 +- .../backend/src/Routes/elections.routes.ts | 500 ++---------------- packages/backend/src/Routes/registerEvents.ts | 4 +- packages/backend/src/Routes/roll.routes.ts | 251 +++++++++ packages/backend/src/Routes/user.routes.ts | 0 packages/backend/src/app.ts | 5 +- packages/backend/src/socketHandler.ts | 5 +- 38 files changed, 614 insertions(+), 579 deletions(-) rename packages/backend/src/Controllers/{ => Ballot}/ballots.controllers.ts (80%) rename packages/backend/src/Controllers/{ => Ballot}/castVoteController.ts (91%) rename packages/backend/src/Controllers/{ => Ballot}/deleteAllBallotsForElectionIDController.ts (85%) rename packages/backend/src/Controllers/{getBallotByBallotID.ts => Ballot/getBallotByBallotIDController.ts} (85%) rename packages/backend/src/Controllers/{ => Ballot}/getBallotsByElectionIDController.ts (81%) create mode 100644 packages/backend/src/Controllers/Ballot/index.ts rename packages/backend/src/Controllers/{ => Election}/archiveElectionController.ts (85%) rename packages/backend/src/Controllers/{ => Election}/createElectionController.ts (84%) rename packages/backend/src/Controllers/{ => Election}/deleteElectionController.ts (76%) rename packages/backend/src/Controllers/{ => Election}/editElectionController.ts (88%) rename packages/backend/src/Controllers/{ => Election}/editElectionRolesController.ts (85%) rename packages/backend/src/Controllers/{ => Election}/elections.controllers.ts (95%) rename packages/backend/src/Controllers/{ => Election}/finalizeElectionController.ts (84%) rename packages/backend/src/Controllers/{ => Election}/getElectionResultsController.ts (90%) rename packages/backend/src/Controllers/{ => Election}/getElectionsController.ts (95%) create mode 100644 packages/backend/src/Controllers/Election/index.ts rename packages/backend/src/Controllers/{ => Election}/sandboxController.ts (88%) rename packages/backend/src/Controllers/{ => Election}/sendInvitesController.ts (96%) rename packages/backend/src/Controllers/{ => Election}/setPublicResultsController.ts (85%) rename packages/backend/src/Controllers/{ => Roll}/addElectionRollController.ts (92%) rename packages/backend/src/Controllers/{ => Roll}/changeElectionRollController.ts (92%) rename packages/backend/src/Controllers/{ => Roll}/editElectionRollController.ts (83%) rename packages/backend/src/Controllers/{ => Roll}/getElectionRollController.ts (90%) create mode 100644 packages/backend/src/Controllers/Roll/index.ts rename packages/backend/src/Controllers/{ => Roll}/registerVoterController.ts (93%) rename packages/backend/src/Controllers/{ => Roll}/voterRollUtils.ts (96%) rename packages/backend/src/Controllers/{ => User}/auth.controllers.ts (85%) rename packages/backend/src/Controllers/{ => User}/getUserTokenController.ts (85%) create mode 100644 packages/backend/src/Controllers/User/index.ts create mode 100644 packages/backend/src/Controllers/index.ts create mode 100644 packages/backend/src/Routes/ballot.routes.ts create mode 100644 packages/backend/src/Routes/roll.routes.ts create mode 100644 packages/backend/src/Routes/user.routes.ts diff --git a/packages/backend/src/Controllers/ballots.controllers.ts b/packages/backend/src/Controllers/Ballot/ballots.controllers.ts similarity index 80% rename from packages/backend/src/Controllers/ballots.controllers.ts rename to packages/backend/src/Controllers/Ballot/ballots.controllers.ts index cdedee70..50d4b9ef 100644 --- a/packages/backend/src/Controllers/ballots.controllers.ts +++ b/packages/backend/src/Controllers/Ballot/ballots.controllers.ts @@ -1,8 +1,8 @@ import { Ballot as BallotType, ballotValidation } from '@equal-vote/star-vote-shared/domain_model/Ballot'; -import { reqIdSuffix } from "../IRequest"; -import Logger from "../Services/Logging/Logger"; -import ServiceLocator from "../ServiceLocator"; -import { responseErr } from '../Util'; +import { reqIdSuffix } from "../../IRequest"; +import Logger from "../../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import { responseErr } from '../../Util'; var BallotModel = ServiceLocator.ballotsDb(); const className = 'Ballots.Controllers'; diff --git a/packages/backend/src/Controllers/castVoteController.ts b/packages/backend/src/Controllers/Ballot/castVoteController.ts similarity index 91% rename from packages/backend/src/Controllers/castVoteController.ts rename to packages/backend/src/Controllers/Ballot/castVoteController.ts index 04e53411..1b26eb98 100644 --- a/packages/backend/src/Controllers/castVoteController.ts +++ b/packages/backend/src/Controllers/Ballot/castVoteController.ts @@ -1,19 +1,19 @@ import { Election, electionValidation } from "@equal-vote/star-vote-shared/domain_model/Election"; import { ElectionRoll, ElectionRollState } from "@equal-vote/star-vote-shared/domain_model/ElectionRoll"; import { Ballot, Ballot as BallotType, ballotValidation } from '@equal-vote/star-vote-shared/domain_model/Ballot'; -import { IRequest } from "../IRequest"; -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import { IRequest } from "../../IRequest"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { BadRequest, InternalServerError, Unauthorized } from "@curveball/http-errors"; -import { ILoggingContext } from "../Services/Logging/ILogger"; +import { ILoggingContext } from "../../Services/Logging/ILogger"; import { randomUUID } from "crypto"; import { Uid } from "@equal-vote/star-vote-shared/domain_model/Uid"; -import { Receipt } from "../Services/Email/EmailTemplates" -import { getOrCreateElectionRoll, checkForMissingAuthenticationData, getVoterAuthorization } from "./voterRollUtils" -const { innerGetGlobalElectionStats} = require('./getElectionsController') -import { IElectionRequest } from "../IRequest"; +import { Receipt } from "../../Services/Email/EmailTemplates" +import { getOrCreateElectionRoll, checkForMissingAuthenticationData, getVoterAuthorization } from "../Roll/voterRollUtils" +import { innerGetGlobalElectionStats } from "../Election"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; -import { io } from "../socketHandler"; +import { io } from "../../socketHandler"; import { Server } from "socket.io"; const ElectionsModel = ServiceLocator.electionsDb(); @@ -117,7 +117,7 @@ async function castVoteController(req: IElectionRequest, res: Response, next: Ne await (await EventQueue).publish(castVoteEventQueue, event); if(io != null){ // necessary for tests - (io as Server).to('landing_page').emit('updated_stats', await innerGetGlobalElectionStats()); + (io as Server).to('landing_page').emit('updated_stats', await innerGetGlobalElectionStats(req)); } res.status(200).json({ ballot: inputBallot} ); diff --git a/packages/backend/src/Controllers/deleteAllBallotsForElectionIDController.ts b/packages/backend/src/Controllers/Ballot/deleteAllBallotsForElectionIDController.ts similarity index 85% rename from packages/backend/src/Controllers/deleteAllBallotsForElectionIDController.ts rename to packages/backend/src/Controllers/Ballot/deleteAllBallotsForElectionIDController.ts index 5ba4130b..a02bed7f 100644 --- a/packages/backend/src/Controllers/deleteAllBallotsForElectionIDController.ts +++ b/packages/backend/src/Controllers/Ballot/deleteAllBallotsForElectionIDController.ts @@ -1,9 +1,9 @@ -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { BadRequest } from "@curveball/http-errors"; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; const BallotModel = ServiceLocator.ballotsDb(); diff --git a/packages/backend/src/Controllers/getBallotByBallotID.ts b/packages/backend/src/Controllers/Ballot/getBallotByBallotIDController.ts similarity index 85% rename from packages/backend/src/Controllers/getBallotByBallotID.ts rename to packages/backend/src/Controllers/Ballot/getBallotByBallotIDController.ts index 1fd76a93..39e1ed8e 100644 --- a/packages/backend/src/Controllers/getBallotByBallotID.ts +++ b/packages/backend/src/Controllers/Ballot/getBallotByBallotIDController.ts @@ -1,7 +1,7 @@ -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { BadRequest } from "@curveball/http-errors"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; const BallotModel = ServiceLocator.ballotsDb(); diff --git a/packages/backend/src/Controllers/getBallotsByElectionIDController.ts b/packages/backend/src/Controllers/Ballot/getBallotsByElectionIDController.ts similarity index 81% rename from packages/backend/src/Controllers/getBallotsByElectionIDController.ts rename to packages/backend/src/Controllers/Ballot/getBallotsByElectionIDController.ts index 81215ab2..e6c31b73 100644 --- a/packages/backend/src/Controllers/getBallotsByElectionIDController.ts +++ b/packages/backend/src/Controllers/Ballot/getBallotsByElectionIDController.ts @@ -1,9 +1,9 @@ -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { BadRequest } from "@curveball/http-errors"; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; const BallotModel = ServiceLocator.ballotsDb(); diff --git a/packages/backend/src/Controllers/Ballot/index.ts b/packages/backend/src/Controllers/Ballot/index.ts new file mode 100644 index 00000000..c36fb85d --- /dev/null +++ b/packages/backend/src/Controllers/Ballot/index.ts @@ -0,0 +1,5 @@ +export * from './ballots.controllers'; +export * from './castVoteController'; +export * from './deleteAllBallotsForElectionIDController'; +export * from './getBallotByBallotIDController'; +export * from './getBallotsByElectionIDController'; \ No newline at end of file diff --git a/packages/backend/src/Controllers/archiveElectionController.ts b/packages/backend/src/Controllers/Election/archiveElectionController.ts similarity index 85% rename from packages/backend/src/Controllers/archiveElectionController.ts rename to packages/backend/src/Controllers/Election/archiveElectionController.ts index 70ac5d1b..b7bcf7a9 100644 --- a/packages/backend/src/Controllers/archiveElectionController.ts +++ b/packages/backend/src/Controllers/Election/archiveElectionController.ts @@ -1,10 +1,10 @@ -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest, InternalServerError } from "@curveball/http-errors"; import { Election } from '@equal-vote/star-vote-shared/domain_model/Election'; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; var ElectionsModel = ServiceLocator.electionsDb(); diff --git a/packages/backend/src/Controllers/createElectionController.ts b/packages/backend/src/Controllers/Election/createElectionController.ts similarity index 84% rename from packages/backend/src/Controllers/createElectionController.ts rename to packages/backend/src/Controllers/Election/createElectionController.ts index 6be9ea37..5c81663c 100644 --- a/packages/backend/src/Controllers/createElectionController.ts +++ b/packages/backend/src/Controllers/Election/createElectionController.ts @@ -1,11 +1,11 @@ import { Election, electionValidation } from "@equal-vote/star-vote-shared/domain_model/Election"; import { ElectionRoll, ElectionRollState } from "@equal-vote/star-vote-shared/domain_model/ElectionRoll"; -import { IRequest } from "../IRequest"; -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import { IRequest } from "../../IRequest"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { InternalServerError } from "@curveball/http-errors"; -import { ILoggingContext } from "../Services/Logging/ILogger"; -import { expectValidElectionFromRequest, catchAndRespondError, expectPermission } from "./controllerUtils"; +import { ILoggingContext } from "../../Services/Logging/ILogger"; +import { expectValidElectionFromRequest, catchAndRespondError, expectPermission } from "../controllerUtils"; import { Response, NextFunction } from "express"; var ElectionsModel = ServiceLocator.electionsDb(); diff --git a/packages/backend/src/Controllers/deleteElectionController.ts b/packages/backend/src/Controllers/Election/deleteElectionController.ts similarity index 76% rename from packages/backend/src/Controllers/deleteElectionController.ts rename to packages/backend/src/Controllers/Election/deleteElectionController.ts index 1fad3fa9..c324c78f 100644 --- a/packages/backend/src/Controllers/deleteElectionController.ts +++ b/packages/backend/src/Controllers/Election/deleteElectionController.ts @@ -1,11 +1,11 @@ -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; -import { responseErr } from '../Util'; -import { IRequest } from '../IRequest'; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; +import { responseErr } from '../../Util'; +import { IRequest } from '../../IRequest'; import { hasPermission, permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest } from "@curveball/http-errors"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; var ElectionsModel = ServiceLocator.electionsDb(); diff --git a/packages/backend/src/Controllers/editElectionController.ts b/packages/backend/src/Controllers/Election/editElectionController.ts similarity index 88% rename from packages/backend/src/Controllers/editElectionController.ts rename to packages/backend/src/Controllers/Election/editElectionController.ts index f94eeac9..3eb6a558 100644 --- a/packages/backend/src/Controllers/editElectionController.ts +++ b/packages/backend/src/Controllers/Election/editElectionController.ts @@ -1,11 +1,11 @@ import { electionValidation } from '@equal-vote/star-vote-shared/domain_model/Election'; -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; -import { responseErr } from '../Util'; -import { expectPermission } from "./controllerUtils"; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; +import { responseErr } from '../../Util'; +import { expectPermission } from "../controllerUtils"; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; import { BadRequest } from "@curveball/http-errors"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; var ElectionsModel = ServiceLocator.electionsDb(); diff --git a/packages/backend/src/Controllers/editElectionRolesController.ts b/packages/backend/src/Controllers/Election/editElectionRolesController.ts similarity index 85% rename from packages/backend/src/Controllers/editElectionRolesController.ts rename to packages/backend/src/Controllers/Election/editElectionRolesController.ts index 2050b14b..a580bde8 100644 --- a/packages/backend/src/Controllers/editElectionRolesController.ts +++ b/packages/backend/src/Controllers/Election/editElectionRolesController.ts @@ -1,11 +1,11 @@ import { electionValidation } from '@equal-vote/star-vote-shared/domain_model/Election'; -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; -import { responseErr } from '../Util'; -import { expectPermission } from "./controllerUtils"; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; +import { responseErr } from '../../Util'; +import { expectPermission } from "../controllerUtils"; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; import { BadRequest } from "@curveball/http-errors"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; var ElectionsModel = ServiceLocator.electionsDb(); diff --git a/packages/backend/src/Controllers/elections.controllers.ts b/packages/backend/src/Controllers/Election/elections.controllers.ts similarity index 95% rename from packages/backend/src/Controllers/elections.controllers.ts rename to packages/backend/src/Controllers/Election/elections.controllers.ts index 795e297a..6d00b79b 100644 --- a/packages/backend/src/Controllers/elections.controllers.ts +++ b/packages/backend/src/Controllers/Election/elections.controllers.ts @@ -1,11 +1,11 @@ import { Election, removeHiddenFields } from '@equal-vote/star-vote-shared/domain_model/Election'; -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; -import { responseErr } from '../Util'; -import { IElectionRequest, IRequest } from '../IRequest'; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; +import { responseErr } from '../../Util'; +import { IElectionRequest, IRequest } from '../../IRequest'; import { roles } from "@equal-vote/star-vote-shared/domain_model/roles" import { getPermissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { getOrCreateElectionRoll, checkForMissingAuthenticationData, getVoterAuthorization } from "./voterRollUtils" +import { getOrCreateElectionRoll, checkForMissingAuthenticationData, getVoterAuthorization } from "../Roll/voterRollUtils" import { ElectionRoll } from '@equal-vote/star-vote-shared/domain_model/ElectionRoll'; diff --git a/packages/backend/src/Controllers/finalizeElectionController.ts b/packages/backend/src/Controllers/Election/finalizeElectionController.ts similarity index 84% rename from packages/backend/src/Controllers/finalizeElectionController.ts rename to packages/backend/src/Controllers/Election/finalizeElectionController.ts index 8d0d6dd2..fce6661d 100644 --- a/packages/backend/src/Controllers/finalizeElectionController.ts +++ b/packages/backend/src/Controllers/Election/finalizeElectionController.ts @@ -1,13 +1,13 @@ -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest } from "@curveball/http-errors"; import { ElectionRoll } from '@equal-vote/star-vote-shared/domain_model/ElectionRoll'; -const { sendBatchEmailInvites } = require('./sendInvitesController') -import { IElectionRequest } from "../IRequest"; +import { sendBatchEmailInvites } from './sendInvitesController'; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; -const {innerDeleteAllBallotsForElectionID} = require('./deleteAllBallotsForElectionIDController') +import { innerDeleteAllBallotsForElectionID } from '../Ballot'; var ElectionsModel = ServiceLocator.electionsDb(); var ElectionRollModel = ServiceLocator.electionRollDb(); diff --git a/packages/backend/src/Controllers/getElectionResultsController.ts b/packages/backend/src/Controllers/Election/getElectionResultsController.ts similarity index 90% rename from packages/backend/src/Controllers/getElectionResultsController.ts rename to packages/backend/src/Controllers/Election/getElectionResultsController.ts index eb70b797..f4095973 100644 --- a/packages/backend/src/Controllers/getElectionResultsController.ts +++ b/packages/backend/src/Controllers/Election/getElectionResultsController.ts @@ -1,12 +1,12 @@ -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { BadRequest } from "@curveball/http-errors"; import { Ballot } from '@equal-vote/star-vote-shared/domain_model/Ballot'; import { Score } from '@equal-vote/star-vote-shared/domain_model/Score'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { VotingMethods } from '../Tabulators/VotingMethodSelecter'; -import { IElectionRequest } from "../IRequest"; +import { VotingMethods } from '../../Tabulators/VotingMethodSelecter'; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; import { ElectionResults } from "@equal-vote/star-vote-shared/domain_model/ITabulators"; var seedrandom = require('seedrandom'); diff --git a/packages/backend/src/Controllers/getElectionsController.ts b/packages/backend/src/Controllers/Election/getElectionsController.ts similarity index 95% rename from packages/backend/src/Controllers/getElectionsController.ts rename to packages/backend/src/Controllers/Election/getElectionsController.ts index 10203bc2..0ca6ffe1 100644 --- a/packages/backend/src/Controllers/getElectionsController.ts +++ b/packages/backend/src/Controllers/Election/getElectionsController.ts @@ -1,8 +1,8 @@ -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; import { BadRequest } from "@curveball/http-errors"; import { Election, removeHiddenFields } from '@equal-vote/star-vote-shared/domain_model/Election'; -import { IElectionRequest, IRequest } from "../IRequest"; +import { IElectionRequest, IRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; diff --git a/packages/backend/src/Controllers/Election/index.ts b/packages/backend/src/Controllers/Election/index.ts new file mode 100644 index 00000000..4ab59ebe --- /dev/null +++ b/packages/backend/src/Controllers/Election/index.ts @@ -0,0 +1,12 @@ +export * from './archiveElectionController'; +export * from './createElectionController'; +export * from './deleteElectionController'; +export * from './editElectionController'; +export * from './editElectionRolesController'; +export * from './elections.controllers'; +export * from './finalizeElectionController'; +export * from './getElectionResultsController'; +export * from './getElectionsController'; +export * from './sandboxController'; +export * from './sendInvitesController'; +export * from './setPublicResultsController'; \ No newline at end of file diff --git a/packages/backend/src/Controllers/sandboxController.ts b/packages/backend/src/Controllers/Election/sandboxController.ts similarity index 88% rename from packages/backend/src/Controllers/sandboxController.ts rename to packages/backend/src/Controllers/Election/sandboxController.ts index 40cbd6f0..2114a2f8 100644 --- a/packages/backend/src/Controllers/sandboxController.ts +++ b/packages/backend/src/Controllers/Election/sandboxController.ts @@ -1,7 +1,7 @@ import { ElectionResults } from '@equal-vote/star-vote-shared/domain_model/ITabulators'; -import Logger from '../Services/Logging/Logger'; +import Logger from '../../Services/Logging/Logger'; const className = "Elections.Controllers"; -import { VotingMethods } from '../Tabulators/VotingMethodSelecter' +import { VotingMethods } from '../../Tabulators/VotingMethodSelecter' import { Request, Response, NextFunction } from 'express'; const getSandboxResults = async (req: Request, res: Response, next: NextFunction) => { diff --git a/packages/backend/src/Controllers/sendInvitesController.ts b/packages/backend/src/Controllers/Election/sendInvitesController.ts similarity index 96% rename from packages/backend/src/Controllers/sendInvitesController.ts rename to packages/backend/src/Controllers/Election/sendInvitesController.ts index 536e4206..d6ac7067 100644 --- a/packages/backend/src/Controllers/sendInvitesController.ts +++ b/packages/backend/src/Controllers/Election/sendInvitesController.ts @@ -1,14 +1,14 @@ -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest, InternalServerError } from "@curveball/http-errors"; -import { Invites } from "../Services/Email/EmailTemplates" +import { Invites } from "../../Services/Email/EmailTemplates" import { ElectionRoll } from '@equal-vote/star-vote-shared/domain_model/ElectionRoll'; import { Uid } from "@equal-vote/star-vote-shared/domain_model/Uid"; import { Election } from '@equal-vote/star-vote-shared/domain_model/Election'; import { randomUUID } from "crypto"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; var ElectionRollModel = ServiceLocator.electionRollDb(); diff --git a/packages/backend/src/Controllers/setPublicResultsController.ts b/packages/backend/src/Controllers/Election/setPublicResultsController.ts similarity index 85% rename from packages/backend/src/Controllers/setPublicResultsController.ts rename to packages/backend/src/Controllers/Election/setPublicResultsController.ts index 92d67063..a021eabf 100644 --- a/packages/backend/src/Controllers/setPublicResultsController.ts +++ b/packages/backend/src/Controllers/Election/setPublicResultsController.ts @@ -1,10 +1,10 @@ -import ServiceLocator from '../ServiceLocator'; -import Logger from '../Services/Logging/Logger'; +import ServiceLocator from '../../ServiceLocator'; +import Logger from '../../Services/Logging/Logger'; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest, InternalServerError } from "@curveball/http-errors"; import { Election } from '@equal-vote/star-vote-shared/domain_model/Election'; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; var ElectionsModel = ServiceLocator.electionsDb(); diff --git a/packages/backend/src/Controllers/addElectionRollController.ts b/packages/backend/src/Controllers/Roll/addElectionRollController.ts similarity index 92% rename from packages/backend/src/Controllers/addElectionRollController.ts rename to packages/backend/src/Controllers/Roll/addElectionRollController.ts index 2c35dee6..50d9c8e8 100644 --- a/packages/backend/src/Controllers/addElectionRollController.ts +++ b/packages/backend/src/Controllers/Roll/addElectionRollController.ts @@ -1,12 +1,12 @@ import { ElectionRoll, ElectionRollState } from "@equal-vote/star-vote-shared/domain_model/ElectionRoll"; -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; -import { responseErr } from "../Util"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; +import { responseErr } from "../../Util"; import { hasPermission, permission, permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest, InternalServerError } from "@curveball/http-errors"; import { randomUUID } from "crypto"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; import { sharedConfig } from "@equal-vote/star-vote-shared/config"; diff --git a/packages/backend/src/Controllers/changeElectionRollController.ts b/packages/backend/src/Controllers/Roll/changeElectionRollController.ts similarity index 92% rename from packages/backend/src/Controllers/changeElectionRollController.ts rename to packages/backend/src/Controllers/Roll/changeElectionRollController.ts index 47adbc7d..193bff93 100644 --- a/packages/backend/src/Controllers/changeElectionRollController.ts +++ b/packages/backend/src/Controllers/Roll/changeElectionRollController.ts @@ -1,13 +1,13 @@ import { ElectionRoll, ElectionRollState } from "@equal-vote/star-vote-shared/domain_model/ElectionRoll"; -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; -import { responseErr } from "../Util"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; +import { responseErr } from "../../Util"; const ElectionRollModel = ServiceLocator.electionRollDb(); import { hasPermission, permission, permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { InternalServerError, Unauthorized } from "@curveball/http-errors"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; const className = "VoterRollState.Controllers"; diff --git a/packages/backend/src/Controllers/editElectionRollController.ts b/packages/backend/src/Controllers/Roll/editElectionRollController.ts similarity index 83% rename from packages/backend/src/Controllers/editElectionRollController.ts rename to packages/backend/src/Controllers/Roll/editElectionRollController.ts index 3417bfbd..754f1214 100644 --- a/packages/backend/src/Controllers/editElectionRollController.ts +++ b/packages/backend/src/Controllers/Roll/editElectionRollController.ts @@ -1,11 +1,11 @@ import { ElectionRoll, ElectionRollState } from "@equal-vote/star-vote-shared/domain_model/ElectionRoll"; -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; -import { responseErr } from "../Util"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; +import { responseErr } from "../../Util"; import { hasPermission, permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest } from "@curveball/http-errors"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; const ElectionRollModel = ServiceLocator.electionRollDb(); diff --git a/packages/backend/src/Controllers/getElectionRollController.ts b/packages/backend/src/Controllers/Roll/getElectionRollController.ts similarity index 90% rename from packages/backend/src/Controllers/getElectionRollController.ts rename to packages/backend/src/Controllers/Roll/getElectionRollController.ts index 204638f0..fa639dfb 100644 --- a/packages/backend/src/Controllers/getElectionRollController.ts +++ b/packages/backend/src/Controllers/Roll/getElectionRollController.ts @@ -1,9 +1,9 @@ -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { BadRequest, Unauthorized } from "@curveball/http-errors"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; const ElectionRollModel = ServiceLocator.electionRollDb(); diff --git a/packages/backend/src/Controllers/Roll/index.ts b/packages/backend/src/Controllers/Roll/index.ts new file mode 100644 index 00000000..89333094 --- /dev/null +++ b/packages/backend/src/Controllers/Roll/index.ts @@ -0,0 +1,6 @@ +export * from './addElectionRollController'; +export * from './changeElectionRollController'; +export * from './editElectionRollController'; +export * from './getElectionRollController'; +export * from './registerVoterController'; +export * from './voterRollUtils'; \ No newline at end of file diff --git a/packages/backend/src/Controllers/registerVoterController.ts b/packages/backend/src/Controllers/Roll/registerVoterController.ts similarity index 93% rename from packages/backend/src/Controllers/registerVoterController.ts rename to packages/backend/src/Controllers/Roll/registerVoterController.ts index 21dbf81a..3b5259d4 100644 --- a/packages/backend/src/Controllers/registerVoterController.ts +++ b/packages/backend/src/Controllers/Roll/registerVoterController.ts @@ -1,6 +1,6 @@ import { ElectionRoll, ElectionRollState } from "@equal-vote/star-vote-shared/domain_model/ElectionRoll"; -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; const ElectionRollModel = ServiceLocator.electionRollDb(); const className = "VoterRolls.Controllers"; @@ -8,9 +8,9 @@ import { getOrCreateElectionRoll, checkForMissingAuthenticationData, getVoterAut import { BadRequest, InternalServerError, Unauthorized } from "@curveball/http-errors"; import { Election } from "@equal-vote/star-vote-shared/domain_model/Election"; import { randomUUID } from "crypto"; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; -import { hashString } from "./controllerUtils"; +import { hashString } from "../controllerUtils"; const registerVoter = async (req: IElectionRequest, res: Response, next: NextFunction) => { Logger.info(req, `${className}.registerVoter ${req.election.election_id}`); diff --git a/packages/backend/src/Controllers/voterRollUtils.ts b/packages/backend/src/Controllers/Roll/voterRollUtils.ts similarity index 96% rename from packages/backend/src/Controllers/voterRollUtils.ts rename to packages/backend/src/Controllers/Roll/voterRollUtils.ts index 17c144b9..f60d9b6b 100644 --- a/packages/backend/src/Controllers/voterRollUtils.ts +++ b/packages/backend/src/Controllers/Roll/voterRollUtils.ts @@ -1,12 +1,12 @@ import { Election } from "@equal-vote/star-vote-shared/domain_model/Election"; import { ElectionRoll, ElectionRollState } from "@equal-vote/star-vote-shared/domain_model/ElectionRoll"; -import { IRequest } from "../IRequest"; -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import { IRequest } from "../../IRequest"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { BadRequest, InternalServerError, Unauthorized } from "@curveball/http-errors"; -import { ILoggingContext } from "../Services/Logging/ILogger"; +import { ILoggingContext } from "../../Services/Logging/ILogger"; import { randomUUID } from "crypto"; -import { hashString } from "./controllerUtils"; +import { hashString } from "../controllerUtils"; const ElectionRollModel = ServiceLocator.electionRollDb(); diff --git a/packages/backend/src/Controllers/auth.controllers.ts b/packages/backend/src/Controllers/User/auth.controllers.ts similarity index 85% rename from packages/backend/src/Controllers/auth.controllers.ts rename to packages/backend/src/Controllers/User/auth.controllers.ts index 63d2e149..d9e54b02 100644 --- a/packages/backend/src/Controllers/auth.controllers.ts +++ b/packages/backend/src/Controllers/User/auth.controllers.ts @@ -1,10 +1,8 @@ -import { isConstructorDeclaration } from "typescript" -import { Election } from "@equal-vote/star-vote-shared/domain_model/Election" -import Logger from "../Services/Logging/Logger" -import { responseErr } from "../Util" +import Logger from "../../Services/Logging/Logger" +import { responseErr } from "../../Util" import { permission } from "@equal-vote/star-vote-shared/domain_model/permissions" import { roles } from "@equal-vote/star-vote-shared/domain_model/roles" -import ServiceLocator from "../ServiceLocator" +import ServiceLocator from "../../ServiceLocator" const className = 'Auth.Controllers'; const accountService = ServiceLocator.accountService(); diff --git a/packages/backend/src/Controllers/getUserTokenController.ts b/packages/backend/src/Controllers/User/getUserTokenController.ts similarity index 85% rename from packages/backend/src/Controllers/getUserTokenController.ts rename to packages/backend/src/Controllers/User/getUserTokenController.ts index 663a4b58..c49dc439 100644 --- a/packages/backend/src/Controllers/getUserTokenController.ts +++ b/packages/backend/src/Controllers/User/getUserTokenController.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from 'express'; -import ServiceLocator from "../ServiceLocator"; +import ServiceLocator from "../../ServiceLocator"; const AccountService = ServiceLocator.accountService() diff --git a/packages/backend/src/Controllers/User/index.ts b/packages/backend/src/Controllers/User/index.ts new file mode 100644 index 00000000..350d1c0b --- /dev/null +++ b/packages/backend/src/Controllers/User/index.ts @@ -0,0 +1,2 @@ +export * from './auth.controllers'; +export * from './getUserTokenController'; \ No newline at end of file diff --git a/packages/backend/src/Controllers/index.ts b/packages/backend/src/Controllers/index.ts new file mode 100644 index 00000000..f65a841d --- /dev/null +++ b/packages/backend/src/Controllers/index.ts @@ -0,0 +1,6 @@ +export * from './Ballot'; +export * from './Election'; +export * from './Roll'; +export * from './User'; +export * from './controllerUtils'; +export * from './uploadImageController'; \ No newline at end of file diff --git a/packages/backend/src/Routes/ballot.routes.ts b/packages/backend/src/Routes/ballot.routes.ts new file mode 100644 index 00000000..d5bf3f8d --- /dev/null +++ b/packages/backend/src/Routes/ballot.routes.ts @@ -0,0 +1,169 @@ +import { + getBallotsByElectionID, + deleteAllBallotsForElectionID, + getBallotByBallotID, + castVoteController, + ballotByID, + handleCastVoteEvent, + innerDeleteAllBallotsForElectionID +} from '../Controllers/Ballot'; +import { returnElection } from '../Controllers/Election'; +import { Router } from 'express'; +import asyncHandler from 'express-async-handler'; +const ballotRouter = Router(); + +/** + * @swagger + * /Election/{id}/ballot: + * post: + * summary: Return election ballot + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Election ballot returned + * content: + * application/json: + * schema: + * type: object + * properties: + * ballot: + * type: object + * $ref: '#/components/schemas/NewBallot' + * + * 404: + * description: Election not found + */ +ballotRouter.post('/Election/:id/ballot', asyncHandler(returnElection)) + +/** + * @swagger + * /Election/{id}/ballots: + * get: + * summary: Get ballots by election ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: List of ballots + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * ballots: + * type: array + * items: + * $ref: '#/components/schemas/Ballot' + * 404: + * description: Election not found +*/ +ballotRouter.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) + +/** + * @swagger + * /Election/{id}/ballots: + * delete: + * summary: Delete all ballots for an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: All ballots deleted + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: If the deletion was successful + * 404: + * description: Election not found */ +ballotRouter.delete('/Election/:id/ballots', asyncHandler(deleteAllBallotsForElectionID)) + +/** + * @swagger + * /Election/{id}/ballot/{ballot_id}: + * get: + * summary: Get ballot by ballot ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * - in: path + * name: ballot_id + * schema: + * type: string + * required: true + * description: The ballot ID + * responses: + * 200: + * description: Ballot details + * content: + * application/json: + * schema: + * type: object + * properties: + * ballot: + * type: object + * $ref: '#/components/schemas/Ballot' + * + * 404: + * description: Ballot not found +*/ +ballotRouter.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBallotID)) + +/** + * @swagger + * /Election/{id}/vote: + * post: + * summary: Cast a vote in an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Vote cast + * content: + * application/json: + * schema: + * type: object + * properties: + * ballot: + * type: object + * $ref: '#/components/schemas/Ballot' + * 404: + * description: Election not found */ +ballotRouter.post('/Election/:id/vote', asyncHandler(castVoteController)) + diff --git a/packages/backend/src/Routes/debug.routes.js b/packages/backend/src/Routes/debug.routes.js index d85bfab7..273db6cc 100644 --- a/packages/backend/src/Routes/debug.routes.js +++ b/packages/backend/src/Routes/debug.routes.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); import { tempTestSuite } from '../test/TempTestSuite'; -const voterRollController = require('../Controllers/registerVoterController') +const voterRollController = require('../Controllers/Roll/registerVoterController') // Just for debugging router.get('/', (req, res) => { diff --git a/packages/backend/src/Routes/elections.routes.ts b/packages/backend/src/Routes/elections.routes.ts index 7935c792..a0c205a1 100644 --- a/packages/backend/src/Routes/elections.routes.ts +++ b/packages/backend/src/Routes/elections.routes.ts @@ -1,48 +1,30 @@ -const router = express.Router(); +import { Router } from 'express'; +const electionsRouter = Router(); import express from 'express'; -import { returnElection, +import { + returnElection, getElectionByID, electionSpecificAuth, electionPostAuthMiddleware, - electionExistsByID} from '../Controllers/elections.controllers'; -import { registerVoter } from '../Controllers/registerVoterController'; -import { - getUser, - isLoggedIn, - assertOwnership, - hasPermission - } from '../Controllers/auth.controllers'; -import { deleteElection } from '../Controllers/deleteElectionController'; -import { - changeElectionRollState, - approveElectionRoll, - flagElectionRoll, - invalidateElectionRoll, - uninvalidateElectionRoll -} from '../Controllers/changeElectionRollController'; -import { editElectionRoll } from '../Controllers/editElectionRollController'; -import { addElectionRoll } from '../Controllers/addElectionRollController'; -import { getRollsByElectionID, getByVoterID } from '../Controllers/getElectionRollController'; -import {createElectionController} from '../Controllers/createElectionController'; -import { castVoteController } from '../Controllers/castVoteController'; -import { finalizeElection } from '../Controllers/finalizeElectionController'; -import { setPublicResults } from '../Controllers/setPublicResultsController'; -import { getElectionResults } from '../Controllers/getElectionResultsController'; -import { getBallotsByElectionID } from '../Controllers/getBallotsByElectionIDController'; -import { deleteAllBallotsForElectionID } from '../Controllers/deleteAllBallotsForElectionIDController'; -import { getBallotByBallotID } from '../Controllers/getBallotByBallotID'; -import { editElection } from '../Controllers/editElectionController'; -import { getSandboxResults } from '../Controllers/sandboxController'; -import { getElections, getGlobalElectionStats } from '../Controllers/getElectionsController'; -import { editElectionRoles } from '../Controllers/editElectionRolesController'; -import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { ElectionRollState } from '@equal-vote/star-vote-shared/domain_model/ElectionRoll'; -import { uploadImageController, upload } from '../Controllers/uploadImageController'; -import { archiveElection } from '../Controllers/archiveElectionController'; -import { sendInvitationsController, sendInvitationController } from '../Controllers/sendInvitesController'; + electionExistsByID, + archiveElection, + createElectionController, + deleteElection, + editElection, + editElectionRoles, + finalizeElection, + getElectionResults, + getElections, + getGlobalElectionStats, + getSandboxResults, + sendInvitationController, + sendInvitationsController, + setPublicResults +} from '../Controllers/Election'; +import {upload, uploadImageController} from '../Controllers/uploadImageController'; import asyncHandler from 'express-async-handler'; -import { IElectionRequest } from '../IRequest'; + @@ -71,7 +53,7 @@ import { IElectionRequest } from '../IRequest'; * description: Election not found */ -router.get('/Election/:id', asyncHandler(returnElection)); +electionsRouter.get('/Election/:id', asyncHandler(returnElection)); /** @@ -101,7 +83,7 @@ router.get('/Election/:id', asyncHandler(returnElection)); * 404: * description: Election not found */ -router.get('/Election/:_id/exists', asyncHandler(electionExistsByID)) +electionsRouter.get('/Election/:_id/exists', asyncHandler(electionExistsByID)) /** * @swagger @@ -122,379 +104,9 @@ router.get('/Election/:_id/exists', asyncHandler(electionExistsByID)) * 404: * description: Election not found */ -router.delete('/Election/:id', asyncHandler(deleteElection)) - -/** - * @swagger - * /Election/{id}/ballot: - * post: - * summary: Return election ballot - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Election ballot returned - * content: - * application/json: - * schema: - * type: object - * properties: - * ballot: - * type: object - * $ref: '#/components/schemas/NewBallot' - * - * 404: - * description: Election not found - */ -router.post('/Election/:id/ballot', asyncHandler(returnElection)) - -/** - * @swagger - * /Election/{id}/register: - * post: - * summary: Register a voter for an election - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Voter registered - * content: - * application/json: - * schema: - * type: object - * properties: - * election: - * type: object - * $ref: '#/components/schemas/Election' - * NewElectionRoll: - * type: object - * $ref: '#/components/schemas/NewElectionRoll' - * 404: - * description: Election not found - */ -router.post('/Election/:id/register',asyncHandler(registerVoter)) - - -/** - * @swagger - * /Election/{id}/ballots: - * get: - * summary: Get ballots by election ID - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: List of ballots - * content: - * application/json: - * schema: - * type: object - * properties: - * election: - * type: object - * $ref: '#/components/schemas/Election' - * ballots: - * type: array - * items: - * $ref: '#/components/schemas/Ballot' - * 404: - * description: Election not found -*/ -router.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) - - -/** - * @swagger - * /Election/{id}/ballots: - * delete: - * summary: Delete all ballots for an election - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: All ballots deleted - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: If the deletion was successful - * 404: - * description: Election not found */ -router.delete('/Election/:id/ballots', asyncHandler(deleteAllBallotsForElectionID)) - - -/** - * @swagger - * /Election/{id}/ballot/{ballot_id}: - * get: - * summary: Get ballot by ballot ID - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * - in: path - * name: ballot_id - * schema: - * type: string - * required: true - * description: The ballot ID - * responses: - * 200: - * description: Ballot details - * content: - * application/json: - * schema: - * type: object - * properties: - * ballot: - * type: object - * $ref: '#/components/schemas/Ballot' - * - * 404: - * description: Ballot not found -*/ -router.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBallotID)) - - - -/** - * @swagger - * /Election/{id}/rolls: - * get: - * summary: Get rolls by election ID - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: List of rolls - * content: - * application/json: - * schema: - * type: object - * properties: - * electionRollEntry: - * type: object - * $ref: '#/components/schemas/ElectionRoll' - * 404: - * description: Election not found -*/ -router.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) - - - -/** - * @swagger - * /Election/{id}/rolls/{voter_id}: - * get: - * summary: Get roll by voter ID - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * - in: path - * name: voter_id - * schema: - * type: string - * required: true - * description: The voter ID - * responses: - * 200: - * description: Roll details - * content: - * application/json: - * schema: - * type: object - * properties: - * electionRollEntry: - * type: object - * $ref: '#/components/schemas/ElectionRoll' - * 404: - * description: Roll not found -*/ -router.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) - - -/** - * @swagger - * /Election/{id}/rolls: - * post: - * summary: Add a new roll to an election - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Roll added - * content: - * application/json: - * schema: - * type: object - * properties: - * election: - * type: object - * $ref: '#/components/schemas/Election' - * NewElectionRoll: - * type: object - * $ref: '#/components/schemas/NewElectionRoll' - * 404: - * description: Election not found - */ -router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) - -/** - * @swagger - * /Election/{id}/rolls: - * put: - * summary: Edit an election roll - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Roll edited - * content: - * application/json: - * schema: - * type: object - * properties: - * electionRollEntry: - * type: object - * $ref: '#/components/schemas/ElectionRoll' - * 404: - * description: Election not found */ - router.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) +electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) - /** - * @swagger - * /Election/{id}/rolls/approve: - * post: - * summary: Approve an election roll - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Roll approved - * 404: - * description: Election not found */ - router.post('/Election/:id/rolls/approve', asyncHandler(approveElectionRoll)) - - /** - * @swagger - * /Election/{id}/rolls/flag: - * post: - * summary: Flag an election roll - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Roll flagged - * 404: - * description: Election not found */ - router.post('/Election/:id/rolls/flag', asyncHandler(flagElectionRoll)) - - /** @swagger - * /Election/{id}/rolls/invalidate: - * post: - * summary: Invalidate an election roll - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Roll invalidated - * 404: - * description: Election not found */ - router.post('/Election/:id/rolls/invalidate', asyncHandler(invalidateElectionRoll)) -/** - * - * @swagger - * /Election/{id}/rolls/unflag: - * post: - * summary: Unflag an election roll - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Roll unflagged - * 404: - * description: Election not found */ - router.post('/Election/:id/rolls/unflag', asyncHandler(uninvalidateElectionRoll)) - /** * @swagger * /Elections: @@ -535,7 +147,7 @@ router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * $ref: '#/components/schemas/Election' * - type: null */ - router.get('/Elections', asyncHandler(getElections)) + electionsRouter.get('/Elections', asyncHandler(getElections)) /** * @swagger @@ -555,7 +167,8 @@ router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * type: object * $ref: '#/components/schemas/Election' * */ - router.post('/Elections/', asyncHandler(createElectionController)) + electionsRouter.post('/Elections/', asyncHandler(createElectionController)) + /** * * @swagger @@ -578,7 +191,7 @@ router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * type: number * description: Number of votes * */ - router.get('/GlobalElectionStats', asyncHandler(getGlobalElectionStats)) + electionsRouter.get('/GlobalElectionStats', asyncHandler(getGlobalElectionStats)) /** * @swagger * /Election/{id}/edit: @@ -620,7 +233,7 @@ router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * $ref: '#/components/schemas/permissions' * 404: * description: Election not found */ - router.post('/Election/:id/edit', asyncHandler(editElection)) + electionsRouter.post('/Election/:id/edit', asyncHandler(editElection)) /** * @swagger @@ -648,7 +261,7 @@ router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * $ref: '#/components/schemas/Election' * 404: * description: Election not found */ - router.put('/Election/:id/roles', asyncHandler(editElectionRoles)) + electionsRouter.put('/Election/:id/roles', asyncHandler(editElectionRoles)) /** * @swagger @@ -679,36 +292,8 @@ router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * $ref: '#/components/schemas/ElectionResults' * 404: * description: Election not found */ - router.get('/ElectionResult/:id', asyncHandler(getElectionResults)) + electionsRouter.get('/ElectionResult/:id', asyncHandler(getElectionResults)) -/** - * @swagger - * /Election/{id}/vote: - * post: - * summary: Cast a vote in an election - * tags: [Elections] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Vote cast - * content: - * application/json: - * schema: - * type: object - * properties: - * ballot: - * type: object - * $ref: '#/components/schemas/Ballot' - * 404: - * description: Election not found */ - router.post('/Election/:id/vote', asyncHandler(castVoteController)) - /** * @swagger @@ -736,7 +321,7 @@ router.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * $ref: '#/components/schemas/Election' * 404: * description: Election not found */ -router.post('/Election/:id/finalize',asyncHandler(finalizeElection)) +electionsRouter.post('/Election/:id/finalize',asyncHandler(finalizeElection)) /** * @swagger @@ -764,7 +349,7 @@ router.post('/Election/:id/finalize',asyncHandler(finalizeElection)) * $ref: '#/components/schemas/Election' * 404: * description: Election not found */ -router.post('/Election/:id/setPublicResults',asyncHandler(setPublicResults)) +electionsRouter.post('/Election/:id/setPublicResults',asyncHandler(setPublicResults)) /** * @swagger @@ -793,7 +378,7 @@ router.post('/Election/:id/setPublicResults',asyncHandler(setPublicResults)) * 404: * description: Election not found */ -router.post('/Election/:id/archive', asyncHandler(archiveElection)) +electionsRouter.post('/Election/:id/archive', asyncHandler(archiveElection)) /** * @swagger @@ -813,7 +398,8 @@ router.post('/Election/:id/archive', asyncHandler(archiveElection)) * description: Invitations sent * 404: * description: Election not found */ -router.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsController)) +electionsRouter.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsController)) + /** * @swagger * /Election/{id}/sendInvite/{voter_id}: @@ -846,7 +432,7 @@ router.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsController) * $ref: '#/components/schemas/ElectionRoll' * 404: * description: Election or voter not found */ -router.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationController)) +electionsRouter.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationController)) /** * @swagger @@ -871,7 +457,7 @@ router.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationCon * candidates: * type: array * items: string */ - router.post('/Sandbox',asyncHandler(getSandboxResults)) + electionsRouter.post('/Sandbox',asyncHandler(getSandboxResults)) /** * @swagger @@ -901,7 +487,7 @@ router.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvitationCon * - string * - null */ -router.post('/images',upload.single("file"), asyncHandler(uploadImageController)) +electionsRouter.post('/images',upload.single("file"), asyncHandler(uploadImageController)) @@ -909,8 +495,8 @@ router.post('/images',upload.single("file"), asyncHandler(uploadImageController) //router.param('id', asyncHandler(electionController.getElectionByID)) -router.param('id', asyncHandler(getElectionByID)) -router.param('id', asyncHandler(electionSpecificAuth)) -router.param('id', asyncHandler(electionPostAuthMiddleware)) +electionsRouter.param('id', asyncHandler(getElectionByID)) +electionsRouter.param('id', asyncHandler(electionSpecificAuth)) +electionsRouter.param('id', asyncHandler(electionPostAuthMiddleware)) -export default router; +export default electionsRouter; diff --git a/packages/backend/src/Routes/registerEvents.ts b/packages/backend/src/Routes/registerEvents.ts index 2c3dc98e..9e076090 100644 --- a/packages/backend/src/Routes/registerEvents.ts +++ b/packages/backend/src/Routes/registerEvents.ts @@ -1,8 +1,8 @@ import ServiceLocator from "../ServiceLocator"; import Logger from "../Services/Logging/Logger"; -const { handleCastVoteEvent } = require('../Controllers/castVoteController'); -const { handleSendInviteEvent } = require('../Controllers/sendInvitesController'); +const { handleCastVoteEvent } = require('../Controllers/Ballot/castVoteController'); +const { handleSendInviteEvent } = require('../Controllers/Election/sendInvitesController'); export default async function registerEvents() { const ctx = Logger.createContext("app init"); diff --git a/packages/backend/src/Routes/roll.routes.ts b/packages/backend/src/Routes/roll.routes.ts new file mode 100644 index 00000000..f8f9945e --- /dev/null +++ b/packages/backend/src/Routes/roll.routes.ts @@ -0,0 +1,251 @@ +import { Router } from 'express'; +import { + addElectionRoll, + registerVoter, + getRollsByElectionID, + getByVoterID, + editElectionRoll, + approveElectionRoll, + flagElectionRoll, + invalidateElectionRoll, + uninvalidateElectionRoll +} from '../Controllers/Roll'; +import asyncHandler from 'express-async-handler'; +const rollRouter = Router(); + + +/** + * @swagger + * /Election/{id}/register: + * post: + * summary: Register a voter for an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Voter registered + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * NewElectionRoll: + * type: object + * $ref: '#/components/schemas/NewElectionRoll' + * 404: + * description: Election not found + */ +rollRouter.post('/Election/:id/register',asyncHandler(registerVoter)) + +/** + * @swagger + * /Election/{id}/rolls: + * get: + * summary: Get rolls by election ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: List of rolls + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' + * 404: + * description: Election not found +*/ +rollRouter.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) + +/** + * @swagger + * /Election/{id}/rolls/{voter_id}: + * get: + * summary: Get roll by voter ID + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * - in: path + * name: voter_id + * schema: + * type: string + * required: true + * description: The voter ID + * responses: + * 200: + * description: Roll details + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' + * 404: + * description: Roll not found +*/ +rollRouter.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) + +/** + * @swagger + * /Election/{id}/rolls: + * post: + * summary: Add a new roll to an election + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll added + * content: + * application/json: + * schema: + * type: object + * properties: + * election: + * type: object + * $ref: '#/components/schemas/Election' + * NewElectionRoll: + * type: object + * $ref: '#/components/schemas/NewElectionRoll' + * 404: + * description: Election not found + */ +rollRouter.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) + +/** + * @swagger + * /Election/{id}/rolls: + * put: + * summary: Edit an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll edited + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' + * 404: + * description: Election not found */ +rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) + + /** + * @swagger + * /Election/{id}/rolls/approve: + * post: + * summary: Approve an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll approved + * 404: + * description: Election not found */ + rollRouter.post('/Election/:id/rolls/approve', asyncHandler(approveElectionRoll)) + + /** + * @swagger + * /Election/{id}/rolls/flag: + * post: + * summary: Flag an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll flagged + * 404: + * description: Election not found */ + rollRouter.post('/Election/:id/rolls/flag', asyncHandler(flagElectionRoll)) + + /** @swagger + * /Election/{id}/rolls/invalidate: + * post: + * summary: Invalidate an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll invalidated + * 404: + * description: Election not found */ + rollRouter.post('/Election/:id/rolls/invalidate', asyncHandler(invalidateElectionRoll)) +/** + * + * @swagger + * /Election/{id}/rolls/unflag: + * post: + * summary: Unflag an election roll + * tags: [Elections] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: Roll unflagged + * 404: + * description: Election not found */ +rollRouter.post('/Election/:id/rolls/unflag', asyncHandler(uninvalidateElectionRoll)) + diff --git a/packages/backend/src/Routes/user.routes.ts b/packages/backend/src/Routes/user.routes.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 7ce09888..208100af 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -15,8 +15,7 @@ import { getMetaTags } from './Util'; import swaggerUi from 'swagger-ui-express'; import openapi from './OpenApi/openapi.json' -const { getUserToken } = require('./Controllers/getUserTokenController') -const authController = require('./Controllers/auth.controllers') +import { getUserToken, getUser } from './Controllers/User'; const asyncHandler = require('express-async-handler') require('./socketHandler') @@ -45,7 +44,7 @@ export default function makeApp() { const path = require('path'); app.use(express.json()); //Routes - app.use('/API',authController.getUser, electionRouter) + app.use('/API', getUser, electionRouter) // app.use('/debug',debugRouter) app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapi)); app.post('/API/Token', asyncHandler(getUserToken)); diff --git a/packages/backend/src/socketHandler.ts b/packages/backend/src/socketHandler.ts index 1b35259a..c70cd356 100644 --- a/packages/backend/src/socketHandler.ts +++ b/packages/backend/src/socketHandler.ts @@ -1,13 +1,14 @@ import express from 'express'; import { Server } from 'socket.io'; -const { innerGetGlobalElectionStats} = require('./Controllers/getElectionsController') +import { innerGetGlobalElectionStats } from './Controllers/Election'; export let io: Server|null = null; export const setupSockets = (app: express.Application) => { const server = require('http').createServer(app) + io = new Server(server, { cors: { @@ -18,7 +19,7 @@ export const setupSockets = (app: express.Application) => { io.on('connection', (socket: any) => { socket.on('join_landing_page', async () => { socket.join('landing_page'); - socket.emit('updated_stats', await innerGetGlobalElectionStats()); + socket.emit('updated_stats', await innerGetGlobalElectionStats(app.locals.req)); }) }) From 871328f79a956a479fcf7b1a56e2a2c8e075483a Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Sun, 22 Sep 2024 23:22:25 -0600 Subject: [PATCH 04/11] fixed bugs --- packages/backend/jest.config.js | 2 +- packages/backend/src/OpenApi/openapi.json | 702 +++++++++--------- packages/backend/src/Routes/ballot.routes.ts | 12 +- .../backend/src/Routes/elections.routes.ts | 2 +- packages/backend/src/Routes/index.ts | 4 + packages/backend/src/Routes/roll.routes.ts | 24 +- packages/backend/src/Routes/user.routes.ts | 0 packages/backend/src/Tabulators/ParseData.ts | 2 +- packages/backend/src/app.ts | 7 +- 9 files changed, 381 insertions(+), 374 deletions(-) create mode 100644 packages/backend/src/Routes/index.ts delete mode 100644 packages/backend/src/Routes/user.routes.ts diff --git a/packages/backend/jest.config.js b/packages/backend/jest.config.js index dc348eea..2ff03bc4 100644 --- a/packages/backend/jest.config.js +++ b/packages/backend/jest.config.js @@ -1,4 +1,4 @@ -export { +module.exports = { transform: { '^.+\\.ts?$': [ 'ts-jest', diff --git a/packages/backend/src/OpenApi/openapi.json b/packages/backend/src/OpenApi/openapi.json index d6793859..4b1056b4 100644 --- a/packages/backend/src/OpenApi/openapi.json +++ b/packages/backend/src/OpenApi/openapi.json @@ -1717,110 +1717,11 @@ } }, "paths": { - "/Election/{id}": { - "get": { - "summary": "Get election by ID", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "The election description by ID", - "content": { - "application/json": { - "schema": { - "type": "object", - "$ref": "#/components/schemas/Election" - } - } - } - }, - "404": { - "description": "Election not found" - } - } - }, - "delete": { - "summary": "Delete election by ID", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election deleted" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{_id}/exists": { - "get": { - "summary": "Check if election exists by ID", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election exists", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "exists": { - "type": "boolean" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, "/Election/{id}/ballot": { "post": { "summary": "Return election ballot", "tags": [ - "Elections" + "Ballots" ], "parameters": [ { @@ -1856,55 +1757,11 @@ } } }, - "/Election/{id}/register": { - "post": { - "summary": "Register a voter for an election", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Voter registered", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - }, - "NewElectionRoll": { - "type": "object", - "$ref": "#/components/schemas/NewElectionRoll" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, "/Election/{id}/ballots": { "get": { "summary": "Get ballots by election ID", "tags": [ - "Elections" + "Ballots" ], "parameters": [ { @@ -1948,7 +1805,7 @@ "delete": { "summary": "Delete all ballots for an election", "tags": [ - "Elections" + "Ballots" ], "parameters": [ { @@ -1988,7 +1845,7 @@ "get": { "summary": "Get ballot by ballot ID", "tags": [ - "Elections" + "Ballots" ], "parameters": [ { @@ -2033,11 +1890,11 @@ } } }, - "/Election/{id}/rolls": { - "get": { - "summary": "Get rolls by election ID", + "/Election/{id}/vote": { + "post": { + "summary": "Cast a vote in an election", "tags": [ - "Elections" + "Ballots" ], "parameters": [ { @@ -2052,15 +1909,15 @@ ], "responses": { "200": { - "description": "List of rolls", + "description": "Vote cast", "content": { "application/json": { "schema": { "type": "object", "properties": { - "electionRollEntry": { + "ballot": { "type": "object", - "$ref": "#/components/schemas/ElectionRoll" + "$ref": "#/components/schemas/Ballot" } } } @@ -2071,9 +1928,11 @@ "description": "Election not found" } } - }, - "post": { - "summary": "Add a new roll to an election", + } + }, + "/Election/{id}": { + "get": { + "summary": "Get election by ID", "tags": [ "Elections" ], @@ -2090,21 +1949,12 @@ ], "responses": { "200": { - "description": "Roll added", + "description": "The election description by ID", "content": { "application/json": { "schema": { "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - }, - "NewElectionRoll": { - "type": "object", - "$ref": "#/components/schemas/NewElectionRoll" - } - } + "$ref": "#/components/schemas/Election" } } } @@ -2114,8 +1964,8 @@ } } }, - "put": { - "summary": "Edit an election roll", + "delete": { + "summary": "Delete election by ID", "tags": [ "Elections" ], @@ -2132,20 +1982,7 @@ ], "responses": { "200": { - "description": "Roll edited", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } + "description": "Election deleted" }, "404": { "description": "Election not found" @@ -2153,9 +1990,9 @@ } } }, - "/Election/{id}/rolls/{voter_id}": { + "/Election/{_id}/exists": { "get": { - "summary": "Get roll by voter ID", + "summary": "Check if election exists by ID", "tags": [ "Elections" ], @@ -2168,28 +2005,18 @@ }, "required": true, "description": "The election ID" - }, - { - "in": "path", - "name": "voter_id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The voter ID" } ], "responses": { "200": { - "description": "Roll details", + "description": "Election exists", "content": { "application/json": { "schema": { "type": "object", "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" + "exists": { + "type": "boolean" } } } @@ -2197,122 +2024,14 @@ } }, "404": { - "description": "Roll not found" + "description": "Election not found" } } } }, - "/Election/{id}/rolls/approve": { - "post": { - "summary": "Approve an election roll", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Roll approved" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls/flag": { - "post": { - "summary": "Flag an election roll", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Roll flagged" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls/invalidate": { - "post": { - "summary": "Invalidate an election roll", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Roll invalidated" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls/unflag": { - "post": { - "summary": "Unflag an election roll", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Roll unflagged" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Elections": { - "get": { - "summary": "Get all elections", + "/Elections": { + "get": { + "summary": "Get all elections", "tags": [ "Elections" ], @@ -2588,46 +2307,6 @@ } } }, - "/Election/{id}/vote": { - "post": { - "summary": "Cast a vote in an election", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Vote cast", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "ballot": { - "type": "object", - "$ref": "#/components/schemas/Ballot" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, "/Election/{id}/finalize": { "post": { "summary": "Finalize an election", @@ -2900,6 +2579,327 @@ } } } + }, + "/Election/{id}/register": { + "post": { + "summary": "Register a voter for an election", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Voter registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "NewElectionRoll": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls": { + "get": { + "summary": "Get rolls by election ID", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "List of rolls", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + }, + "post": { + "summary": "Add a new roll to an election", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll added", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "NewElectionRoll": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + }, + "put": { + "summary": "Edit an election roll", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll edited", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/{voter_id}": { + "get": { + "summary": "Get roll by voter ID", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + }, + { + "in": "path", + "name": "voter_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The voter ID" + } + ], + "responses": { + "200": { + "description": "Roll details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Roll not found" + } + } + } + }, + "/Election/{id}/rolls/approve": { + "post": { + "summary": "Approve an election roll", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll approved" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/flag": { + "post": { + "summary": "Flag an election roll", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll flagged" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/invalidate": { + "post": { + "summary": "Invalidate an election roll", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll invalidated" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/unflag": { + "post": { + "summary": "Unflag an election roll", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll unflagged" + }, + "404": { + "description": "Election not found" + } + } + } } }, "tags": [] diff --git a/packages/backend/src/Routes/ballot.routes.ts b/packages/backend/src/Routes/ballot.routes.ts index d5bf3f8d..d1b2b659 100644 --- a/packages/backend/src/Routes/ballot.routes.ts +++ b/packages/backend/src/Routes/ballot.routes.ts @@ -10,14 +10,14 @@ import { import { returnElection } from '../Controllers/Election'; import { Router } from 'express'; import asyncHandler from 'express-async-handler'; -const ballotRouter = Router(); +export const ballotRouter = Router(); /** * @swagger * /Election/{id}/ballot: * post: * summary: Return election ballot - * tags: [Elections] + * tags: [Ballots] * parameters: * - in: path * name: id @@ -47,7 +47,7 @@ ballotRouter.post('/Election/:id/ballot', asyncHandler(returnElection)) * /Election/{id}/ballots: * get: * summary: Get ballots by election ID - * tags: [Elections] + * tags: [Ballots] * parameters: * - in: path * name: id @@ -80,7 +80,7 @@ ballotRouter.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) * /Election/{id}/ballots: * delete: * summary: Delete all ballots for an election - * tags: [Elections] + * tags: [Ballots] * parameters: * - in: path * name: id @@ -108,7 +108,7 @@ ballotRouter.delete('/Election/:id/ballots', asyncHandler(deleteAllBallotsForEle * /Election/{id}/ballot/{ballot_id}: * get: * summary: Get ballot by ballot ID - * tags: [Elections] + * tags: [Ballots] * parameters: * - in: path * name: id @@ -144,7 +144,7 @@ ballotRouter.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBall * /Election/{id}/vote: * post: * summary: Cast a vote in an election - * tags: [Elections] + * tags: [Ballots] * parameters: * - in: path * name: id diff --git a/packages/backend/src/Routes/elections.routes.ts b/packages/backend/src/Routes/elections.routes.ts index a0c205a1..f5a69b24 100644 --- a/packages/backend/src/Routes/elections.routes.ts +++ b/packages/backend/src/Routes/elections.routes.ts @@ -499,4 +499,4 @@ electionsRouter.param('id', asyncHandler(getElectionByID)) electionsRouter.param('id', asyncHandler(electionSpecificAuth)) electionsRouter.param('id', asyncHandler(electionPostAuthMiddleware)) -export default electionsRouter; +export {electionsRouter}; diff --git a/packages/backend/src/Routes/index.ts b/packages/backend/src/Routes/index.ts new file mode 100644 index 00000000..ec85a554 --- /dev/null +++ b/packages/backend/src/Routes/index.ts @@ -0,0 +1,4 @@ +export * from './elections.routes'; +export * from './debug.routes'; +export * from './ballot.routes'; +export * from './roll.routes'; diff --git a/packages/backend/src/Routes/roll.routes.ts b/packages/backend/src/Routes/roll.routes.ts index f8f9945e..7df07dfe 100644 --- a/packages/backend/src/Routes/roll.routes.ts +++ b/packages/backend/src/Routes/roll.routes.ts @@ -11,7 +11,7 @@ import { uninvalidateElectionRoll } from '../Controllers/Roll'; import asyncHandler from 'express-async-handler'; -const rollRouter = Router(); +export const rollRouter = Router(); /** @@ -19,7 +19,7 @@ const rollRouter = Router(); * /Election/{id}/register: * post: * summary: Register a voter for an election - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -40,7 +40,7 @@ const rollRouter = Router(); * $ref: '#/components/schemas/Election' * NewElectionRoll: * type: object - * $ref: '#/components/schemas/NewElectionRoll' + * $ref: '#/components/schemas/ElectionRoll' * 404: * description: Election not found */ @@ -51,7 +51,7 @@ rollRouter.post('/Election/:id/register',asyncHandler(registerVoter)) * /Election/{id}/rolls: * get: * summary: Get rolls by election ID - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -80,7 +80,7 @@ rollRouter.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) * /Election/{id}/rolls/{voter_id}: * get: * summary: Get roll by voter ID - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -115,7 +115,7 @@ rollRouter.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) * /Election/{id}/rolls: * post: * summary: Add a new roll to an election - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -136,7 +136,7 @@ rollRouter.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) * $ref: '#/components/schemas/Election' * NewElectionRoll: * type: object - * $ref: '#/components/schemas/NewElectionRoll' + * $ref: '#/components/schemas/ElectionRoll' * 404: * description: Election not found */ @@ -147,7 +147,7 @@ rollRouter.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * /Election/{id}/rolls: * put: * summary: Edit an election roll - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -175,7 +175,7 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * /Election/{id}/rolls/approve: * post: * summary: Approve an election roll - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -195,7 +195,7 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * /Election/{id}/rolls/flag: * post: * summary: Flag an election roll - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -214,7 +214,7 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * /Election/{id}/rolls/invalidate: * post: * summary: Invalidate an election roll - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id @@ -234,7 +234,7 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * /Election/{id}/rolls/unflag: * post: * summary: Unflag an election roll - * tags: [Elections] + * tags: [Rolls] * parameters: * - in: path * name: id diff --git a/packages/backend/src/Routes/user.routes.ts b/packages/backend/src/Routes/user.routes.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/backend/src/Tabulators/ParseData.ts b/packages/backend/src/Tabulators/ParseData.ts index 295c5ebe..a4c829d0 100644 --- a/packages/backend/src/Tabulators/ParseData.ts +++ b/packages/backend/src/Tabulators/ParseData.ts @@ -22,7 +22,7 @@ function getStarBallotValidity(ballot: ballot) { return { isValid: true, isUnderVote: isUnderVote } } -export function ParseData(data: ballot[], validityCheck = getStarBallotValidity): IparsedData { +module.exports = function ParseData(data: ballot[], validityCheck = getStarBallotValidity): IparsedData { // Initialize arrays const scores: ballot[] = []; const validVotes: voter[] = []; diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 208100af..82bcde08 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -1,6 +1,7 @@ require('dotenv').config(); import express from 'express'; -import electionRouter from './Routes/elections.routes' +import {electionsRouter, ballotRouter, rollRouter} from './Routes'; + // var debugRouter = require('./Routes/debug.routes') import cors from 'cors'; @@ -44,7 +45,9 @@ export default function makeApp() { const path = require('path'); app.use(express.json()); //Routes - app.use('/API', getUser, electionRouter) + app.use('/API', getUser, electionsRouter) + app.use('/API', getUser, ballotRouter) + app.use('/API', getUser, rollRouter) // app.use('/debug',debugRouter) app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapi)); app.post('/API/Token', asyncHandler(getUserToken)); From 2c830c41552cbff9f9f7a997fa8fc542ec7328b2 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Mon, 23 Sep 2024 00:48:36 -0600 Subject: [PATCH 05/11] added response body to documentation --- packages/backend/src/OpenApi/openapi.json | 237 ++++++++++++++---- packages/backend/src/Routes/ballot.routes.ts | 55 ++-- .../backend/src/Routes/elections.routes.ts | 42 +++- packages/backend/src/Routes/roll.routes.ts | 57 ++++- 4 files changed, 316 insertions(+), 75 deletions(-) diff --git a/packages/backend/src/OpenApi/openapi.json b/packages/backend/src/OpenApi/openapi.json index 4b1056b4..a9c21a2e 100644 --- a/packages/backend/src/OpenApi/openapi.json +++ b/packages/backend/src/OpenApi/openapi.json @@ -1717,46 +1717,6 @@ } }, "paths": { - "/Election/{id}/ballot": { - "post": { - "summary": "Return election ballot", - "tags": [ - "Ballots" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election ballot returned", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "ballot": { - "type": "object", - "$ref": "#/components/schemas/NewBallot" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, "/Election/{id}/ballots": { "get": { "summary": "Get ballots by election ID", @@ -1818,6 +1778,22 @@ "description": "The election ID" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "public_results": { + "type": "boolean", + "description": "If the election results are public" + } + } + } + } + } + }, "responses": { "200": { "description": "All ballots deleted", @@ -1907,6 +1883,32 @@ "description": "The election ID" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballot": { + "type": "object", + "$ref": "#/components/schemas/Ballot" + }, + "recieptEmail": { + "oneOf": [ + { + "type": "string" + }, + { + "type": null + } + ] + } + } + } + } + } + }, "responses": { "200": { "description": "Vote cast", @@ -2110,6 +2112,21 @@ "tags": [ "Elections" ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Election": { + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, "responses": { "200": { "description": "Election created", @@ -2177,6 +2194,20 @@ "description": "The election ID" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Election": { + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, "responses": { "200": { "description": "Election edited", @@ -2240,6 +2271,29 @@ "description": "The election ID" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "admin_ids": { + "type": "array", + "items": "string" + }, + "audit_ids": { + "type": "array", + "items": "string" + }, + "credential_ids": { + "type": "array", + "items": "string" + } + } + } + } + } + }, "responses": { "200": { "description": "Roles edited", @@ -2364,6 +2418,20 @@ "description": "The election ID" } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "public_results": { + "type": "boolean" + } + } + } + } + } + }, "responses": { "200": { "description": "Public results set", @@ -2567,7 +2635,7 @@ "type": "object", "properties": { "photo_filename": { - "typeOf": [ + "oneOf": [ "string", null ] @@ -2664,7 +2732,7 @@ } }, "post": { - "summary": "Add a new roll to an election", + "summary": "Add a list of rolls to an election", "tags": [ "Rolls" ], @@ -2679,9 +2747,28 @@ "description": "The election ID" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRoll": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + } + }, "responses": { "200": { - "description": "Roll added", + "description": "Rolls added", "content": { "application/json": { "schema": { @@ -2810,6 +2897,22 @@ "description": "The election ID" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, "responses": { "200": { "description": "Roll approved" @@ -2837,6 +2940,22 @@ "description": "The election ID" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, "responses": { "200": { "description": "Roll flagged" @@ -2864,6 +2983,22 @@ "description": "The election ID" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, "responses": { "200": { "description": "Roll invalidated" @@ -2891,6 +3026,22 @@ "description": "The election ID" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, "responses": { "200": { "description": "Roll unflagged" diff --git a/packages/backend/src/Routes/ballot.routes.ts b/packages/backend/src/Routes/ballot.routes.ts index d1b2b659..bed50ec1 100644 --- a/packages/backend/src/Routes/ballot.routes.ts +++ b/packages/backend/src/Routes/ballot.routes.ts @@ -12,35 +12,7 @@ import { Router } from 'express'; import asyncHandler from 'express-async-handler'; export const ballotRouter = Router(); -/** - * @swagger - * /Election/{id}/ballot: - * post: - * summary: Return election ballot - * tags: [Ballots] - * parameters: - * - in: path - * name: id - * schema: - * type: string - * required: true - * description: The election ID - * responses: - * 200: - * description: Election ballot returned - * content: - * application/json: - * schema: - * type: object - * properties: - * ballot: - * type: object - * $ref: '#/components/schemas/NewBallot' - * - * 404: - * description: Election not found - */ -ballotRouter.post('/Election/:id/ballot', asyncHandler(returnElection)) + /** * @swagger @@ -88,6 +60,17 @@ ballotRouter.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) * type: string * required: true * description: The election ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * public_results: + * type: boolean + * description: If the election results are public + * * responses: * 200: * description: All ballots deleted @@ -152,6 +135,20 @@ ballotRouter.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBall * type: string * required: true * description: The election ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * ballot: + * type: object + * $ref: '#/components/schemas/Ballot' + * recieptEmail: + * oneOf: + * - type: string + * - type: null * responses: * 200: * description: Vote cast diff --git a/packages/backend/src/Routes/elections.routes.ts b/packages/backend/src/Routes/elections.routes.ts index f5a69b24..5cc8968d 100644 --- a/packages/backend/src/Routes/elections.routes.ts +++ b/packages/backend/src/Routes/elections.routes.ts @@ -155,6 +155,15 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * post: * summary: Create a new election * tags: [Elections] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * Election: + * $ref: '#/components/schemas/Election' * responses: * 200: * description: Election created @@ -205,6 +214,14 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * type: string * required: true * description: The election ID + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * Election: + * $ref: '#/components/schemas/Election' * responses: * 200: * description: Election edited @@ -248,6 +265,21 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * type: string * required: true * description: The election ID + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * admin_ids: + * type: array + * items: string + * audit_ids: + * type: array + * items: string + * credential_ids: + * type: array + * items: string * responses: * 200: * description: Roles edited @@ -336,6 +368,14 @@ electionsRouter.post('/Election/:id/finalize',asyncHandler(finalizeElection)) * type: string * required: true * description: The election ID + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * public_results: + * type: boolean * responses: * 200: * description: Public results set @@ -483,7 +523,7 @@ electionsRouter.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvi * type: object * properties: * photo_filename: - * typeOf: + * oneOf: * - string * - null */ diff --git a/packages/backend/src/Routes/roll.routes.ts b/packages/backend/src/Routes/roll.routes.ts index 7df07dfe..963d285f 100644 --- a/packages/backend/src/Routes/roll.routes.ts +++ b/packages/backend/src/Routes/roll.routes.ts @@ -114,7 +114,7 @@ rollRouter.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) * @swagger * /Election/{id}/rolls: * post: - * summary: Add a new roll to an election + * summary: Add a list of rolls to an election * tags: [Rolls] * parameters: * - in: path @@ -123,9 +123,21 @@ rollRouter.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) * type: string * required: true * description: The election ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRoll: + * type: array + * items: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * responses: * 200: - * description: Roll added + * description: Rolls added * content: * application/json: * schema: @@ -183,6 +195,16 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * type: string * required: true * description: The election ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * responses: * 200: * description: Roll approved @@ -203,6 +225,16 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * type: string * required: true * description: The election ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * responses: * 200: * description: Roll flagged @@ -222,6 +254,16 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * type: string * required: true * description: The election ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * responses: * 200: * description: Roll invalidated @@ -242,6 +284,17 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * type: string * required: true * description: The election ID + * + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * electionRollEntry: + * type: object + * $ref: '#/components/schemas/ElectionRoll' * responses: * 200: * description: Roll unflagged From b67d76e5c4f9418a9ea5c062e185fbad77b32237 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Mon, 23 Sep 2024 12:46:24 -0600 Subject: [PATCH 06/11] add authentication info to swagger doc --- packages/backend/src/OpenApi/openapi.json | 29 +++++++++++++++++++ packages/backend/src/OpenApi/swagger.ts | 18 ++++++++++++ packages/backend/src/Routes/ballot.routes.ts | 20 +++++++++++-- packages/backend/src/Routes/roll.routes.ts | 9 ++++++ .../src/Services/Account/AccountService.ts | 1 + 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/OpenApi/openapi.json b/packages/backend/src/OpenApi/openapi.json index a9c21a2e..de6d6716 100644 --- a/packages/backend/src/OpenApi/openapi.json +++ b/packages/backend/src/OpenApi/openapi.json @@ -4,7 +4,21 @@ "title": "API Documentation", "version": "1.0.0" }, + "servers": [ + { + "url": "/API", + "description": "Base URL for the API" + } + ], "components": { + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "cookie", + "name": "id_token", + "description": "This uses authenticaion token generated with keycloak, the token can be sent both as a cookie or in the auth_key property of the election object. Unfortunately, the swagger-ui does not support setting cookies, so you will need to manually set the cookie in your browser to test the API" + } + }, "schemas": { "Ballot": { "properties": { @@ -1723,6 +1737,11 @@ "tags": [ "Ballots" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -1767,6 +1786,11 @@ "tags": [ "Ballots" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -1872,6 +1896,11 @@ "tags": [ "Ballots" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", diff --git a/packages/backend/src/OpenApi/swagger.ts b/packages/backend/src/OpenApi/swagger.ts index 6a51c347..7b6c9e1e 100644 --- a/packages/backend/src/OpenApi/swagger.ts +++ b/packages/backend/src/OpenApi/swagger.ts @@ -12,7 +12,25 @@ const options = { title: 'API Documentation', version: '1.0.0', }, + servers: [ + { + url: '/API', + description: 'Base URL for the API', + }, + ], components: { + securitySchemes: { + ApiKeyAuth: { + type: 'apiKey', + in: 'cookie', + name: 'id_token', + description: 'This uses authenticaion token generated with keycloak, the token can be sent both as a cookie ' + + 'or in the auth_key property of the election object. [More information about token generation can ' + + 'be found here](https://github.com/Equal-Vote/star-server/blob/6fdb2f653266b5f2710f0701509f54e1e558ecc1/docs/api.md) ' + + 'Unfortunately, the swagger-ui does not support ' + + 'setting cookies, so you will need to manually set the cookie in your browser to test the API', + } + }, schemas: { ...schema.components.schemas, }, diff --git a/packages/backend/src/Routes/ballot.routes.ts b/packages/backend/src/Routes/ballot.routes.ts index bed50ec1..00be915e 100644 --- a/packages/backend/src/Routes/ballot.routes.ts +++ b/packages/backend/src/Routes/ballot.routes.ts @@ -3,10 +3,14 @@ import { deleteAllBallotsForElectionID, getBallotByBallotID, castVoteController, - ballotByID, - handleCastVoteEvent, - innerDeleteAllBallotsForElectionID + } from '../Controllers/Ballot'; +import { + getElectionByID, + electionSpecificAuth, + electionPostAuthMiddleware +} from '../Controllers/Election'; + import { returnElection } from '../Controllers/Election'; import { Router } from 'express'; import asyncHandler from 'express-async-handler'; @@ -20,6 +24,8 @@ export const ballotRouter = Router(); * get: * summary: Get ballots by election ID * tags: [Ballots] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -47,12 +53,15 @@ export const ballotRouter = Router(); */ ballotRouter.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) + /** * @swagger * /Election/{id}/ballots: * delete: * summary: Delete all ballots for an election * tags: [Ballots] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -128,6 +137,8 @@ ballotRouter.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBall * post: * summary: Cast a vote in an election * tags: [Ballots] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -164,3 +175,6 @@ ballotRouter.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBall * description: Election not found */ ballotRouter.post('/Election/:id/vote', asyncHandler(castVoteController)) +ballotRouter.param('id', asyncHandler(getElectionByID)) +ballotRouter.param('id', asyncHandler(electionSpecificAuth)) +ballotRouter.param('id', asyncHandler(electionPostAuthMiddleware)) diff --git a/packages/backend/src/Routes/roll.routes.ts b/packages/backend/src/Routes/roll.routes.ts index 963d285f..14f5ede1 100644 --- a/packages/backend/src/Routes/roll.routes.ts +++ b/packages/backend/src/Routes/roll.routes.ts @@ -10,6 +10,12 @@ import { invalidateElectionRoll, uninvalidateElectionRoll } from '../Controllers/Roll'; +import { + getElectionByID, + electionSpecificAuth, + electionPostAuthMiddleware +} from '../Controllers/Election'; + import asyncHandler from 'express-async-handler'; export const rollRouter = Router(); @@ -302,3 +308,6 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * description: Election not found */ rollRouter.post('/Election/:id/rolls/unflag', asyncHandler(uninvalidateElectionRoll)) +rollRouter.param('id', asyncHandler(getElectionByID)) +rollRouter.param('id', asyncHandler(electionSpecificAuth)) +rollRouter.param('id', asyncHandler(electionPostAuthMiddleware)) diff --git a/packages/backend/src/Services/Account/AccountService.ts b/packages/backend/src/Services/Account/AccountService.ts index 0edbfdb3..8befd738 100644 --- a/packages/backend/src/Services/Account/AccountService.ts +++ b/packages/backend/src/Services/Account/AccountService.ts @@ -79,6 +79,7 @@ export default class AccountService { extractUserFromRequest = (req:IRequest, customKey?:string) => { const token = customKey ? req.cookies.custom_id_token : req.cookies.id_token; + console.log('token', token); if (token){ const key = customKey ? customKey : this.publicKey; return AccountServiceUtils.extractUserFromRequest(req, token, key); From 23fc2dd7de3d589a7972df151751445082d34b5b Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Mon, 23 Sep 2024 13:58:47 -0600 Subject: [PATCH 07/11] fix failed tests --- packages/backend/src/Routes/ballot.routes.ts | 4 ++++ packages/backend/src/Routes/debug.routes.js | 25 -------------------- packages/backend/src/Routes/debug.routes.ts | 23 ++++++++++++++++++ 3 files changed, 27 insertions(+), 25 deletions(-) delete mode 100644 packages/backend/src/Routes/debug.routes.js create mode 100644 packages/backend/src/Routes/debug.routes.ts diff --git a/packages/backend/src/Routes/ballot.routes.ts b/packages/backend/src/Routes/ballot.routes.ts index 00be915e..efdbf6a1 100644 --- a/packages/backend/src/Routes/ballot.routes.ts +++ b/packages/backend/src/Routes/ballot.routes.ts @@ -175,6 +175,10 @@ ballotRouter.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBall * description: Election not found */ ballotRouter.post('/Election/:id/vote', asyncHandler(castVoteController)) +//I don't really understand what the point of this is, but it's in the test suite so I'm including it +ballotRouter.post('/Election/:id/ballot', asyncHandler(returnElection)) + + ballotRouter.param('id', asyncHandler(getElectionByID)) ballotRouter.param('id', asyncHandler(electionSpecificAuth)) ballotRouter.param('id', asyncHandler(electionPostAuthMiddleware)) diff --git a/packages/backend/src/Routes/debug.routes.js b/packages/backend/src/Routes/debug.routes.js deleted file mode 100644 index 273db6cc..00000000 --- a/packages/backend/src/Routes/debug.routes.js +++ /dev/null @@ -1,25 +0,0 @@ -const express = require('express'); -const router = express.Router(); -import { tempTestSuite } from '../test/TempTestSuite'; - -const voterRollController = require('../Controllers/Roll/registerVoterController') - -// Just for debugging -router.get('/', (req, res) => { - res.json("19:40") -}) - -router.get('/test', (req, res) => { - tempTestSuite().then( - val => { - res.json(val); - } - ).catch( - err => { - console.err(err); - res.json(err); - } - ) -}) - -export default router \ No newline at end of file diff --git a/packages/backend/src/Routes/debug.routes.ts b/packages/backend/src/Routes/debug.routes.ts new file mode 100644 index 00000000..5246c02f --- /dev/null +++ b/packages/backend/src/Routes/debug.routes.ts @@ -0,0 +1,23 @@ +import { Router } from 'express'; +import { tempTestSuite } from '../test/TempTestSuite'; + +export const debugRouter = Router(); + +// Just for debugging +debugRouter.get('/', (req, res) => { + res.json("19:40") +}) + +debugRouter.get('/test', (req, res) => { + tempTestSuite().then( + val => { + res.json(val); + } + ).catch( + err => { + console.log(err); + res.json(err); + } + ) +}) + From 60fd1d7045b6ec434f31c037306d4c415a1e2ad6 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Mon, 23 Sep 2024 17:49:31 -0600 Subject: [PATCH 08/11] finished documenting the routes that require authentication --- .../Election/deleteElectionController.ts | 2 +- packages/backend/src/Models/Elections.ts | 1 + packages/backend/src/OpenApi/openapi.json | 89 ++++++++++++++++++- .../backend/src/Routes/elections.routes.ts | 21 ++++- packages/backend/src/Routes/roll.routes.ts | 16 ++++ 5 files changed, 125 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/Controllers/Election/deleteElectionController.ts b/packages/backend/src/Controllers/Election/deleteElectionController.ts index c324c78f..80e0e4c3 100644 --- a/packages/backend/src/Controllers/Election/deleteElectionController.ts +++ b/packages/backend/src/Controllers/Election/deleteElectionController.ts @@ -23,7 +23,7 @@ const deleteElection = async (req: IElectionRequest, res: Response, next: NextFu throw new BadRequest(msg) } Logger.info(req, `Deleted election ${electionId}`); - res.status(200) + res.status(200).send('Election Deleted'); } export { diff --git a/packages/backend/src/Models/Elections.ts b/packages/backend/src/Models/Elections.ts index ae9439af..9b0d722a 100644 --- a/packages/backend/src/Models/Elections.ts +++ b/packages/backend/src/Models/Elections.ts @@ -204,6 +204,7 @@ export default class ElectionsDB implements IElectionStore { .executeTakeFirst() return deletedElection.then((election) => { + console.log(election) if (election) { return true } else { diff --git a/packages/backend/src/OpenApi/openapi.json b/packages/backend/src/OpenApi/openapi.json index de6d6716..8232969e 100644 --- a/packages/backend/src/OpenApi/openapi.json +++ b/packages/backend/src/OpenApi/openapi.json @@ -16,7 +16,7 @@ "type": "apiKey", "in": "cookie", "name": "id_token", - "description": "This uses authenticaion token generated with keycloak, the token can be sent both as a cookie or in the auth_key property of the election object. Unfortunately, the swagger-ui does not support setting cookies, so you will need to manually set the cookie in your browser to test the API" + "description": "This uses authenticaion token generated with keycloak, the token can be sent both as a cookie or in the auth_key property of the election object. [More information about token generation can be found here](https://github.com/Equal-Vote/star-server/blob/6fdb2f653266b5f2710f0701509f54e1e558ecc1/docs/api.md) Unfortunately, the swagger-ui does not support setting cookies, so you will need to manually set the cookie in your browser to test the API" } }, "schemas": { @@ -2000,6 +2000,11 @@ "tags": [ "Elections" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2166,7 +2171,7 @@ "properties": { "election": { "type": "object", - "$ref": "#/components/schemas/Election" + "$ref": "#/components/schemas/NewElection" } } } @@ -2212,6 +2217,11 @@ "tags": [ "Elections" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2286,6 +2296,11 @@ "/Election/{id}/roles": { "put": { "summary": "Edit election roles", + "security": [ + { + "ApiKeyAuth": [] + } + ], "tags": [ "Elections" ], @@ -2352,6 +2367,11 @@ "tags": [ "Elections" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2396,6 +2416,11 @@ "tags": [ "Elections" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2436,6 +2461,11 @@ "tags": [ "Elections" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2487,6 +2517,11 @@ "/Election/{id}/archive": { "post": { "summary": "Archive an election", + "security": [ + { + "ApiKeyAuth": [] + } + ], "tags": [ "Elections" ], @@ -2530,6 +2565,11 @@ "tags": [ "Elections" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2557,6 +2597,11 @@ "tags": [ "Elections" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2727,6 +2772,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2765,6 +2815,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2826,6 +2881,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2866,6 +2926,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2915,6 +2980,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -2958,6 +3028,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -3001,6 +3076,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", @@ -3044,6 +3124,11 @@ "tags": [ "Rolls" ], + "security": [ + { + "ApiKeyAuth": [] + } + ], "parameters": [ { "in": "path", diff --git a/packages/backend/src/Routes/elections.routes.ts b/packages/backend/src/Routes/elections.routes.ts index 5cc8968d..19cef62d 100644 --- a/packages/backend/src/Routes/elections.routes.ts +++ b/packages/backend/src/Routes/elections.routes.ts @@ -91,6 +91,9 @@ electionsRouter.get('/Election/:_id/exists', asyncHandler(electionExistsByID)) * delete: * summary: Delete election by ID * tags: [Elections] + * security: + * - ApiKeyAuth: [] + * parameters: * - in: path * name: id @@ -174,7 +177,7 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * properties: * election: * type: object - * $ref: '#/components/schemas/Election' + * $ref: '#/components/schemas/NewElection' * */ electionsRouter.post('/Elections/', asyncHandler(createElectionController)) @@ -207,6 +210,8 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * post: * summary: Edit an election * tags: [Elections] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -257,6 +262,8 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * /Election/{id}/roles: * put: * summary: Edit election roles + * security: + * - ApiKeyAuth: [] * tags: [Elections] * parameters: * - in: path @@ -301,6 +308,8 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * get: * summary: Get election results by ID * tags: [Elections] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -333,6 +342,8 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * post: * summary: Finalize an election * tags: [Elections] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -361,6 +372,8 @@ electionsRouter.post('/Election/:id/finalize',asyncHandler(finalizeElection)) * post: * summary: Set public results for an election * tags: [Elections] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -396,6 +409,8 @@ electionsRouter.post('/Election/:id/setPublicResults',asyncHandler(setPublicResu * /Election/{id}/archive: * post: * summary: Archive an election + * security: + * - ApiKeyAuth: [] * tags: [Elections] * parameters: * - in: path @@ -426,6 +441,8 @@ electionsRouter.post('/Election/:id/archive', asyncHandler(archiveElection)) * post: * summary: Send invitations for an election * tags: [Elections] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -446,6 +463,8 @@ electionsRouter.post('/Election/:id/sendInvites', asyncHandler(sendInvitationsCo * post: * summary: Send an invitation to a specific voter * tags: [Elections] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id diff --git a/packages/backend/src/Routes/roll.routes.ts b/packages/backend/src/Routes/roll.routes.ts index 14f5ede1..d100851d 100644 --- a/packages/backend/src/Routes/roll.routes.ts +++ b/packages/backend/src/Routes/roll.routes.ts @@ -58,6 +58,8 @@ rollRouter.post('/Election/:id/register',asyncHandler(registerVoter)) * get: * summary: Get rolls by election ID * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -87,6 +89,8 @@ rollRouter.get('/Election/:id/rolls', asyncHandler(getRollsByElectionID)) * get: * summary: Get roll by voter ID * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -122,6 +126,8 @@ rollRouter.get('/Election/:id/rolls/:voter_id', asyncHandler(getByVoterID)) * post: * summary: Add a list of rolls to an election * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -166,6 +172,8 @@ rollRouter.post('/Election/:id/rolls/', asyncHandler(addElectionRoll)) * put: * summary: Edit an election roll * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -194,6 +202,8 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * post: * summary: Approve an election roll * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -224,6 +234,8 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * post: * summary: Flag an election roll * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -253,6 +265,8 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * post: * summary: Invalidate an election roll * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id @@ -283,6 +297,8 @@ rollRouter.put('/Election/:id/rolls/', asyncHandler(editElectionRoll)) * post: * summary: Unflag an election roll * tags: [Rolls] + * security: + * - ApiKeyAuth: [] * parameters: * - in: path * name: id From acf22bd266d4b57a6d0e48a3c5bcffac115cc889 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Tue, 24 Sep 2024 16:37:26 -0600 Subject: [PATCH 09/11] updated to show which properties are required, moved generated files into build folder --- packages/backend/package.json | 4 +- .../backend/src/OpenApi/generateOpenApi.ts | 4 - .../src/OpenApi/generateSwaggerJSON.ts | 9 + packages/backend/src/OpenApi/openapi.json | 3171 ----------------- .../OpenApi/{swagger.ts => swaggerSpec.ts} | 0 packages/backend/src/app.ts | 2 +- packages/shared/fixSchemaRefs.ts | 2 +- packages/shared/package.json | 4 +- packages/shared/schema.json | 1715 --------- 9 files changed, 15 insertions(+), 4896 deletions(-) delete mode 100644 packages/backend/src/OpenApi/generateOpenApi.ts create mode 100644 packages/backend/src/OpenApi/generateSwaggerJSON.ts delete mode 100644 packages/backend/src/OpenApi/openapi.json rename packages/backend/src/OpenApi/{swagger.ts => swaggerSpec.ts} (100%) delete mode 100644 packages/shared/schema.json diff --git a/packages/backend/package.json b/packages/backend/package.json index ac581ee4..a6e80314 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -7,13 +7,13 @@ "test": "jest --forceExit --detectOpenHandles", "start": "npm run build && node ./build/src/index.js", "dev": "tsx watch ./src", - "build": "tsc --project ./ && npm run generate:openapi", + "build": "npm run generate:openapi && tsc --project ./", "clean": "npx rimraf build node_modules ../node_modules", "migrate:latest": "node ./build/src/Migrators/migrate-to-latest.js", "migrate:up": "node ./build/src/Migrators/migrate-up.js", "migrate:down": "node ./build/src/Migrators/migrate-down.js", "db_sandbox": "npm run-script build && node ./build/src/test/database_sandbox.js", - "generate:openapi": "ts-node ./src/OpenApi/generateOpenApi.ts" + "generate:openapi": "ts-node ./src/OpenApi/generateSwaggerJSON.ts" }, "keywords": [], "author": "", diff --git a/packages/backend/src/OpenApi/generateOpenApi.ts b/packages/backend/src/OpenApi/generateOpenApi.ts deleted file mode 100644 index 66b2b7c3..00000000 --- a/packages/backend/src/OpenApi/generateOpenApi.ts +++ /dev/null @@ -1,4 +0,0 @@ -import fs from 'fs'; -import openapiSpecification from './swagger'; - -fs.writeFileSync('./src/OpenApi/openapi.json', JSON.stringify(openapiSpecification, null, 2)); diff --git a/packages/backend/src/OpenApi/generateSwaggerJSON.ts b/packages/backend/src/OpenApi/generateSwaggerJSON.ts new file mode 100644 index 00000000..4f73b837 --- /dev/null +++ b/packages/backend/src/OpenApi/generateSwaggerJSON.ts @@ -0,0 +1,9 @@ +import fs from 'fs'; +import openapiSpecification from './swaggerSpec'; +const dir = './build/src/OpenApi'; +if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); +} + +fs.writeFileSync(`${dir}/swagger.json`, JSON.stringify(openapiSpecification, null, 2)); +fs.writeFileSync('./build/src/OpenApi/swagger.json', JSON.stringify(openapiSpecification, null, 2)); diff --git a/packages/backend/src/OpenApi/openapi.json b/packages/backend/src/OpenApi/openapi.json deleted file mode 100644 index 8232969e..00000000 --- a/packages/backend/src/OpenApi/openapi.json +++ /dev/null @@ -1,3171 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "API Documentation", - "version": "1.0.0" - }, - "servers": [ - { - "url": "/API", - "description": "Base URL for the API" - } - ], - "components": { - "securitySchemes": { - "ApiKeyAuth": { - "type": "apiKey", - "in": "cookie", - "name": "id_token", - "description": "This uses authenticaion token generated with keycloak, the token can be sent both as a cookie or in the auth_key property of the election object. [More information about token generation can be found here](https://github.com/Equal-Vote/star-server/blob/6fdb2f653266b5f2710f0701509f54e1e558ecc1/docs/api.md) Unfortunately, the swagger-ui does not support setting cookies, so you will need to manually set the cookie in your browser to test the API" - } - }, - "schemas": { - "Ballot": { - "properties": { - "ballot_id": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "date_submitted": { - "type": "number" - }, - "election_id": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "history": { - "items": { - "$ref": "#/components/schemas/BallotAction" - }, - "type": "array" - }, - "ip_hash": { - "type": "string" - }, - "precinct": { - "type": "string" - }, - "status": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "user_id": { - "type": "string" - }, - "votes": { - "items": { - "$ref": "#/components/schemas/Vote" - }, - "type": "array" - } - }, - "type": "object" - }, - "BallotAction": { - "properties": { - "action_type": { - "type": "string" - }, - "actor": { - "type": "string" - }, - "timestamp": { - "type": "number" - } - }, - "type": "object" - }, - "Candidate": { - "properties": { - "bio": { - "type": "string" - }, - "candidate_id": { - "type": "string" - }, - "candidate_name": { - "type": "string" - }, - "candidate_url": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "party": { - "type": "string" - }, - "partyUrl": { - "type": "string" - }, - "party_url": { - "type": "string" - }, - "photo_filename": { - "type": "string" - } - }, - "type": "object" - }, - "Credentials": { - "properties": { - "email": { - "type": "string" - }, - "password": { - "type": "string" - } - }, - "type": "object" - }, - "Election": { - "properties": { - "admin_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "audit_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "auth_key": { - "type": "string" - }, - "claim_key_hash": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "credential_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "election_id": { - "type": "string" - }, - "end_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "frontend_url": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "is_public": { - "type": "boolean" - }, - "owner_id": { - "type": "string" - }, - "races": { - "items": { - "$ref": "#/components/schemas/Race" - }, - "type": "array" - }, - "settings": { - "$ref": "#/components/schemas/ElectionSettings" - }, - "start_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "state": { - "$ref": "#/components/schemas/ElectionState" - }, - "support_email": { - "type": "string" - }, - "title": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - } - }, - "type": "object" - }, - "ElectionResults": { - "anyOf": [ - { - "properties": { - "results": { - "$ref": "#/components/schemas/starResults" - }, - "votingMethod": { - "const": "STAR", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/allocatedScoreResults" - }, - "votingMethod": { - "const": "STAR_PR", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/approvalResults" - }, - "votingMethod": { - "const": "Approval", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/rankedRobinResults" - }, - "votingMethod": { - "const": "RankedRobin", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/irvResults" - }, - "votingMethod": { - "const": "IRV", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/pluralityResults" - }, - "votingMethod": { - "const": "Plurality", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/irvResults" - }, - "votingMethod": { - "const": "STV", - "type": "string" - } - }, - "type": "object" - } - ] - }, - "ElectionRoll": { - "properties": { - "address": { - "type": "string" - }, - "ballot_id": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "election_id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "email_data": { - "properties": { - "inviteResponse": {}, - "reminderResponse": {} - }, - "type": "object" - }, - "head": { - "type": "boolean" - }, - "history": { - "items": { - "$ref": "#/components/schemas/ElectionRollAction" - }, - "type": "array" - }, - "ip_hash": { - "type": "string" - }, - "precinct": { - "type": "string" - }, - "registration": {}, - "state": { - "$ref": "#/components/schemas/ElectionRollState" - }, - "submitted": { - "type": "boolean" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "voter_id": { - "type": "string" - } - }, - "type": "object" - }, - "ElectionRollAction": { - "properties": { - "action_type": { - "type": "string" - }, - "actor": { - "type": "string" - }, - "timestamp": { - "type": "number" - } - }, - "type": "object" - }, - "ElectionRollState": { - "enum": [ - "approved", - "flagged", - "registered", - "invalid" - ], - "type": "string" - }, - "ElectionSettings": { - "properties": { - "ballot_updates": { - "type": "boolean" - }, - "break_ties_randomly": { - "type": "boolean" - }, - "invitation": { - "enum": [ - "address", - "email" - ], - "type": "string" - }, - "max_rankings": { - "type": "number" - }, - "public_results": { - "type": "boolean" - }, - "random_candidate_order": { - "type": "boolean" - }, - "reminders": { - "type": "boolean" - }, - "require_instruction_confirmation": { - "type": "boolean" - }, - "term_type": { - "enum": [ - "election", - "poll" - ], - "type": "string" - }, - "time_zone": { - "type": "string" - }, - "voter_access": { - "enum": [ - "closed", - "open", - "registration" - ], - "type": "string" - }, - "voter_authentication": { - "$ref": "#/components/schemas/authentication" - } - }, - "type": "object" - }, - "ElectionState": { - "enum": [ - "archived", - "closed", - "draft", - "finalized", - "open" - ], - "type": "string" - }, - "Email": { - "type": "string" - }, - "NewBallot": { - "properties": { - "ballot_id": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "date_submitted": { - "type": "number" - }, - "election_id": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "history": { - "items": { - "$ref": "#/components/schemas/BallotAction" - }, - "type": "array" - }, - "ip_hash": { - "type": "string" - }, - "precinct": { - "type": "string" - }, - "status": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "user_id": { - "type": "string" - }, - "votes": { - "items": { - "$ref": "#/components/schemas/Vote" - }, - "type": "array" - } - }, - "type": "object" - }, - "NewElection": { - "properties": { - "admin_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "audit_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "auth_key": { - "type": "string" - }, - "claim_key_hash": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "credential_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "election_id": { - "type": "string" - }, - "end_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "frontend_url": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "is_public": { - "type": "boolean" - }, - "owner_id": { - "type": "string" - }, - "races": { - "items": { - "$ref": "#/components/schemas/Race" - }, - "type": "array" - }, - "settings": { - "$ref": "#/components/schemas/ElectionSettings" - }, - "start_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "state": { - "$ref": "#/components/schemas/ElectionState" - }, - "support_email": { - "type": "string" - }, - "title": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - } - }, - "type": "object" - }, - "NewPassword": { - "properties": { - "newPassword": { - "type": "string" - }, - "oldPassword": { - "type": "string" - } - }, - "type": "object" - }, - "Omit": { - "type": "object" - }, - "Omit_1": { - "type": "object" - }, - "Partial>": { - "type": "object" - }, - "PartialBy": { - "allOf": [ - { - "$ref": "#/components/schemas/Omit_1" - }, - { - "$ref": "#/components/schemas/Partial>" - } - ] - }, - "Race": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/Candidate" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "num_winners": { - "type": "number" - }, - "precincts": { - "items": { - "additionalProperties": false, - "patternProperties": { - "^[0-9]+$": { - "type": "string" - } - }, - "properties": { - "length": { - "type": "number" - } - }, - "type": "object" - }, - "type": "array" - }, - "race_id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "voting_method": { - "$ref": "#/components/schemas/VotingMethod" - } - }, - "type": "object" - }, - "Score": { - "properties": { - "candidate_id": { - "type": "string" - }, - "score": { - "type": "number" - } - }, - "type": "object" - }, - "TermType": { - "enum": [ - "election", - "poll" - ], - "type": "string" - }, - "Uid": { - "type": "string" - }, - "Vote": { - "properties": { - "race_id": { - "type": "string" - }, - "scores": { - "items": { - "$ref": "#/components/schemas/Score" - }, - "type": "array" - } - }, - "type": "object" - }, - "VoterAuth": { - "properties": { - "authorized_voter": { - "type": "boolean" - }, - "has_voted": { - "type": "boolean" - }, - "permissions": { - "items": { - "enum": [ - "canAddToElectionRoll", - "canApproveElectionRoll", - "canDeleteAllBallots", - "canDeleteElection", - "canDeleteElectionRoll", - "canEditBallot", - "canEditElection", - "canEditElectionRoles", - "canEditElectionRoll", - "canEditElectionState", - "canFlagBallot", - "canFlagElectionRoll", - "canInvalidateBallot", - "canInvalidateElectionRoll", - "canSendEmails", - "canUnflagElectionRoll", - "canViewBallot", - "canViewBallots", - "canViewElection", - "canViewElectionRoll", - "canViewElectionRollIDs", - "canViewPreliminaryResults" - ], - "type": "string" - }, - "type": "array" - }, - "required": { - "type": "string" - }, - "roles": { - "items": { - "$ref": "#/components/schemas/roles" - }, - "type": "array" - } - }, - "type": "object" - }, - "VotingMethod": { - "enum": [ - "Approval", - "IRV", - "Plurality", - "RankedRobin", - "STAR", - "STAR_PR", - "STV" - ], - "type": "string" - }, - "allocatedScoreResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/allocatedScoreSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "type": "array" - } - }, - "type": "object" - }, - "allocatedScoreSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "noPreferenceStars": { - "items": { - "type": "number" - }, - "type": "array" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "scoreHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "spentAboves": { - "items": { - "type": "number" - }, - "type": "array" - }, - "splitPoints": { - "items": { - "type": "number" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - }, - "weight_on_splits": { - "items": { - "type": "number" - }, - "type": "array" - }, - "weightedScoresByRound": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - } - }, - "type": "object" - }, - "approvalResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/approvalSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "approvalSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "authentication": { - "properties": { - "address": { - "type": "boolean" - }, - "email": { - "type": "boolean" - }, - "ip_address": { - "type": "boolean" - }, - "phone": { - "type": "boolean" - }, - "registration_api_endpoint": { - "type": "string" - }, - "registration_data": { - "items": [ - { - "$ref": "#/components/schemas/registration_field" - } - ], - "maxItems": 1, - "minItems": 1, - "type": "array" - }, - "voter_id": { - "type": "boolean" - } - }, - "type": "object" - }, - "ballot": { - "items": { - "type": "number" - }, - "type": "array" - }, - "ballots": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "candidate": { - "properties": { - "index": { - "type": "number" - }, - "name": { - "type": "string" - }, - "tieBreakOrder": { - "type": "number" - } - }, - "type": "object" - }, - "fiveStarCount": { - "properties": { - "candidate": { - "$ref": "#/components/schemas/candidate" - }, - "counts": { - "type": "number" - } - }, - "type": "object" - }, - "genericResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/genericSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "genericSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "irvResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "exhaustedVoteCounts": { - "items": { - "type": "number" - }, - "type": "array" - }, - "logs": { - "items": { - "type": "string" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "overVoteCounts": { - "items": { - "type": "number" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/irvRoundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/rankedRobinSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "voteCounts": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - } - }, - "type": "object" - }, - "irvRoundResults": { - "properties": { - "eliminated": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "logs": { - "items": { - "type": "string" - }, - "type": "array" - }, - "winners": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "irvSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "permission": { - "items": { - "$ref": "#/components/schemas/roles" - }, - "type": "array" - }, - "pluralityResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/pluralitySummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "pluralitySummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankedRobinResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/rankedRobinSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "rankedRobinSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "registration_field": { - "properties": { - "field_name": { - "type": "string" - }, - "field_type": { - "enum": [ - "photo", - "text" - ], - "type": "string" - }, - "help_text": { - "type": "string" - } - }, - "type": "object" - }, - "roles": { - "enum": [ - "owner", - "admin", - "auditor", - "system_admin", - "credentialer" - ], - "type": "string" - }, - "roundResults": { - "properties": { - "logs": { - "items": { - "$ref": "#/components/schemas/tabulatorLog" - }, - "type": "array" - }, - "runner_up": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "winners": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "score": { - "type": "number" - }, - "scoreHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "starResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/starSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "starSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "noPreferenceStars": { - "items": { - "type": "number" - }, - "type": "array" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "scoreHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "tabulatorLog": { - "anyOf": [ - { - "$ref": "#/components/schemas/tabulatorLogObject" - }, - { - "type": "string" - } - ] - }, - "tabulatorLogObject": { - "additionalProperties": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": [ - "string", - "number" - ] - } - ] - }, - "properties": { - "key": { - "type": "string" - } - }, - "type": "object" - }, - "tieBreakType": { - "enum": [ - "five_star", - "none", - "random", - "score" - ], - "type": "string" - }, - "totalScore": { - "properties": { - "index": { - "type": "number" - }, - "score": { - "type": "number" - } - }, - "type": "object" - }, - "voter": { - "properties": { - "csvRow": { - "type": "number" - } - }, - "type": "object" - } - } - }, - "paths": { - "/Election/{id}/ballots": { - "get": { - "summary": "Get ballots by election ID", - "tags": [ - "Ballots" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "List of ballots", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - }, - "ballots": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Ballot" - } - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - }, - "delete": { - "summary": "Delete all ballots for an election", - "tags": [ - "Ballots" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "public_results": { - "type": "boolean", - "description": "If the election results are public" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "All ballots deleted", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "description": "If the deletion was successful" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/ballot/{ballot_id}": { - "get": { - "summary": "Get ballot by ballot ID", - "tags": [ - "Ballots" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - }, - { - "in": "path", - "name": "ballot_id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The ballot ID" - } - ], - "responses": { - "200": { - "description": "Ballot details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "ballot": { - "type": "object", - "$ref": "#/components/schemas/Ballot" - } - } - } - } - } - }, - "404": { - "description": "Ballot not found" - } - } - } - }, - "/Election/{id}/vote": { - "post": { - "summary": "Cast a vote in an election", - "tags": [ - "Ballots" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "ballot": { - "type": "object", - "$ref": "#/components/schemas/Ballot" - }, - "recieptEmail": { - "oneOf": [ - { - "type": "string" - }, - { - "type": null - } - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Vote cast", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "ballot": { - "type": "object", - "$ref": "#/components/schemas/Ballot" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}": { - "get": { - "summary": "Get election by ID", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "The election description by ID", - "content": { - "application/json": { - "schema": { - "type": "object", - "$ref": "#/components/schemas/Election" - } - } - } - }, - "404": { - "description": "Election not found" - } - } - }, - "delete": { - "summary": "Delete election by ID", - "tags": [ - "Elections" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election deleted" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{_id}/exists": { - "get": { - "summary": "Check if election exists by ID", - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election exists", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "exists": { - "type": "boolean" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Elections": { - "get": { - "summary": "Get all elections", - "tags": [ - "Elections" - ], - "responses": { - "200": { - "description": "List of elections", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "elections_as_official": { - "oneOf": [ - { - "type": "array", - "items": { - "$ref": "#/components/schemas/Election" - } - }, - { - "type": null - } - ] - }, - "elections_as_unsubmitted_voter": { - "oneOf": [ - { - "type": "array", - "items": { - "$ref": "#/components/schemas/Election" - } - }, - { - "type": null - }, - { - "type": "undefined" - } - ] - }, - "elections_as_submitted_voter": { - "oneOf": [ - { - "type": "array", - "items": { - "$ref": "#/components/schemas/Election" - } - }, - { - "type": null - } - ] - }, - "open_elections": { - "oneOf": [ - { - "type": "array", - "items": { - "$ref": "#/components/schemas/Election" - } - }, - { - "type": null - } - ] - } - } - } - } - } - } - } - }, - "post": { - "summary": "Create a new election", - "tags": [ - "Elections" - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Election": { - "$ref": "#/components/schemas/Election" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Election created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/NewElection" - } - } - } - } - } - } - } - } - }, - "/GlobalElectionStats": { - "get": { - "summary": "Get global election statistics", - "tags": [ - "Elections" - ], - "responses": { - "200": { - "description": "Global election statistics", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "elections": { - "type": "number", - "description": "Number of elections" - }, - "votes": { - "type": "number", - "description": "Number of votes" - } - } - } - } - } - } - } - } - }, - "/Election/{id}/edit": { - "post": { - "summary": "Edit an election", - "tags": [ - "Elections" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Election": { - "$ref": "#/components/schemas/Election" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Election edited", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - }, - "voterAuth": { - "type": "object", - "properties": { - "authorized_voter": { - "type": "boolean" - }, - "has_voted": { - "type": "boolean" - }, - "roles": { - "type": "array", - "items": { - "$ref": "#/components/schemas/roles" - } - }, - "permissions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/permissions" - } - } - } - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/roles": { - "put": { - "summary": "Edit election roles", - "security": [ - { - "ApiKeyAuth": [] - } - ], - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "admin_ids": { - "type": "array", - "items": "string" - }, - "audit_ids": { - "type": "array", - "items": "string" - }, - "credential_ids": { - "type": "array", - "items": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Roles edited", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/ElectionResult/{id}": { - "get": { - "summary": "Get election results by ID", - "tags": [ - "Elections" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election results", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - }, - "results": { - "type": "object", - "$ref": "#/components/schemas/ElectionResults" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/finalize": { - "post": { - "summary": "Finalize an election", - "tags": [ - "Elections" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election finalized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/setPublicResults": { - "post": { - "summary": "Set public results for an election", - "tags": [ - "Elections" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "public_results": { - "type": "boolean" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Public results set", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/archive": { - "post": { - "summary": "Archive an election", - "security": [ - { - "ApiKeyAuth": [] - } - ], - "tags": [ - "Elections" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Election archived", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/sendInvites": { - "post": { - "summary": "Send invitations for an election", - "tags": [ - "Elections" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Invitations sent" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/sendInvite/{voter_id}": { - "post": { - "summary": "Send an invitation to a specific voter", - "tags": [ - "Elections" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - }, - { - "in": "path", - "name": "voter_id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The voter ID" - } - ], - "responses": { - "200": { - "description": "Invitation sent", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "404": { - "description": "Election or voter not found" - } - } - } - }, - "/Sandbox": { - "post": { - "summary": "Get sandbox results", - "tags": [ - "Elections" - ], - "responses": { - "200": { - "description": "Sandbox results", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "results": { - "type": "object", - "$ref": "#/components/schemas/ElectionResults" - }, - "nWinners": { - "type": "number", - "description": "Number of winners" - }, - "candidates": { - "type": "array", - "items": "string" - } - } - } - } - } - } - } - } - }, - "/images": { - "post": { - "summary": "Upload an image", - "tags": [ - "Elections" - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "file": { - "type": "string", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Image uploaded", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "photo_filename": { - "oneOf": [ - "string", - null - ] - } - } - } - } - } - } - } - } - }, - "/Election/{id}/register": { - "post": { - "summary": "Register a voter for an election", - "tags": [ - "Rolls" - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Voter registered", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - }, - "NewElectionRoll": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls": { - "get": { - "summary": "Get rolls by election ID", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "List of rolls", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - }, - "post": { - "summary": "Add a list of rolls to an election", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRoll": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Rolls added", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "election": { - "type": "object", - "$ref": "#/components/schemas/Election" - }, - "NewElectionRoll": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - }, - "put": { - "summary": "Edit an election roll", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "responses": { - "200": { - "description": "Roll edited", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls/{voter_id}": { - "get": { - "summary": "Get roll by voter ID", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - }, - { - "in": "path", - "name": "voter_id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The voter ID" - } - ], - "responses": { - "200": { - "description": "Roll details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "404": { - "description": "Roll not found" - } - } - } - }, - "/Election/{id}/rolls/approve": { - "post": { - "summary": "Approve an election roll", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Roll approved" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls/flag": { - "post": { - "summary": "Flag an election roll", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Roll flagged" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls/invalidate": { - "post": { - "summary": "Invalidate an election roll", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Roll invalidated" - }, - "404": { - "description": "Election not found" - } - } - } - }, - "/Election/{id}/rolls/unflag": { - "post": { - "summary": "Unflag an election roll", - "tags": [ - "Rolls" - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "id", - "schema": { - "type": "string" - }, - "required": true, - "description": "The election ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "electionRollEntry": { - "type": "object", - "$ref": "#/components/schemas/ElectionRoll" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Roll unflagged" - }, - "404": { - "description": "Election not found" - } - } - } - } - }, - "tags": [] -} \ No newline at end of file diff --git a/packages/backend/src/OpenApi/swagger.ts b/packages/backend/src/OpenApi/swaggerSpec.ts similarity index 100% rename from packages/backend/src/OpenApi/swagger.ts rename to packages/backend/src/OpenApi/swaggerSpec.ts diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 82bcde08..02a449f6 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -14,7 +14,7 @@ import registerEvents from './Routes/registerEvents'; import { setupSockets } from './socketHandler'; import { getMetaTags } from './Util'; import swaggerUi from 'swagger-ui-express'; -import openapi from './OpenApi/openapi.json' +import openapi from '../build/src/OpenApi/swagger.json'; import { getUserToken, getUser } from './Controllers/User'; const asyncHandler = require('express-async-handler') diff --git a/packages/shared/fixSchemaRefs.ts b/packages/shared/fixSchemaRefs.ts index 3491d07a..8fe3e887 100644 --- a/packages/shared/fixSchemaRefs.ts +++ b/packages/shared/fixSchemaRefs.ts @@ -1,7 +1,7 @@ const fs = require('fs'); // Load the generated schema -const schemaPath = './schema.json'; +const schemaPath = './dist/schema.json'; const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8')); // Function to recursively replace $ref values diff --git a/packages/shared/package.json b/packages/shared/package.json index 1e8aeac7..84ed163c 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -8,7 +8,7 @@ "test": "npm install && npm test", "clean": "npx rimraf dist node_modules ../node_modules", "build": "tsc --project ./ && npm run generate:schema", - "generate:schema": "typescript-json-schema tsconfig.json \"*\" --out schema.json && node ./fixSchemaRefs.ts" + "generate:schema": "typescript-json-schema tsconfig.json \"*\" --required --out dist/schema.json && node ./fixSchemaRefs.ts" }, "keywords": [], "author": "", @@ -38,7 +38,7 @@ "default": "./dist/domain_model/*.js" }, "./schema.json": { - "default": "./schema.json" + "default": "./dist/schema.json" } }, "dependencies": { diff --git a/packages/shared/schema.json b/packages/shared/schema.json deleted file mode 100644 index a75e4ce8..00000000 --- a/packages/shared/schema.json +++ /dev/null @@ -1,1715 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "components": { - "schemas": { - "Ballot": { - "properties": { - "ballot_id": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "date_submitted": { - "type": "number" - }, - "election_id": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "history": { - "items": { - "$ref": "#/components/schemas/BallotAction" - }, - "type": "array" - }, - "ip_hash": { - "type": "string" - }, - "precinct": { - "type": "string" - }, - "status": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "user_id": { - "type": "string" - }, - "votes": { - "items": { - "$ref": "#/components/schemas/Vote" - }, - "type": "array" - } - }, - "type": "object" - }, - "BallotAction": { - "properties": { - "action_type": { - "type": "string" - }, - "actor": { - "type": "string" - }, - "timestamp": { - "type": "number" - } - }, - "type": "object" - }, - "Candidate": { - "properties": { - "bio": { - "type": "string" - }, - "candidate_id": { - "type": "string" - }, - "candidate_name": { - "type": "string" - }, - "candidate_url": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "party": { - "type": "string" - }, - "partyUrl": { - "type": "string" - }, - "party_url": { - "type": "string" - }, - "photo_filename": { - "type": "string" - } - }, - "type": "object" - }, - "Credentials": { - "properties": { - "email": { - "type": "string" - }, - "password": { - "type": "string" - } - }, - "type": "object" - }, - "Election": { - "properties": { - "admin_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "audit_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "auth_key": { - "type": "string" - }, - "claim_key_hash": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "credential_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "election_id": { - "type": "string" - }, - "end_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "frontend_url": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "is_public": { - "type": "boolean" - }, - "owner_id": { - "type": "string" - }, - "races": { - "items": { - "$ref": "#/components/schemas/Race" - }, - "type": "array" - }, - "settings": { - "$ref": "#/components/schemas/ElectionSettings" - }, - "start_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "state": { - "$ref": "#/components/schemas/ElectionState" - }, - "support_email": { - "type": "string" - }, - "title": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - } - }, - "type": "object" - }, - "ElectionResults": { - "anyOf": [ - { - "properties": { - "results": { - "$ref": "#/components/schemas/starResults" - }, - "votingMethod": { - "const": "STAR", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/allocatedScoreResults" - }, - "votingMethod": { - "const": "STAR_PR", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/approvalResults" - }, - "votingMethod": { - "const": "Approval", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/rankedRobinResults" - }, - "votingMethod": { - "const": "RankedRobin", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/irvResults" - }, - "votingMethod": { - "const": "IRV", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/pluralityResults" - }, - "votingMethod": { - "const": "Plurality", - "type": "string" - } - }, - "type": "object" - }, - { - "properties": { - "results": { - "$ref": "#/components/schemas/irvResults" - }, - "votingMethod": { - "const": "STV", - "type": "string" - } - }, - "type": "object" - } - ] - }, - "ElectionRoll": { - "properties": { - "address": { - "type": "string" - }, - "ballot_id": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "election_id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "email_data": { - "properties": { - "inviteResponse": {}, - "reminderResponse": {} - }, - "type": "object" - }, - "head": { - "type": "boolean" - }, - "history": { - "items": { - "$ref": "#/components/schemas/ElectionRollAction" - }, - "type": "array" - }, - "ip_hash": { - "type": "string" - }, - "precinct": { - "type": "string" - }, - "registration": {}, - "state": { - "$ref": "#/components/schemas/ElectionRollState" - }, - "submitted": { - "type": "boolean" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "voter_id": { - "type": "string" - } - }, - "type": "object" - }, - "ElectionRollAction": { - "properties": { - "action_type": { - "type": "string" - }, - "actor": { - "type": "string" - }, - "timestamp": { - "type": "number" - } - }, - "type": "object" - }, - "ElectionRollState": { - "enum": [ - "approved", - "flagged", - "registered", - "invalid" - ], - "type": "string" - }, - "ElectionSettings": { - "properties": { - "ballot_updates": { - "type": "boolean" - }, - "break_ties_randomly": { - "type": "boolean" - }, - "invitation": { - "enum": [ - "address", - "email" - ], - "type": "string" - }, - "max_rankings": { - "type": "number" - }, - "public_results": { - "type": "boolean" - }, - "random_candidate_order": { - "type": "boolean" - }, - "reminders": { - "type": "boolean" - }, - "require_instruction_confirmation": { - "type": "boolean" - }, - "term_type": { - "enum": [ - "election", - "poll" - ], - "type": "string" - }, - "time_zone": { - "type": "string" - }, - "voter_access": { - "enum": [ - "closed", - "open", - "registration" - ], - "type": "string" - }, - "voter_authentication": { - "$ref": "#/components/schemas/authentication" - } - }, - "type": "object" - }, - "ElectionState": { - "enum": [ - "archived", - "closed", - "draft", - "finalized", - "open" - ], - "type": "string" - }, - "Email": { - "type": "string" - }, - "NewBallot": { - "properties": { - "ballot_id": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "date_submitted": { - "type": "number" - }, - "election_id": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "history": { - "items": { - "$ref": "#/components/schemas/BallotAction" - }, - "type": "array" - }, - "ip_hash": { - "type": "string" - }, - "precinct": { - "type": "string" - }, - "status": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "user_id": { - "type": "string" - }, - "votes": { - "items": { - "$ref": "#/components/schemas/Vote" - }, - "type": "array" - } - }, - "type": "object" - }, - "NewElection": { - "properties": { - "admin_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "audit_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "auth_key": { - "type": "string" - }, - "claim_key_hash": { - "type": "string" - }, - "create_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "credential_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "election_id": { - "type": "string" - }, - "end_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "frontend_url": { - "type": "string" - }, - "head": { - "type": "boolean" - }, - "is_public": { - "type": "boolean" - }, - "owner_id": { - "type": "string" - }, - "races": { - "items": { - "$ref": "#/components/schemas/Race" - }, - "type": "array" - }, - "settings": { - "$ref": "#/components/schemas/ElectionSettings" - }, - "start_time": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - }, - "state": { - "$ref": "#/components/schemas/ElectionState" - }, - "support_email": { - "type": "string" - }, - "title": { - "type": "string" - }, - "update_date": { - "anyOf": [ - { - "format": "date-time", - "type": "string" - }, - { - "type": "string" - } - ] - } - }, - "type": "object" - }, - "NewPassword": { - "properties": { - "newPassword": { - "type": "string" - }, - "oldPassword": { - "type": "string" - } - }, - "type": "object" - }, - "Omit": { - "type": "object" - }, - "Omit_1": { - "type": "object" - }, - "Partial>": { - "type": "object" - }, - "PartialBy": { - "allOf": [ - { - "$ref": "#/components/schemas/Omit_1" - }, - { - "$ref": "#/components/schemas/Partial>" - } - ] - }, - "Race": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/Candidate" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "num_winners": { - "type": "number" - }, - "precincts": { - "items": { - "additionalProperties": false, - "patternProperties": { - "^[0-9]+$": { - "type": "string" - } - }, - "properties": { - "length": { - "type": "number" - } - }, - "type": "object" - }, - "type": "array" - }, - "race_id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "voting_method": { - "$ref": "#/components/schemas/VotingMethod" - } - }, - "type": "object" - }, - "Score": { - "properties": { - "candidate_id": { - "type": "string" - }, - "score": { - "type": "number" - } - }, - "type": "object" - }, - "TermType": { - "enum": [ - "election", - "poll" - ], - "type": "string" - }, - "Uid": { - "type": "string" - }, - "Vote": { - "properties": { - "race_id": { - "type": "string" - }, - "scores": { - "items": { - "$ref": "#/components/schemas/Score" - }, - "type": "array" - } - }, - "type": "object" - }, - "VoterAuth": { - "properties": { - "authorized_voter": { - "type": "boolean" - }, - "has_voted": { - "type": "boolean" - }, - "permissions": { - "items": { - "enum": [ - "canAddToElectionRoll", - "canApproveElectionRoll", - "canDeleteAllBallots", - "canDeleteElection", - "canDeleteElectionRoll", - "canEditBallot", - "canEditElection", - "canEditElectionRoles", - "canEditElectionRoll", - "canEditElectionState", - "canFlagBallot", - "canFlagElectionRoll", - "canInvalidateBallot", - "canInvalidateElectionRoll", - "canSendEmails", - "canUnflagElectionRoll", - "canViewBallot", - "canViewBallots", - "canViewElection", - "canViewElectionRoll", - "canViewElectionRollIDs", - "canViewPreliminaryResults" - ], - "type": "string" - }, - "type": "array" - }, - "required": { - "type": "string" - }, - "roles": { - "items": { - "$ref": "#/components/schemas/roles" - }, - "type": "array" - } - }, - "type": "object" - }, - "VotingMethod": { - "enum": [ - "Approval", - "IRV", - "Plurality", - "RankedRobin", - "STAR", - "STAR_PR", - "STV" - ], - "type": "string" - }, - "allocatedScoreResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/allocatedScoreSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "type": "array" - } - }, - "type": "object" - }, - "allocatedScoreSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "noPreferenceStars": { - "items": { - "type": "number" - }, - "type": "array" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "scoreHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "spentAboves": { - "items": { - "type": "number" - }, - "type": "array" - }, - "splitPoints": { - "items": { - "type": "number" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - }, - "weight_on_splits": { - "items": { - "type": "number" - }, - "type": "array" - }, - "weightedScoresByRound": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - } - }, - "type": "object" - }, - "approvalResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/approvalSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "approvalSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "authentication": { - "properties": { - "address": { - "type": "boolean" - }, - "email": { - "type": "boolean" - }, - "ip_address": { - "type": "boolean" - }, - "phone": { - "type": "boolean" - }, - "registration_api_endpoint": { - "type": "string" - }, - "registration_data": { - "items": [ - { - "$ref": "#/components/schemas/registration_field" - } - ], - "maxItems": 1, - "minItems": 1, - "type": "array" - }, - "voter_id": { - "type": "boolean" - } - }, - "type": "object" - }, - "ballot": { - "items": { - "type": "number" - }, - "type": "array" - }, - "ballots": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "candidate": { - "properties": { - "index": { - "type": "number" - }, - "name": { - "type": "string" - }, - "tieBreakOrder": { - "type": "number" - } - }, - "type": "object" - }, - "fiveStarCount": { - "properties": { - "candidate": { - "$ref": "#/components/schemas/candidate" - }, - "counts": { - "type": "number" - } - }, - "type": "object" - }, - "genericResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/genericSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "genericSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "irvResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "exhaustedVoteCounts": { - "items": { - "type": "number" - }, - "type": "array" - }, - "logs": { - "items": { - "type": "string" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "overVoteCounts": { - "items": { - "type": "number" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/irvRoundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/rankedRobinSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "voteCounts": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - } - }, - "type": "object" - }, - "irvRoundResults": { - "properties": { - "eliminated": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "logs": { - "items": { - "type": "string" - }, - "type": "array" - }, - "winners": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "irvSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "permission": { - "items": { - "$ref": "#/components/schemas/roles" - }, - "type": "array" - }, - "pluralityResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/pluralitySummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "pluralitySummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankedRobinResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/rankedRobinSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "rankedRobinSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "rankHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "registration_field": { - "properties": { - "field_name": { - "type": "string" - }, - "field_type": { - "enum": [ - "photo", - "text" - ], - "type": "string" - }, - "help_text": { - "type": "string" - } - }, - "type": "object" - }, - "roles": { - "enum": [ - "owner", - "admin", - "auditor", - "system_admin", - "credentialer" - ], - "type": "string" - }, - "roundResults": { - "properties": { - "logs": { - "items": { - "$ref": "#/components/schemas/tabulatorLog" - }, - "type": "array" - }, - "runner_up": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "winners": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "score": { - "type": "number" - }, - "scoreHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "starResults": { - "properties": { - "elected": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "other": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "roundResults": { - "items": { - "$ref": "#/components/schemas/roundResults" - }, - "type": "array" - }, - "summaryData": { - "$ref": "#/components/schemas/starSummaryData" - }, - "tieBreakType": { - "$ref": "#/components/schemas/tieBreakType" - }, - "tied": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - } - }, - "type": "object" - }, - "starSummaryData": { - "properties": { - "candidates": { - "items": { - "$ref": "#/components/schemas/candidate" - }, - "type": "array" - }, - "nBulletVotes": { - "type": "number" - }, - "nInvalidVotes": { - "type": "number" - }, - "nUnderVotes": { - "type": "number" - }, - "nValidVotes": { - "type": "number" - }, - "noPreferenceStars": { - "items": { - "type": "number" - }, - "type": "array" - }, - "pairwiseMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "preferenceMatrix": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "scoreHist": { - "items": { - "items": { - "type": "number" - }, - "type": "array" - }, - "type": "array" - }, - "totalScores": { - "items": { - "$ref": "#/components/schemas/totalScore" - }, - "type": "array" - } - }, - "type": "object" - }, - "tabulatorLog": { - "anyOf": [ - { - "$ref": "#/components/schemas/tabulatorLogObject" - }, - { - "type": "string" - } - ] - }, - "tabulatorLogObject": { - "additionalProperties": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": [ - "string", - "number" - ] - } - ] - }, - "properties": { - "key": { - "type": "string" - } - }, - "type": "object" - }, - "tieBreakType": { - "enum": [ - "five_star", - "none", - "random", - "score" - ], - "type": "string" - }, - "totalScore": { - "properties": { - "index": { - "type": "number" - }, - "score": { - "type": "number" - } - }, - "type": "object" - }, - "voter": { - "properties": { - "csvRow": { - "type": "number" - } - }, - "type": "object" - } - } - } -} \ No newline at end of file From 7a018607e125ebb96525bd5bbd6d18ef01754b57 Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Tue, 24 Sep 2024 17:23:43 -0600 Subject: [PATCH 10/11] cleaning up a few errors --- .../src/OpenApi/generateSwaggerJSON.ts | 8 +- packages/backend/src/OpenApi/swagger.json | 3498 +++++++++++++++++ packages/backend/src/Routes/ballot.routes.ts | 2 +- .../backend/src/Routes/elections.routes.ts | 37 +- packages/backend/src/app.ts | 4 +- 5 files changed, 3535 insertions(+), 14 deletions(-) create mode 100644 packages/backend/src/OpenApi/swagger.json diff --git a/packages/backend/src/OpenApi/generateSwaggerJSON.ts b/packages/backend/src/OpenApi/generateSwaggerJSON.ts index 4f73b837..e5ff3e99 100644 --- a/packages/backend/src/OpenApi/generateSwaggerJSON.ts +++ b/packages/backend/src/OpenApi/generateSwaggerJSON.ts @@ -1,9 +1,3 @@ import fs from 'fs'; import openapiSpecification from './swaggerSpec'; -const dir = './build/src/OpenApi'; -if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); -} - -fs.writeFileSync(`${dir}/swagger.json`, JSON.stringify(openapiSpecification, null, 2)); -fs.writeFileSync('./build/src/OpenApi/swagger.json', JSON.stringify(openapiSpecification, null, 2)); +fs.writeFileSync('./src/OpenApi/swagger.json', JSON.stringify(openapiSpecification, null, 2)); diff --git a/packages/backend/src/OpenApi/swagger.json b/packages/backend/src/OpenApi/swagger.json new file mode 100644 index 00000000..b940d049 --- /dev/null +++ b/packages/backend/src/OpenApi/swagger.json @@ -0,0 +1,3498 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "API Documentation", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/API", + "description": "Base URL for the API" + } + ], + "components": { + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "cookie", + "name": "id_token", + "description": "This uses authenticaion token generated with keycloak, the token can be sent both as a cookie or in the auth_key property of the election object. [More information about token generation can be found here](https://github.com/Equal-Vote/star-server/blob/6fdb2f653266b5f2710f0701509f54e1e558ecc1/docs/api.md) Unfortunately, the swagger-ui does not support setting cookies, so you will need to manually set the cookie in your browser to test the API" + } + }, + "schemas": { + "Ballot": { + "properties": { + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "date_submitted": { + "type": "number" + }, + "election_id": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/BallotAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "status": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "user_id": { + "type": "string" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/Vote" + }, + "type": "array" + } + }, + "required": [ + "ballot_id", + "create_date", + "date_submitted", + "election_id", + "head", + "status", + "update_date", + "votes" + ], + "type": "object" + }, + "BallotAction": { + "properties": { + "action_type": { + "type": "string" + }, + "actor": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + }, + "required": [ + "action_type", + "actor", + "timestamp" + ], + "type": "object" + }, + "Candidate": { + "properties": { + "bio": { + "type": "string" + }, + "candidate_id": { + "type": "string" + }, + "candidate_name": { + "type": "string" + }, + "candidate_url": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "party": { + "type": "string" + }, + "partyUrl": { + "type": "string" + }, + "party_url": { + "type": "string" + }, + "photo_filename": { + "type": "string" + } + }, + "required": [ + "candidate_id", + "candidate_name" + ], + "type": "object" + }, + "Credentials": { + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password" + ], + "type": "object" + }, + "Election": { + "properties": { + "admin_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "audit_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_key": { + "type": "string" + }, + "claim_key_hash": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "credential_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "election_id": { + "type": "string" + }, + "end_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "frontend_url": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "owner_id": { + "type": "string" + }, + "races": { + "items": { + "$ref": "#/components/schemas/Race" + }, + "type": "array" + }, + "settings": { + "$ref": "#/components/schemas/ElectionSettings" + }, + "start_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "state": { + "$ref": "#/components/schemas/ElectionState" + }, + "support_email": { + "type": "string" + }, + "title": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "required": [ + "create_date", + "election_id", + "frontend_url", + "head", + "owner_id", + "races", + "settings", + "state", + "title", + "update_date" + ], + "type": "object" + }, + "ElectionResults": { + "anyOf": [ + { + "properties": { + "results": { + "$ref": "#/components/schemas/starResults" + }, + "votingMethod": { + "const": "STAR", + "type": "string" + } + }, + "required": [ + "results", + "votingMethod" + ], + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/allocatedScoreResults" + }, + "votingMethod": { + "const": "STAR_PR", + "type": "string" + } + }, + "required": [ + "results", + "votingMethod" + ], + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/approvalResults" + }, + "votingMethod": { + "const": "Approval", + "type": "string" + } + }, + "required": [ + "results", + "votingMethod" + ], + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/rankedRobinResults" + }, + "votingMethod": { + "const": "RankedRobin", + "type": "string" + } + }, + "required": [ + "results", + "votingMethod" + ], + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/irvResults" + }, + "votingMethod": { + "const": "IRV", + "type": "string" + } + }, + "required": [ + "results", + "votingMethod" + ], + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/pluralityResults" + }, + "votingMethod": { + "const": "Plurality", + "type": "string" + } + }, + "required": [ + "results", + "votingMethod" + ], + "type": "object" + }, + { + "properties": { + "results": { + "$ref": "#/components/schemas/irvResults" + }, + "votingMethod": { + "const": "STV", + "type": "string" + } + }, + "required": [ + "results", + "votingMethod" + ], + "type": "object" + } + ] + }, + "ElectionRoll": { + "properties": { + "address": { + "type": "string" + }, + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "election_id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "email_data": { + "properties": { + "inviteResponse": {}, + "reminderResponse": {} + }, + "type": "object" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/ElectionRollAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "registration": {}, + "state": { + "$ref": "#/components/schemas/ElectionRollState" + }, + "submitted": { + "type": "boolean" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "voter_id": { + "type": "string" + } + }, + "required": [ + "create_date", + "election_id", + "head", + "state", + "submitted", + "update_date", + "voter_id" + ], + "type": "object" + }, + "ElectionRollAction": { + "properties": { + "action_type": { + "type": "string" + }, + "actor": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + }, + "required": [ + "action_type", + "actor", + "timestamp" + ], + "type": "object" + }, + "ElectionRollState": { + "enum": [ + "approved", + "flagged", + "registered", + "invalid" + ], + "type": "string" + }, + "ElectionSettings": { + "properties": { + "ballot_updates": { + "type": "boolean" + }, + "break_ties_randomly": { + "type": "boolean" + }, + "invitation": { + "enum": [ + "address", + "email" + ], + "type": "string" + }, + "max_rankings": { + "type": "number" + }, + "public_results": { + "type": "boolean" + }, + "random_candidate_order": { + "type": "boolean" + }, + "reminders": { + "type": "boolean" + }, + "require_instruction_confirmation": { + "type": "boolean" + }, + "term_type": { + "enum": [ + "election", + "poll" + ], + "type": "string" + }, + "time_zone": { + "type": "string" + }, + "voter_access": { + "enum": [ + "closed", + "open", + "registration" + ], + "type": "string" + }, + "voter_authentication": { + "$ref": "#/components/schemas/authentication" + } + }, + "required": [ + "voter_authentication" + ], + "type": "object" + }, + "ElectionState": { + "enum": [ + "archived", + "closed", + "draft", + "finalized", + "open" + ], + "type": "string" + }, + "Email": { + "type": "string" + }, + "NewBallot": { + "properties": { + "ballot_id": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "date_submitted": { + "type": "number" + }, + "election_id": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "history": { + "items": { + "$ref": "#/components/schemas/BallotAction" + }, + "type": "array" + }, + "ip_hash": { + "type": "string" + }, + "precinct": { + "type": "string" + }, + "status": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "user_id": { + "type": "string" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/Vote" + }, + "type": "array" + } + }, + "required": [ + "date_submitted", + "election_id", + "status", + "votes" + ], + "type": "object" + }, + "NewElection": { + "properties": { + "admin_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "audit_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "auth_key": { + "type": "string" + }, + "claim_key_hash": { + "type": "string" + }, + "create_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "credential_ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "election_id": { + "type": "string" + }, + "end_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "frontend_url": { + "type": "string" + }, + "head": { + "type": "boolean" + }, + "is_public": { + "type": "boolean" + }, + "owner_id": { + "type": "string" + }, + "races": { + "items": { + "$ref": "#/components/schemas/Race" + }, + "type": "array" + }, + "settings": { + "$ref": "#/components/schemas/ElectionSettings" + }, + "start_time": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "state": { + "$ref": "#/components/schemas/ElectionState" + }, + "support_email": { + "type": "string" + }, + "title": { + "type": "string" + }, + "update_date": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "required": [ + "frontend_url", + "owner_id", + "races", + "settings", + "state", + "title" + ], + "type": "object" + }, + "NewPassword": { + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + }, + "required": [ + "newPassword", + "oldPassword" + ], + "type": "object" + }, + "Omit": { + "type": "object" + }, + "Omit_1": { + "type": "object" + }, + "Partial>": { + "type": "object" + }, + "PartialBy": { + "allOf": [ + { + "$ref": "#/components/schemas/Omit_1" + }, + { + "$ref": "#/components/schemas/Partial>" + } + ] + }, + "Race": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/Candidate" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "num_winners": { + "type": "number" + }, + "precincts": { + "items": { + "additionalProperties": false, + "patternProperties": { + "^[0-9]+$": { + "type": "string" + } + }, + "properties": { + "length": { + "type": "number" + } + }, + "required": [ + "length" + ], + "type": "object" + }, + "type": "array" + }, + "race_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "voting_method": { + "$ref": "#/components/schemas/VotingMethod" + } + }, + "required": [ + "candidates", + "num_winners", + "race_id", + "title", + "voting_method" + ], + "type": "object" + }, + "Score": { + "properties": { + "candidate_id": { + "type": "string" + }, + "score": { + "type": "number" + } + }, + "required": [ + "candidate_id", + "score" + ], + "type": "object" + }, + "TermType": { + "enum": [ + "election", + "poll" + ], + "type": "string" + }, + "Uid": { + "type": "string" + }, + "Vote": { + "properties": { + "race_id": { + "type": "string" + }, + "scores": { + "items": { + "$ref": "#/components/schemas/Score" + }, + "type": "array" + } + }, + "required": [ + "race_id", + "scores" + ], + "type": "object" + }, + "VoterAuth": { + "properties": { + "authorized_voter": { + "type": "boolean" + }, + "has_voted": { + "type": "boolean" + }, + "permissions": { + "items": { + "enum": [ + "canAddToElectionRoll", + "canApproveElectionRoll", + "canDeleteAllBallots", + "canDeleteElection", + "canDeleteElectionRoll", + "canEditBallot", + "canEditElection", + "canEditElectionRoles", + "canEditElectionRoll", + "canEditElectionState", + "canFlagBallot", + "canFlagElectionRoll", + "canInvalidateBallot", + "canInvalidateElectionRoll", + "canSendEmails", + "canUnflagElectionRoll", + "canViewBallot", + "canViewBallots", + "canViewElection", + "canViewElectionRoll", + "canViewElectionRollIDs", + "canViewPreliminaryResults" + ], + "type": "string" + }, + "type": "array" + }, + "required": { + "type": "string" + }, + "roles": { + "items": { + "$ref": "#/components/schemas/roles" + }, + "type": "array" + } + }, + "required": [ + "authorized_voter", + "has_voted", + "permissions", + "required", + "roles" + ], + "type": "object" + }, + "VotingMethod": { + "enum": [ + "Approval", + "IRV", + "Plurality", + "RankedRobin", + "STAR", + "STAR_PR", + "STV" + ], + "type": "string" + }, + "allocatedScoreResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/allocatedScoreSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "type": "array" + } + }, + "required": [ + "elected", + "other", + "roundResults", + "summaryData", + "tieBreakType", + "tied" + ], + "type": "object" + }, + "allocatedScoreSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "noPreferenceStars": { + "items": { + "type": "number" + }, + "type": "array" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "spentAboves": { + "items": { + "type": "number" + }, + "type": "array" + }, + "splitPoints": { + "items": { + "type": "number" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + }, + "weight_on_splits": { + "items": { + "type": "number" + }, + "type": "array" + }, + "weightedScoresByRound": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + } + }, + "required": [ + "candidates", + "nInvalidVotes", + "nUnderVotes", + "nValidVotes", + "noPreferenceStars", + "pairwiseMatrix", + "preferenceMatrix", + "scoreHist", + "spentAboves", + "splitPoints", + "totalScores", + "weight_on_splits", + "weightedScoresByRound" + ], + "type": "object" + }, + "approvalResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/approvalSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "required": [ + "elected", + "other", + "roundResults", + "summaryData", + "tieBreakType", + "tied" + ], + "type": "object" + }, + "approvalSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "required": [ + "candidates", + "nInvalidVotes", + "nUnderVotes", + "nValidVotes", + "totalScores" + ], + "type": "object" + }, + "authentication": { + "properties": { + "address": { + "type": "boolean" + }, + "email": { + "type": "boolean" + }, + "ip_address": { + "type": "boolean" + }, + "phone": { + "type": "boolean" + }, + "registration_api_endpoint": { + "type": "string" + }, + "registration_data": { + "items": [ + { + "$ref": "#/components/schemas/registration_field" + } + ], + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + "voter_id": { + "type": "boolean" + } + }, + "type": "object" + }, + "ballot": { + "items": { + "type": "number" + }, + "type": "array" + }, + "ballots": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "candidate": { + "properties": { + "index": { + "type": "number" + }, + "name": { + "type": "string" + }, + "tieBreakOrder": { + "type": "number" + } + }, + "required": [ + "index", + "name", + "tieBreakOrder" + ], + "type": "object" + }, + "fiveStarCount": { + "properties": { + "candidate": { + "$ref": "#/components/schemas/candidate" + }, + "counts": { + "type": "number" + } + }, + "required": [ + "candidate", + "counts" + ], + "type": "object" + }, + "genericResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/genericSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "required": [ + "elected", + "other", + "roundResults", + "summaryData", + "tieBreakType", + "tied" + ], + "type": "object" + }, + "genericSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "required": [ + "candidates", + "nInvalidVotes", + "nUnderVotes", + "nValidVotes", + "totalScores" + ], + "type": "object" + }, + "irvResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "exhaustedVoteCounts": { + "items": { + "type": "number" + }, + "type": "array" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "overVoteCounts": { + "items": { + "type": "number" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/irvRoundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/rankedRobinSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "voteCounts": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + } + }, + "required": [ + "elected", + "exhaustedVoteCounts", + "logs", + "other", + "overVoteCounts", + "roundResults", + "summaryData", + "tieBreakType", + "tied", + "voteCounts" + ], + "type": "object" + }, + "irvRoundResults": { + "properties": { + "eliminated": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "logs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "winners": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "required": [ + "eliminated", + "logs", + "winners" + ], + "type": "object" + }, + "irvSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "required": [ + "candidates", + "nInvalidVotes", + "nUnderVotes", + "nValidVotes", + "pairwiseMatrix", + "preferenceMatrix", + "rankHist", + "totalScores" + ], + "type": "object" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "permission": { + "items": { + "$ref": "#/components/schemas/roles" + }, + "type": "array" + }, + "pluralityResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/pluralitySummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "required": [ + "elected", + "other", + "roundResults", + "summaryData", + "tieBreakType", + "tied" + ], + "type": "object" + }, + "pluralitySummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "required": [ + "candidates", + "nInvalidVotes", + "nUnderVotes", + "nValidVotes", + "totalScores" + ], + "type": "object" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankedRobinResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/rankedRobinSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "required": [ + "elected", + "other", + "roundResults", + "summaryData", + "tieBreakType", + "tied" + ], + "type": "object" + }, + "rankedRobinSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "rankHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "required": [ + "candidates", + "nInvalidVotes", + "nUnderVotes", + "nValidVotes", + "pairwiseMatrix", + "preferenceMatrix", + "rankHist", + "totalScores" + ], + "type": "object" + }, + "registration_field": { + "properties": { + "field_name": { + "type": "string" + }, + "field_type": { + "enum": [ + "photo", + "text" + ], + "type": "string" + }, + "help_text": { + "type": "string" + } + }, + "required": [ + "field_name", + "field_type" + ], + "type": "object" + }, + "roles": { + "enum": [ + "owner", + "admin", + "auditor", + "system_admin", + "credentialer" + ], + "type": "string" + }, + "roundResults": { + "properties": { + "logs": { + "items": { + "$ref": "#/components/schemas/tabulatorLog" + }, + "type": "array" + }, + "runner_up": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "winners": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "required": [ + "logs", + "runner_up", + "tieBreakType", + "tied", + "winners" + ], + "type": "object" + }, + "score": { + "type": "number" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "starResults": { + "properties": { + "elected": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "other": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "roundResults": { + "items": { + "$ref": "#/components/schemas/roundResults" + }, + "type": "array" + }, + "summaryData": { + "$ref": "#/components/schemas/starSummaryData" + }, + "tieBreakType": { + "$ref": "#/components/schemas/tieBreakType" + }, + "tied": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + } + }, + "required": [ + "elected", + "other", + "roundResults", + "summaryData", + "tieBreakType", + "tied" + ], + "type": "object" + }, + "starSummaryData": { + "properties": { + "candidates": { + "items": { + "$ref": "#/components/schemas/candidate" + }, + "type": "array" + }, + "nBulletVotes": { + "type": "number" + }, + "nInvalidVotes": { + "type": "number" + }, + "nUnderVotes": { + "type": "number" + }, + "nValidVotes": { + "type": "number" + }, + "noPreferenceStars": { + "items": { + "type": "number" + }, + "type": "array" + }, + "pairwiseMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "preferenceMatrix": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "scoreHist": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "type": "array" + }, + "totalScores": { + "items": { + "$ref": "#/components/schemas/totalScore" + }, + "type": "array" + } + }, + "required": [ + "candidates", + "nInvalidVotes", + "nUnderVotes", + "nValidVotes", + "noPreferenceStars", + "pairwiseMatrix", + "preferenceMatrix", + "scoreHist", + "totalScores" + ], + "type": "object" + }, + "tabulatorLog": { + "anyOf": [ + { + "$ref": "#/components/schemas/tabulatorLogObject" + }, + { + "type": "string" + } + ] + }, + "tabulatorLogObject": { + "additionalProperties": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": [ + "string", + "number" + ] + } + ] + }, + "properties": { + "key": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "tieBreakType": { + "enum": [ + "five_star", + "none", + "random", + "score" + ], + "type": "string" + }, + "totalScore": { + "properties": { + "index": { + "type": "number" + }, + "score": { + "type": "number" + } + }, + "required": [ + "index", + "score" + ], + "type": "object" + }, + "voter": { + "properties": { + "csvRow": { + "type": "number" + } + }, + "required": [ + "csvRow" + ], + "type": "object" + } + } + }, + "paths": { + "/Election/{id}/ballots": { + "get": { + "summary": "Get ballots by election ID", + "tags": [ + "Ballots" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "List of ballots", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "ballots": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Ballot" + } + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + }, + "delete": { + "summary": "Delete all ballots for an election", + "tags": [ + "Ballots" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "public_results": { + "type": "boolean", + "description": "If the election results are public" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "All ballots deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "If the deletion was successful" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/ballot/{ballot_id}": { + "get": { + "summary": "Get ballot by ballot ID", + "tags": [ + "Ballots" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + }, + { + "in": "path", + "name": "ballot_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The ballot ID" + } + ], + "responses": { + "200": { + "description": "Ballot details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballot": { + "type": "object", + "$ref": "#/components/schemas/Ballot" + } + } + } + } + } + }, + "404": { + "description": "Ballot not found" + } + } + } + }, + "/Election/{id}/vote": { + "post": { + "summary": "Cast a vote in an election", + "tags": [ + "Ballots" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballot": { + "type": "object", + "$ref": "#/components/schemas/Ballot" + }, + "recieptEmail": { + "oneOf": [ + { + "type": "string" + }, + { + "type": null + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Vote cast", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballot": { + "type": "object", + "$ref": "#/components/schemas/NewBallot" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}": { + "get": { + "summary": "Get election by ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "The election description by ID", + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + }, + "404": { + "description": "Election not found" + } + } + }, + "delete": { + "summary": "Delete election by ID", + "tags": [ + "Elections" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election deleted" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{_id}/exists": { + "get": { + "summary": "Check if election exists by ID", + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election exists", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "exists": { + "type": "boolean" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Elections": { + "get": { + "summary": "Get all elections", + "tags": [ + "Elections" + ], + "responses": { + "200": { + "description": "List of elections", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "elections_as_official": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + } + ] + }, + "elections_as_unsubmitted_voter": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + }, + { + "type": "undefined" + } + ] + }, + "elections_as_submitted_voter": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + } + ] + }, + "open_elections": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Election" + } + }, + { + "type": null + } + ] + } + } + } + } + } + } + } + }, + "post": { + "summary": "Create a new election", + "tags": [ + "Elections" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Election": { + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Election created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/NewElection" + } + } + } + } + } + } + } + } + }, + "/GlobalElectionStats": { + "get": { + "summary": "Get global election statistics", + "tags": [ + "Elections" + ], + "responses": { + "200": { + "description": "Global election statistics", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "elections": { + "type": "number", + "description": "Number of elections" + }, + "votes": { + "type": "number", + "description": "Number of votes" + } + } + } + } + } + } + } + } + }, + "/Election/{id}/edit": { + "post": { + "summary": "Edit an election", + "tags": [ + "Elections" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Election": { + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Election edited", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "voterAuth": { + "type": "object", + "properties": { + "authorized_voter": { + "type": "boolean" + }, + "has_voted": { + "type": "boolean" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/roles" + } + }, + "permissions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/permissions" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/roles": { + "put": { + "summary": "Edit election roles", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "admin_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "audit_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "credential_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Roles edited", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/ElectionResult/{id}": { + "get": { + "summary": "Get election results by ID", + "tags": [ + "Elections" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "results": { + "type": "object", + "$ref": "#/components/schemas/ElectionResults" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/finalize": { + "post": { + "summary": "Finalize an election", + "tags": [ + "Elections" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election finalized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/setPublicResults": { + "post": { + "summary": "Set public results for an election", + "tags": [ + "Elections" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "public_results": { + "type": "boolean" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Public results set", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/archive": { + "post": { + "summary": "Archive an election", + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "Elections" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Election archived", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/sendInvites": { + "post": { + "summary": "Send invitations for an election", + "tags": [ + "Elections" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Invitations sent" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/sendInvite/{voter_id}": { + "post": { + "summary": "Send an invitation to a specific voter", + "tags": [ + "Elections" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + }, + { + "in": "path", + "name": "voter_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The voter ID" + } + ], + "responses": { + "200": { + "description": "Invitation sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election or voter not found" + } + } + } + }, + "/Sandbox": { + "post": { + "summary": "Get sandbox results", + "tags": [ + "Elections" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cvr": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "candidates": { + "type": "array", + "items": { + "type": "string" + } + }, + "num_winners": { + "type": "number" + }, + "votingMethod": { + "type": "object", + "$ref": "#/components/schemas/VotingMethod" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Sandbox results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "object", + "$ref": "#/components/schemas/ElectionResults" + }, + "nWinners": { + "type": "number", + "description": "Number of winners" + }, + "candidates": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/images": { + "post": { + "summary": "Upload an image", + "tags": [ + "Elections" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Image uploaded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "photo_filename": { + "oneOf": [ + "string", + null + ] + } + } + } + } + } + } + } + } + }, + "/Election/{id}/register": { + "post": { + "summary": "Register a voter for an election", + "tags": [ + "Rolls" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Voter registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "NewElectionRoll": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls": { + "get": { + "summary": "Get rolls by election ID", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "List of rolls", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + }, + "post": { + "summary": "Add a list of rolls to an election", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRoll": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Rolls added", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "election": { + "type": "object", + "$ref": "#/components/schemas/Election" + }, + "NewElectionRoll": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + }, + "put": { + "summary": "Edit an election roll", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "Roll edited", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/{voter_id}": { + "get": { + "summary": "Get roll by voter ID", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + }, + { + "in": "path", + "name": "voter_id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The voter ID" + } + ], + "responses": { + "200": { + "description": "Roll details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "404": { + "description": "Roll not found" + } + } + } + }, + "/Election/{id}/rolls/approve": { + "post": { + "summary": "Approve an election roll", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Roll approved" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/flag": { + "post": { + "summary": "Flag an election roll", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Roll flagged" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/invalidate": { + "post": { + "summary": "Invalidate an election roll", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Roll invalidated" + }, + "404": { + "description": "Election not found" + } + } + } + }, + "/Election/{id}/rolls/unflag": { + "post": { + "summary": "Unflag an election roll", + "tags": [ + "Rolls" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "electionRollEntry": { + "type": "object", + "$ref": "#/components/schemas/ElectionRoll" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Roll unflagged" + }, + "404": { + "description": "Election not found" + } + } + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/backend/src/Routes/ballot.routes.ts b/packages/backend/src/Routes/ballot.routes.ts index efdbf6a1..b6292dc6 100644 --- a/packages/backend/src/Routes/ballot.routes.ts +++ b/packages/backend/src/Routes/ballot.routes.ts @@ -170,7 +170,7 @@ ballotRouter.get('/Election/:id/ballot/:ballot_id', asyncHandler(getBallotByBall * properties: * ballot: * type: object - * $ref: '#/components/schemas/Ballot' + * $ref: '#/components/schemas/NewBallot' * 404: * description: Election not found */ ballotRouter.post('/Election/:id/vote', asyncHandler(castVoteController)) diff --git a/packages/backend/src/Routes/elections.routes.ts b/packages/backend/src/Routes/elections.routes.ts index 19cef62d..0dbdae46 100644 --- a/packages/backend/src/Routes/elections.routes.ts +++ b/packages/backend/src/Routes/elections.routes.ts @@ -280,13 +280,16 @@ electionsRouter.delete('/Election/:id', asyncHandler(deleteElection)) * properties: * admin_ids: * type: array - * items: string + * items: + * type: string * audit_ids: * type: array - * items: string + * items: + * type: string * credential_ids: * type: array - * items: string + * items: + * type: string * responses: * 200: * description: Roles edited @@ -499,6 +502,29 @@ electionsRouter.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvi * post: * summary: Get sandbox results * tags: [Elections] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * cvr: + * type: array + * items: + * type: array + * items: + * type: "number" + * candidates: + * type: array + * items: + * type: string + * num_winners: + * type: number + * votingMethod: + * type: object + * $ref: '#/components/schemas/VotingMethod' + * * responses: * 200: * description: Sandbox results @@ -515,7 +541,10 @@ electionsRouter.post('/Election/:id/sendInvite/:voter_id', asyncHandler(sendInvi * description: Number of winners * candidates: * type: array - * items: string */ + * items: + * type: string + * + */ electionsRouter.post('/Sandbox',asyncHandler(getSandboxResults)) /** diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 02a449f6..142c0e30 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -14,7 +14,7 @@ import registerEvents from './Routes/registerEvents'; import { setupSockets } from './socketHandler'; import { getMetaTags } from './Util'; import swaggerUi from 'swagger-ui-express'; -import openapi from '../build/src/OpenApi/swagger.json'; +import swagger from './OpenApi/swagger.json'; import { getUserToken, getUser } from './Controllers/User'; const asyncHandler = require('express-async-handler') @@ -49,7 +49,7 @@ export default function makeApp() { app.use('/API', getUser, ballotRouter) app.use('/API', getUser, rollRouter) // app.use('/debug',debugRouter) - app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapi)); + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swagger)); app.post('/API/Token', asyncHandler(getUserToken)); // NOTE: I've removed express.static because it doesn't allow me to inject meta tags From 4c57d720b65991065c190d29daa8f9134b1f178e Mon Sep 17 00:00:00 2001 From: Samuel Coleman Date: Tue, 24 Sep 2024 20:23:00 -0600 Subject: [PATCH 11/11] document anonymized ballots --- ...AnonymizedBallotsByElectionIDController.ts | 13 ++--- .../backend/src/Controllers/Ballot/index.ts | 3 +- packages/backend/src/OpenApi/swagger.json | 57 +++++++++++++++++++ packages/backend/src/Routes/ballot.routes.ts | 30 ++++++++++ 4 files changed, 94 insertions(+), 9 deletions(-) rename packages/backend/src/Controllers/{ => Ballot}/getAnonymizedBallotsByElectionIDController.ts (75%) diff --git a/packages/backend/src/Controllers/getAnonymizedBallotsByElectionIDController.ts b/packages/backend/src/Controllers/Ballot/getAnonymizedBallotsByElectionIDController.ts similarity index 75% rename from packages/backend/src/Controllers/getAnonymizedBallotsByElectionIDController.ts rename to packages/backend/src/Controllers/Ballot/getAnonymizedBallotsByElectionIDController.ts index 6803aa8a..50792c7c 100644 --- a/packages/backend/src/Controllers/getAnonymizedBallotsByElectionIDController.ts +++ b/packages/backend/src/Controllers/Ballot/getAnonymizedBallotsByElectionIDController.ts @@ -1,16 +1,16 @@ -import ServiceLocator from "../ServiceLocator"; -import Logger from "../Services/Logging/Logger"; +import ServiceLocator from "../../ServiceLocator"; +import Logger from "../../Services/Logging/Logger"; import { BadRequest } from "@curveball/http-errors"; -import { expectPermission } from "./controllerUtils"; +import { expectPermission } from "../controllerUtils"; import { permissions } from '@equal-vote/star-vote-shared/domain_model/permissions'; -import { IElectionRequest } from "../IRequest"; +import { IElectionRequest } from "../../IRequest"; import { Response, NextFunction } from 'express'; import { AnonymizedBallot } from "@equal-vote/star-vote-shared/domain_model/Ballot"; const BallotModel = ServiceLocator.ballotsDb(); -const getAnonymizedBallotsByElectionID = async (req: IElectionRequest, res: Response, next: NextFunction) => { +export const getAnonymizedBallotsByElectionID = async (req: IElectionRequest, res: Response, next: NextFunction) => { var electionId = req.election.election_id; Logger.debug(req, "getBallotsByElectionID: " + electionId); const election = req.election; @@ -34,6 +34,3 @@ const getAnonymizedBallotsByElectionID = async (req: IElectionRequest, res: Resp res.json({ ballots: anonymizedBallots }) } -module.exports = { - getAnonymizedBallotsByElectionID -} diff --git a/packages/backend/src/Controllers/Ballot/index.ts b/packages/backend/src/Controllers/Ballot/index.ts index c36fb85d..f30ad321 100644 --- a/packages/backend/src/Controllers/Ballot/index.ts +++ b/packages/backend/src/Controllers/Ballot/index.ts @@ -2,4 +2,5 @@ export * from './ballots.controllers'; export * from './castVoteController'; export * from './deleteAllBallotsForElectionIDController'; export * from './getBallotByBallotIDController'; -export * from './getBallotsByElectionIDController'; \ No newline at end of file +export * from './getBallotsByElectionIDController'; +export * from './getAnonymizedBallotsByElectionIDController'; \ No newline at end of file diff --git a/packages/backend/src/OpenApi/swagger.json b/packages/backend/src/OpenApi/swagger.json index b940d049..0195e44f 100644 --- a/packages/backend/src/OpenApi/swagger.json +++ b/packages/backend/src/OpenApi/swagger.json @@ -20,6 +20,24 @@ } }, "schemas": { + "AnonymizedBallot": { + "properties": { + "ballot_id": { + "type": "string" + }, + "votes": { + "items": { + "$ref": "#/components/schemas/Vote" + }, + "type": "array" + } + }, + "required": [ + "ballot_id", + "votes" + ], + "type": "object" + }, "Ballot": { "properties": { "ballot_id": { @@ -2126,6 +2144,45 @@ } } }, + "/Election/{id}/anonymizedBallots": { + "get": { + "summary": "Get anonymized ballots by election ID", + "tags": [ + "Ballots" + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The election ID" + } + ], + "responses": { + "200": { + "description": "List of anonymized ballots", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ballots": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnonymizedBallot" + } + } + } + } + } + } + } + } + } + }, "/Election/{id}/ballot/{ballot_id}": { "get": { "summary": "Get ballot by ballot ID", diff --git a/packages/backend/src/Routes/ballot.routes.ts b/packages/backend/src/Routes/ballot.routes.ts index b6292dc6..4064b90e 100644 --- a/packages/backend/src/Routes/ballot.routes.ts +++ b/packages/backend/src/Routes/ballot.routes.ts @@ -3,6 +3,7 @@ import { deleteAllBallotsForElectionID, getBallotByBallotID, castVoteController, + getAnonymizedBallotsByElectionID } from '../Controllers/Ballot'; import { @@ -53,6 +54,35 @@ export const ballotRouter = Router(); */ ballotRouter.get('/Election/:id/ballots', asyncHandler(getBallotsByElectionID)) +/** + * @swagger + * /Election/{id}/anonymizedBallots: + * get: + * summary: Get anonymized ballots by election ID + * tags: [Ballots] + * parameters: + * - in: path + * name: id + * schema: + * type: string + * required: true + * description: The election ID + * responses: + * 200: + * description: List of anonymized ballots + * content: + * application/json: + * schema: + * type: object + * properties: + * ballots: + * type: array + * items: + * $ref: '#/components/schemas/AnonymizedBallot' + * + * + */ +ballotRouter.get('/Election/:id/anonymizedBallots', asyncHandler(getAnonymizedBallotsByElectionID)) /** * @swagger