diff --git a/.gitignore b/.gitignore index ceaea36..a7fe266 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ dist .yarn/install-state.gz .pnp.* +export \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 187b61a..6eb54c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "member-administration-server", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "member-administration-server", - "version": "0.0.2", + "version": "0.0.3", "license": "GPL-3.0-only", "dependencies": { "cors": "^2.8.5", @@ -19,6 +19,7 @@ "mysql": "^2.18.1", "node-schedule": "^2.1.1", "nodemailer": "^6.9.14", + "pdf-creator-node": "^2.3.5", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "socket.io": "^4.7.5", @@ -406,6 +407,23 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -452,6 +470,50 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "optional": true + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT", + "optional": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -490,6 +552,16 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/better-sqlite3": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz", @@ -638,12 +710,29 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT", + "optional": true + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -678,6 +767,13 @@ "node": ">=6" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -805,6 +901,35 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -884,6 +1009,19 @@ "node": ">= 8" } }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/dayjs": { "version": "1.11.12", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", @@ -953,6 +1091,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1010,6 +1158,17 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -1123,6 +1282,13 @@ "node": ">= 0.4" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT", + "optional": true + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -1211,6 +1377,76 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "optional": true + }, + "node_modules/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/extract-zip/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT", + "optional": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1262,6 +1498,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1285,6 +1546,18 @@ "optional": true, "peer": true }, + "node_modules/fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1319,6 +1592,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1356,6 +1639,59 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "optional": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1397,6 +1733,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha512-jZ38TU/EBiGKrmyTNNZgnvCZHNowiRI4+w/I9noMlekHTZH3KyGgvJLmhSgykeAQ9j2SYPDosM0Bg3wHfzibAQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1416,6 +1766,22 @@ "node": "*" } }, + "node_modules/html-pdf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-pdf/-/html-pdf-3.0.1.tgz", + "integrity": "sha512-CKNSacmQn+CKJ2GNfT4UYKaPy/T3Ndj82yJ2aju/UPmnvWNjIpyumqRqkFU0mwT6BTHBFhFGTnXN8dBn4Bdj0Q==", + "deprecated": "Please migrate your projects to a newer library like puppeteer", + "license": "MIT", + "bin": { + "html-pdf": "bin/index.js" + }, + "engines": { + "node": ">=4.0.0" + }, + "optionalDependencies": { + "phantomjs-prebuilt": "^2.1.16" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1431,6 +1797,22 @@ "node": ">= 0.8" } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/iconv-lite": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", @@ -1505,6 +1887,23 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT", + "optional": true + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1515,6 +1914,13 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT", + "optional": true + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -1529,6 +1935,44 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT", + "optional": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)", + "optional": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT", + "optional": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC", + "optional": true + }, + "node_modules/jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1551,6 +1995,22 @@ "npm": ">=6" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -1572,6 +2032,23 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha512-IG6nm0+QtAMdXt9KvbgbGdvY50RSrw+U4sGZg+KlrSKPJEwVE5JVoI3d7RWfSMdBQneRheeAOj3lIjX5VL/9RQ==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1720,8 +2197,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "optional": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1832,6 +2307,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/node-abi": { "version": "3.65.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", @@ -1866,6 +2347,16 @@ "node": ">=6.0.0" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2014,6 +2505,89 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" }, + "node_modules/pdf-creator-node": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/pdf-creator-node/-/pdf-creator-node-2.3.5.tgz", + "integrity": "sha512-dUnYiYTGBkC60M1hBrHjlZs+gsSZZvXP33OLDUDOUPhNNhnHXQrzXFC3leo57ujzS/3rpbrqFcXxWpB5fgwkpw==", + "license": "ISC", + "dependencies": { + "handlebars": "^4.7.7", + "html-pdf": "^3.0.1" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT", + "optional": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "node_modules/phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha512-PIiRzBhW85xco2fuj41FmsyuYHKjKuXWmhjy3A/Y+CMpN/63TV+s9uzfVhsUwFe0G77xWtHBG8xmXf5BqEUEuQ==", + "deprecated": "this package is now deprecated", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" + }, + "bin": { + "phantomjs": "bin/phantomjs" + } + }, + "node_modules/phantomjs-prebuilt/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -2054,6 +2628,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/property-expr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", @@ -2072,6 +2655,13 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "license": "MIT", + "optional": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -2083,6 +2673,16 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qrcode": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", @@ -2175,6 +2775,70 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha512-dxdraeZVUNEn9AvLrxkgB2k6buTlym71dJk1fk4v8j3Ou3RKNm07BcgbHdj2lLgYGfqX71F+awb1MR+tWPFJzA==", + "license": "MIT", + "optional": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2533,6 +3197,15 @@ "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/speakeasy": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/speakeasy/-/speakeasy-2.0.0.tgz", @@ -2553,6 +3226,32 @@ "node": ">= 0.6" } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2709,6 +3408,16 @@ "node": ">=0.8" } }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tiny-case": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", @@ -2729,6 +3438,20 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", "license": "MIT" }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/ts-node": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", @@ -2782,7 +3505,6 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "optional": true, - "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -2790,6 +3512,13 @@ "node": "*" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense", + "optional": true + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -2814,6 +3543,13 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT", + "optional": true + }, "node_modules/typeorm": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", @@ -3027,6 +3763,19 @@ "node": ">=4.2.0" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3035,6 +3784,16 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3074,6 +3833,28 @@ "node": ">= 0.8" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT", + "optional": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3093,6 +3874,12 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -3188,6 +3975,17 @@ "node": ">=6" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index c8a1140..4190f17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "member-administration-server", - "version": "0.0.2", + "version": "0.0.3", "description": "Feuerwehr/Verein Mitgliederverwaltung Server", "main": "dist/index.js", "scripts": { @@ -34,6 +34,7 @@ "mysql": "^2.18.1", "node-schedule": "^2.1.1", "nodemailer": "^6.9.14", + "pdf-creator-node": "^2.3.5", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "socket.io": "^4.7.5", diff --git a/src/command/protocolAgendaCommand.ts b/src/command/protocolAgendaCommand.ts new file mode 100644 index 0000000..749390d --- /dev/null +++ b/src/command/protocolAgendaCommand.ts @@ -0,0 +1,6 @@ +export interface SynchronizeProtocolAgendaCommand { + id?: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/command/protocolAgendaCommandHandler.ts b/src/command/protocolAgendaCommandHandler.ts new file mode 100644 index 0000000..91c7dea --- /dev/null +++ b/src/command/protocolAgendaCommandHandler.ts @@ -0,0 +1,49 @@ +import { dataSource } from "../data-source"; +import { protocolAgenda } from "../entity/protocolAgenda"; +import InternalException from "../exceptions/internalException"; +import { SynchronizeProtocolAgendaCommand } from "./protocolAgendaCommand"; + +export default abstract class ProtocolAgendaCommandHandler { + /** + * @description create protocolAgenda + * @param {number} + * @returns {Promise} + */ + static async create(protocolId: number): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolAgenda) + .values({ + topic: "", + context: "", + protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } + + /** + * @description sync protocolAgenda + * @param {Array} + * @returns {Promise} + */ + static async sync(syncProtocolAgenda: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolAgenda) + .values(syncProtocolAgenda) + .orUpdate(["topic", "context"], ["id"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/command/protocolCommand.ts b/src/command/protocolCommand.ts new file mode 100644 index 0000000..42e9ce9 --- /dev/null +++ b/src/command/protocolCommand.ts @@ -0,0 +1,13 @@ +export interface CreateProtocolCommand { + title: string; + date: Date; +} + +export interface SynchronizeProtocolCommand { + id: number; + title: string; + date: Date; + starttime: Date; + endtime: Date; + summary: string; +} diff --git a/src/command/protocolCommandHandler.ts b/src/command/protocolCommandHandler.ts new file mode 100644 index 0000000..f2f293e --- /dev/null +++ b/src/command/protocolCommandHandler.ts @@ -0,0 +1,53 @@ +import { dataSource } from "../data-source"; +import { protocol } from "../entity/protocol"; +import InternalException from "../exceptions/internalException"; +import { CreateProtocolCommand, SynchronizeProtocolCommand } from "./protocolCommand"; + +export default abstract class ProtocolCommandHandler { + /** + * @description create protocol + * @param CreateProtocolCommand + * @returns {Promise} + */ + static async create(createProtocol: CreateProtocolCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocol) + .values({ + title: createProtocol.title, + date: createProtocol.date, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } + + /** + * @description sync protocol + * @param SynchronizeProtocolCommand + * @returns {Promise} + */ + static async sync(syncProtocol: SynchronizeProtocolCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(protocol) + .set({ + title: syncProtocol.title, + date: syncProtocol.date, + starttime: syncProtocol.starttime, + endtime: syncProtocol.endtime, + summary: syncProtocol.summary, + }) + .where("id = :id", { id: syncProtocol.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/command/protocolDecisionCommand.ts b/src/command/protocolDecisionCommand.ts new file mode 100644 index 0000000..61eb6f0 --- /dev/null +++ b/src/command/protocolDecisionCommand.ts @@ -0,0 +1,6 @@ +export interface SynchronizeProtocolDecisionCommand { + id?: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/command/protocolDecisionCommandHandler.ts b/src/command/protocolDecisionCommandHandler.ts new file mode 100644 index 0000000..750a40e --- /dev/null +++ b/src/command/protocolDecisionCommandHandler.ts @@ -0,0 +1,48 @@ +import { dataSource } from "../data-source"; +import { protocolDecision } from "../entity/protocolDecision"; +import InternalException from "../exceptions/internalException"; +import { SynchronizeProtocolDecisionCommand } from "./protocolDecisionCommand"; + +export default abstract class ProtocolDecisionCommandHandler { + /** + * @description create protocolDecision + * @param {number} + * @returns {Promise} + */ + static async create(protocolId: number): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolDecision) + .values({ + topic: "", + context: "", + protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } + /** + * @description sync protocolDecision + * @param {Array} + * @returns {Promise} + */ + static async sync(syncProtocolDecisions: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolDecision) + .values(syncProtocolDecisions) + .orUpdate(["topic", "context"], ["id"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/command/protocolPresenceCommand.ts b/src/command/protocolPresenceCommand.ts new file mode 100644 index 0000000..b39878b --- /dev/null +++ b/src/command/protocolPresenceCommand.ts @@ -0,0 +1,4 @@ +export interface SynchronizeProtocolPresenceCommand { + memberIds: Array; + protocolId: number; +} diff --git a/src/command/protocolPresenceCommandHandler.ts b/src/command/protocolPresenceCommandHandler.ts new file mode 100644 index 0000000..b773cc6 --- /dev/null +++ b/src/command/protocolPresenceCommandHandler.ts @@ -0,0 +1,69 @@ +import { DeleteResult, EntityManager, InsertResult } from "typeorm"; +import { dataSource } from "../data-source"; +import { protocolPresence } from "../entity/protocolPresence"; +import InternalException from "../exceptions/internalException"; +import ProtocolPresenceService from "../service/protocolPrecenseService"; +import { SynchronizeProtocolPresenceCommand } from "./protocolPresenceCommand"; + +export default abstract class ProtocolPresenceCommandHandler { + /** + * @description sync protocolPresence + * @param {SynchronizeProtocolPresenceCommand} + * @returns {Promise} + */ + static async sync(syncProtocolPresences: SynchronizeProtocolPresenceCommand): Promise { + let currentPresence = (await ProtocolPresenceService.getAll(syncProtocolPresences.protocolId)).map( + (r) => r.memberId + ); + + return await dataSource.manager + .transaction(async (manager) => { + let newMembers = syncProtocolPresences.memberIds.filter((r) => !currentPresence.includes(r)); + let removeMembers = currentPresence.filter((r) => !syncProtocolPresences.memberIds.includes(r)); + + if (newMembers.length != 0) { + await this.syncPresenceAdd(manager, syncProtocolPresences.protocolId, newMembers); + } + + if (removeMembers.length != 0) { + await this.syncPresenceRemove(manager, syncProtocolPresences.protocolId, removeMembers); + } + }) + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed saving protocol presence", err); + }); + } + + private static async syncPresenceAdd( + manager: EntityManager, + protocolId: number, + memberIds: Array + ): Promise { + return await manager + .createQueryBuilder() + .insert() + .into(protocolPresence) + .values( + memberIds.map((m) => ({ + protocolId, + memberId: m, + })) + ) + .execute(); + } + + private static async syncPresenceRemove( + manager: EntityManager, + protocolId: number, + memberIds: Array + ): Promise { + return await manager + .createQueryBuilder() + .delete() + .from(protocolPresence) + .where("memberId IN (:...ids)", { ids: memberIds }) + .andWhere("protocolId = :protocolId", { protocolId }) + .execute(); + } +} diff --git a/src/command/protocolPrintoutCommand.ts b/src/command/protocolPrintoutCommand.ts new file mode 100644 index 0000000..6491f9d --- /dev/null +++ b/src/command/protocolPrintoutCommand.ts @@ -0,0 +1,6 @@ +export interface CreateProtocolPrintoutCommand { + title: string; + iteration: number; + filename: string; + protocolId: number; +} diff --git a/src/command/protocolPrintoutCommandHandler.ts b/src/command/protocolPrintoutCommandHandler.ts new file mode 100644 index 0000000..c1cdcbe --- /dev/null +++ b/src/command/protocolPrintoutCommandHandler.ts @@ -0,0 +1,31 @@ +import { dataSource } from "../data-source"; +import { protocolPrintout } from "../entity/protocolPrintout"; +import InternalException from "../exceptions/internalException"; +import { CreateProtocolPrintoutCommand } from "./protocolPrintoutCommand"; + +export default abstract class ProtocolPrintoutCommandHandler { + /** + * @description create protocolPrintout + * @param {number} + * @returns {Promise} + */ + static async create(printout: CreateProtocolPrintoutCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolPrintout) + .values({ + title: printout.title, + iteration: printout.iteration, + filename: printout.filename, + protocolId: printout.protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/command/protocolVotingCommand.ts b/src/command/protocolVotingCommand.ts new file mode 100644 index 0000000..a707b64 --- /dev/null +++ b/src/command/protocolVotingCommand.ts @@ -0,0 +1,9 @@ +export interface SynchronizeProtocolVotingCommand { + id: number; + topic: string; + context: string; + favour: number; + abstain: number; + against: number; + protocolId: number; +} diff --git a/src/command/protocolVotingCommandHandler.ts b/src/command/protocolVotingCommandHandler.ts new file mode 100644 index 0000000..bbc9660 --- /dev/null +++ b/src/command/protocolVotingCommandHandler.ts @@ -0,0 +1,48 @@ +import { dataSource } from "../data-source"; +import { protocolVoting } from "../entity/protocolVoting"; +import InternalException from "../exceptions/internalException"; +import { SynchronizeProtocolVotingCommand } from "./protocolVotingCommand"; + +export default abstract class ProtocolVotingCommandHandler { + /** + * @description create protocolVoting + * @param {number} + * @returns {Promise} + */ + static async create(protocolId: number): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolVoting) + .values({ + topic: "", + context: "", + protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } + /** + * @description sync protocolVoting + * @param {Array} + * @returns {Promise} + */ + static async sync(syncProtocolVotings: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolVoting) + .values(syncProtocolVotings) + .orUpdate(["topic", "context", "favour", "abstain", "against"], ["id"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts new file mode 100644 index 0000000..32e1016 --- /dev/null +++ b/src/controller/admin/protocolController.ts @@ -0,0 +1,386 @@ +import { Request, Response } from "express"; +import ProtocolService from "../../service/protocolService"; +import ProtocolFactory from "../../factory/admin/protocol"; +import ProtocolAgendaService from "../../service/protocolAgendaService"; +import ProtocolAgendaFactory from "../../factory/admin/protocolAgenda"; +import ProtocolDecisionService from "../../service/protocolDecisionService"; +import ProtocolDecisionFactory from "../../factory/admin/protocolDecision"; +import ProtocolPresenceService from "../../service/protocolPrecenseService"; +import ProtocolPresenceFactory from "../../factory/admin/protocolPresence"; +import ProtocolVotingService from "../../service/protocolVotingService"; +import ProtocolVotingFactory from "../../factory/admin/protocolVoting"; +import { CreateProtocolCommand, SynchronizeProtocolCommand } from "../../command/protocolCommand"; +import ProtocolCommandHandler from "../../command/protocolCommandHandler"; +import { SynchronizeProtocolAgendaCommand } from "../../command/protocolAgendaCommand"; +import ProtocolAgendaCommandHandler from "../../command/protocolAgendaCommandHandler"; +import { ProtocolAgendaViewModel } from "../../viewmodel/admin/protocolAgenda.models"; +import ProtocolDecisionCommandHandler from "../../command/protocolDecisionCommandHandler"; +import { ProtocolDecisionViewModel } from "../../viewmodel/admin/protocolDecision.models"; +import ProtocolPresenceCommandHandler from "../../command/protocolPresenceCommandHandler"; +import { SynchronizeProtocolPresenceCommand } from "../../command/protocolPresenceCommand"; +import { SynchronizeProtocolDecisionCommand } from "../../command/protocolDecisionCommand"; +import { SynchronizeProtocolVotingCommand } from "../../command/protocolVotingCommand"; +import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models"; +import ProtocolVotingCommandHandler from "../../command/protocolVotingCommandHandler"; +import { PdfExport } from "../../helpers/pdfExport"; +import ProtocolPrintoutService from "../../service/protocolPrintoutService"; +import ProtocolPrintoutFactory from "../../factory/admin/protocolPrintout"; +import { CreateProtocolPrintoutCommand } from "../../command/protocolPrintoutCommand"; +import ProtocolPrintoutCommandHandler from "../../command/protocolPrintoutCommandHandler"; + +/** + * @description get all protocols + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllProtocols(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let [protocols, total] = await ProtocolService.getAll(offset, count); + + res.json({ + protocols: ProtocolFactory.mapToBase(protocols), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get protocol by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolById(req: Request, res: Response): Promise { + let id = parseInt(req.params.id); + let protocol = await ProtocolService.getById(id); + + res.json(ProtocolFactory.mapToSingle(protocol)); +} + +/** + * @description get protocol agenda by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolAgendaById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let agenda = await ProtocolAgendaService.getAll(protocolId); + + res.json(ProtocolAgendaFactory.mapToBase(agenda)); +} + +/** + * @description get protocol decisions by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolDecisonsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let decisions = await ProtocolDecisionService.getAll(protocolId); + + res.json(ProtocolDecisionFactory.mapToBase(decisions)); +} + +/** + * @description get protocol precense by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolPrecenseById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let presence = await ProtocolPresenceService.getAll(protocolId); + + res.json(ProtocolPresenceFactory.mapToBase(presence)); +} + +/** + * @description get protocol votings by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolVotingsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let votings = await ProtocolVotingService.getAll(protocolId); + + res.json(ProtocolVotingFactory.mapToBase(votings)); +} + +/** + * @description get protocol printouts by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolPrintoutsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let printouts = await ProtocolPrintoutService.getAll(protocolId); + + res.json(ProtocolPrintoutFactory.mapToBase(printouts)); +} + +/** + * @description get protocol printout by id and print + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolPrintoutByIdAndPrint(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let printoutId = parseInt(req.params.printoutId); + + let printout = await ProtocolPrintoutService.getById(printoutId, protocolId); + + res.sendFile(process.cwd() + `/export/${printout.filename}.pdf`, { + headers: { + "Content-Type": "application/pdf", + }, + }); +} + +/** + * @description create protocol + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocol(req: Request, res: Response): Promise { + let title = req.body.title; + let date = req.body.date; + + let createProtocol: CreateProtocolCommand = { + title, + date, + }; + let id = await ProtocolCommandHandler.create(createProtocol); + + res.send(id); +} + +/** + * @description create protocol agenda by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolAgendaById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let agenda = await ProtocolAgendaCommandHandler.create(protocolId); + + res.send(agenda); +} + +/** + * @description create protocol decisions by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolDecisonsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let decision = await ProtocolDecisionCommandHandler.create(protocolId); + + res.send(decision); +} + +/** + * @description create protocol votings by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolVotingsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let voting = await ProtocolVotingCommandHandler.create(protocolId); + + res.send(voting); +} + +/** + * @description create protocol printout by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolPrintoutById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let protocol = await ProtocolService.getById(protocolId); + let agenda = await ProtocolAgendaService.getAll(protocolId); + let decisions = await ProtocolDecisionService.getAll(protocolId); + let presence = await ProtocolPresenceService.getAll(protocolId); + let votings = await ProtocolVotingService.getAll(protocolId); + let iteration = await ProtocolPrintoutService.getCount(protocolId); + + let title = `Sitzungsprotokoll - ${new Date(protocol.date).toLocaleDateString("de-DE", { + day: "2-digit", + month: "long", + year: "numeric", + })}`; + + let filename = `P_${protocol.title.replace(/[^a-zA-Z0-9]/g, "")}_${iteration + 1}_${new Date().toLocaleDateString()}`; + + await PdfExport.renderFile({ + template: "protocol.template.html", + title, + filename, + data: { + title: protocol.title, + summary: protocol.summary, + iteration: iteration + 1, + date: new Date(protocol.date).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "2-digit", + year: "numeric", + }), + start: protocol.starttime, + end: protocol.endtime, + agenda, + decisions, + presence: presence.map((p) => p.member), + votings, + }, + }); + + let printout: CreateProtocolPrintoutCommand = { + title, + iteration: iteration + 1, + filename, + protocolId, + }; + await ProtocolPrintoutCommandHandler.create(printout); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolById(req: Request, res: Response): Promise { + let id = parseInt(req.params.id); + let title = req.body.title; + let date = req.body.date; + let starttime = req.body.starttime; + let endtime = req.body.endtime; + let summary = req.body.summary; + + let syncProtocol: SynchronizeProtocolCommand = { + id, + title, + date, + starttime, + endtime, + summary, + }; + await ProtocolCommandHandler.sync(syncProtocol); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol agenda by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolAgendaById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let agenda = req.body.agenda as Array; + + let syncAgenda: Array = agenda.map( + (a: ProtocolAgendaViewModel): SynchronizeProtocolAgendaCommand => ({ + id: a.id ?? null, + topic: a.topic, + context: a.context, + protocolId, + }) + ); + await ProtocolAgendaCommandHandler.sync(syncAgenda); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol decisions by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolDecisonsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let decisions = req.body.decisions as Array; + + let syncDecision: Array = decisions.map( + (d: ProtocolDecisionViewModel): SynchronizeProtocolDecisionCommand => ({ + id: d.id ?? null, + topic: d.topic, + context: d.context, + protocolId, + }) + ); + await ProtocolDecisionCommandHandler.sync(syncDecision); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol votings by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolVotingsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let votings = req.body.votings as Array; + + let syncVoting: Array = votings.map( + (d: ProtocolVotingViewModel): SynchronizeProtocolVotingCommand => ({ + id: d.id ?? null, + topic: d.topic, + context: d.context, + favour: d.favour, + abstain: d.abstain, + against: d.abstain, + protocolId, + }) + ); + await ProtocolVotingCommandHandler.sync(syncVoting); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol precense by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolPrecenseById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let presence = req.body.presence as Array; + + let syncPresence: SynchronizeProtocolPresenceCommand = { + memberIds: presence, + protocolId, + }; + await ProtocolPresenceCommandHandler.sync(syncPresence); + + res.sendStatus(204); +} diff --git a/src/data-source.ts b/src/data-source.ts index f1701c4..a38def0 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -29,6 +29,13 @@ import { memberQualifications } from "./entity/memberQualifications"; import { membership } from "./entity/membership"; import { Memberdata1726301836849 } from "./migrations/1726301836849-memberdata"; import { CommunicationFields1727439800630 } from "./migrations/1727439800630-communicationFields"; +import { protocol } from "./entity/protocol"; +import { protocolAgenda } from "./entity/protocolAgenda"; +import { protocolDecision } from "./entity/protocolDecision"; +import { protocolPresence } from "./entity/protocolPresence"; +import { protocolVoting } from "./entity/protocolVoting"; +import { protocolPrintout } from "./entity/protocolPrintout"; +import { Protocol1729347911107 } from "./migrations/1729347911107-protocol"; import { calendar } from "./entity/calendar"; import { calendarType } from "./entity/calendarType"; import { Calendar1729947763295 } from "./migrations/1729947763295-calendar"; @@ -61,6 +68,12 @@ const dataSource = new DataSource({ memberExecutivePositions, memberQualifications, membership, + protocol, + protocolAgenda, + protocolDecision, + protocolPresence, + protocolVoting, + protocolPrintout, calendar, calendarType, ], @@ -73,6 +86,7 @@ const dataSource = new DataSource({ MemberBaseData1725435669492, Memberdata1726301836849, CommunicationFields1727439800630, + Protocol1729347911107, Calendar1729947763295, ], migrationsRun: true, diff --git a/src/entity/protocol.ts b/src/entity/protocol.ts new file mode 100644 index 0000000..b218164 --- /dev/null +++ b/src/entity/protocol.ts @@ -0,0 +1,22 @@ +import { Column, Entity, PrimaryColumn } from "typeorm"; + +@Entity() +export class protocol { + @PrimaryColumn({ generated: "increment", type: "int" }) + id: number; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "date" }) + date: Date; + + @Column({ type: "time", nullable: true }) + starttime: Date; + + @Column({ type: "time", nullable: true }) + endtime: Date; + + @Column({ type: "text", nullable: true }) + summary: string; +} diff --git a/src/entity/protocolAgenda.ts b/src/entity/protocolAgenda.ts new file mode 100644 index 0000000..7ce7556 --- /dev/null +++ b/src/entity/protocolAgenda.ts @@ -0,0 +1,24 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolAgenda { + @PrimaryGeneratedColumn("increment") + id: number; + + @Column({ type: "varchar", length: 255 }) + topic: string; + + @Column({ type: "text", default: "" }) + context: string; + + @Column() + protocolId: number; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/entity/protocolDecision.ts b/src/entity/protocolDecision.ts new file mode 100644 index 0000000..4978d17 --- /dev/null +++ b/src/entity/protocolDecision.ts @@ -0,0 +1,24 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolDecision { + @PrimaryGeneratedColumn("increment") + id: number; + + @Column({ type: "varchar", length: 255 }) + topic: string; + + @Column({ type: "text", default: "" }) + context: string; + + @Column() + protocolId: number; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/entity/protocolPresence.ts b/src/entity/protocolPresence.ts new file mode 100644 index 0000000..5d80b10 --- /dev/null +++ b/src/entity/protocolPresence.ts @@ -0,0 +1,26 @@ +import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm"; +import { protocol } from "./protocol"; +import { member } from "./member"; + +@Entity() +export class protocolPresence { + @PrimaryColumn() + memberId: number; + + @PrimaryColumn() + protocolId: number; + + @ManyToOne(() => member, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + member: member; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/entity/protocolPrintout.ts b/src/entity/protocolPrintout.ts new file mode 100644 index 0000000..311b407 --- /dev/null +++ b/src/entity/protocolPrintout.ts @@ -0,0 +1,30 @@ +import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolPrintout { + @PrimaryGeneratedColumn("increment") + id: number; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "int" }) + iteration: number; + + @Column({ type: "varchar", length: 255 }) + filename: string; + + @CreateDateColumn() + createdAt: Date; + + @Column() + protocolId: number; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/entity/protocolVoting.ts b/src/entity/protocolVoting.ts new file mode 100644 index 0000000..8e8f7a1 --- /dev/null +++ b/src/entity/protocolVoting.ts @@ -0,0 +1,33 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolVoting { + @PrimaryGeneratedColumn("increment") + id: number; + + @Column({ type: "varchar", length: 255 }) + topic: string; + + @Column({ type: "text", default: "" }) + context: string; + + @Column({ type: "int", default: 0 }) + favour: number; + + @Column({ type: "int", default: 0 }) + abstain: number; + + @Column({ type: "int", default: 0 }) + against: number; + + @Column() + protocolId: number; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/factory/admin/protocol.ts b/src/factory/admin/protocol.ts new file mode 100644 index 0000000..9b86894 --- /dev/null +++ b/src/factory/admin/protocol.ts @@ -0,0 +1,29 @@ +import { protocol } from "../../entity/protocol"; +import { ProtocolViewModel } from "../../viewmodel/admin/protocol.models"; + +export default abstract class ProtocolFactory { + /** + * @description map record to protocol + * @param {protocol} record + * @returns {ProtocolViewModel} + */ + public static mapToSingle(record: protocol): ProtocolViewModel { + return { + id: record.id, + title: record.title, + date: record.date, + starttime: record.starttime, + endtime: record.endtime, + summary: record.summary, + }; + } + + /** + * @description map records to protocol + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolAgenda.ts b/src/factory/admin/protocolAgenda.ts new file mode 100644 index 0000000..432c479 --- /dev/null +++ b/src/factory/admin/protocolAgenda.ts @@ -0,0 +1,27 @@ +import { protocolAgenda } from "../../entity/protocolAgenda"; +import { ProtocolAgendaViewModel } from "../../viewmodel/admin/protocolAgenda.models"; + +export default abstract class ProtocolAgendaFactory { + /** + * @description map record to protocolAgenda + * @param {protocol} record + * @returns {ProtocolAgendaViewModel} + */ + public static mapToSingle(record: protocolAgenda): ProtocolAgendaViewModel { + return { + id: record.id, + topic: record.topic, + context: record.context, + protocolId: record.protocolId, + }; + } + + /** + * @description map records to protocolAgenda + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolDecision.ts b/src/factory/admin/protocolDecision.ts new file mode 100644 index 0000000..3e33823 --- /dev/null +++ b/src/factory/admin/protocolDecision.ts @@ -0,0 +1,27 @@ +import { protocolDecision } from "../../entity/protocolDecision"; +import { ProtocolDecisionViewModel } from "../../viewmodel/admin/protocolDecision.models"; + +export default abstract class ProtocolDecisionFactory { + /** + * @description map record to protocolDecision + * @param {protocol} record + * @returns {ProtocolDecisionViewModel} + */ + public static mapToSingle(record: protocolDecision): ProtocolDecisionViewModel { + return { + id: record.id, + topic: record.topic, + context: record.context, + protocolId: record.protocolId, + }; + } + + /** + * @description map records to protocolDecision + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolPresence.ts b/src/factory/admin/protocolPresence.ts new file mode 100644 index 0000000..59a70ad --- /dev/null +++ b/src/factory/admin/protocolPresence.ts @@ -0,0 +1,27 @@ +import { protocolPresence } from "../../entity/protocolPresence"; +import { ProtocolPresenceViewModel } from "../../viewmodel/admin/protocolPresence.models"; +import MemberFactory from "./member"; + +export default abstract class ProtocolPresenceFactory { + /** + * @description map record to protocolPresence + * @param {protocol} record + * @returns {ProtocolPresenceViewModel} + */ + public static mapToSingle(record: protocolPresence): ProtocolPresenceViewModel { + return { + memberId: record.member.id, + member: MemberFactory.mapToSingle(record.member), + protocolId: record.protocolId, + }; + } + + /** + * @description map records to protocolPresence + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolPrintout.ts b/src/factory/admin/protocolPrintout.ts new file mode 100644 index 0000000..a93fad1 --- /dev/null +++ b/src/factory/admin/protocolPrintout.ts @@ -0,0 +1,28 @@ +import { protocolPrintout } from "../../entity/protocolPrintout"; +import { ProtocolPrintoutViewModel } from "../../viewmodel/admin/protocolPrintout.models"; + +export default abstract class ProtocolPrintoutFactory { + /** + * @description map record to protocolPrintout + * @param {protocol} record + * @returns {ProtocolPrintoutViewModel} + */ + public static mapToSingle(record: protocolPrintout): ProtocolPrintoutViewModel { + return { + id: record.id, + title: record.title, + iteration: record.iteration, + createdAt: record.createdAt, + protocolId: record.protocolId, + }; + } + + /** + * @description map records to protocolPrintout + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolVoting.ts b/src/factory/admin/protocolVoting.ts new file mode 100644 index 0000000..19865fb --- /dev/null +++ b/src/factory/admin/protocolVoting.ts @@ -0,0 +1,30 @@ +import { protocolVoting } from "../../entity/protocolVoting"; +import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models"; + +export default abstract class ProtocolVotingFactory { + /** + * @description map record to protocolVoting + * @param {protocol} record + * @returns {ProtocolVotingViewModel} + */ + public static mapToSingle(record: protocolVoting): ProtocolVotingViewModel { + return { + id: record.id, + topic: record.topic, + context: record.context, + favour: record.favour, + abstain: record.abstain, + against: record.against, + protocolId: record.protocolId, + }; + } + + /** + * @description map records to protocolVoting + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/helpers/pdfExport.ts b/src/helpers/pdfExport.ts new file mode 100644 index 0000000..353d8dd --- /dev/null +++ b/src/helpers/pdfExport.ts @@ -0,0 +1,44 @@ +import { readFileSync } from "fs"; +import pdf, { Options } from "pdf-creator-node"; + +var options = (title: string = "pdf-export Mitgliederverwaltung"): Options => ({ + format: "A4", + orientation: "portrait", + border: "10mm", + header: { + height: "10mm", + contents: `

${title}

`, + }, + footer: { + height: "5mm", + contents: { + default: '{{page}}/{{pages}}', + }, + }, +}); + +export abstract class PdfExport { + static getTemplate(template: string) { + return readFileSync(process.cwd() + "/src/templates/" + template, "utf8"); + } + + static async renderFile({ + template, + title, + filename, + data, + }: { + template: string; + title: string; + filename: string; + data: any; + }) { + let document = { + html: this.getTemplate(template), + data, + path: process.cwd() + `/export/${filename}.pdf`, + }; + + await pdf.create(document, options(title)); + } +} diff --git a/src/migrations/1729347911107-protocol.ts b/src/migrations/1729347911107-protocol.ts new file mode 100644 index 0000000..57c51cf --- /dev/null +++ b/src/migrations/1729347911107-protocol.ts @@ -0,0 +1,203 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm"; +import { DB_TYPE } from "../env.defaults"; + +export class Protocol1729347911107 implements MigrationInterface { + name = "Protocol1729347911107"; + + public async up(queryRunner: QueryRunner): Promise { + const variableType_int = DB_TYPE == "mysql" ? "int" : "integer"; + + await queryRunner.createTable( + new Table({ + name: "protocol", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "title", type: "varchar", length: "255", isNullable: false }, + { name: "date", type: "date", isNullable: false }, + { name: "starttime", type: "time", isNullable: true }, + { name: "endtime", type: "time", isNullable: true }, + { name: "summary", type: "text", isNullable: true }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_agenda", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "topic", type: "varchar", length: "255", isNullable: false }, + { name: "context", type: "text", default: "''", isNullable: false }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_decision", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "topic", type: "varchar", length: "255", isNullable: false }, + { name: "context", type: "text", default: "''", isNullable: false }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_presence", + columns: [ + { name: "memberId", type: variableType_int, isPrimary: true, isNullable: false }, + { name: "protocolId", type: variableType_int, isPrimary: true, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_voting", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "topic", type: "varchar", length: "255", isNullable: false }, + { name: "context", type: "text", default: "''", isNullable: false }, + { name: "favour", type: variableType_int, default: 0, isNullable: false }, + { name: "abstain", type: variableType_int, default: 0, isNullable: false }, + { name: "against", type: variableType_int, default: 0, isNullable: false }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_printout", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "title", type: "varchar", length: "255", isNullable: false }, + { name: "iteration", type: variableType_int, default: 1, isNullable: false }, + { name: "filename", type: "varchar", length: "255", isNullable: false }, + { name: "createdAt", type: "datetime(6)", isNullable: false, default: "CURRENT_TIMESTAMP(6)" }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createForeignKey( + "protocol_agenda", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + + await queryRunner.createForeignKey( + "protocol_decision", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + + await queryRunner.createForeignKey( + "protocol_voting", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + + await queryRunner.createForeignKey( + "protocol_presence", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + await queryRunner.createForeignKey( + "protocol_presence", + new TableForeignKey({ + columnNames: ["memberId"], + referencedColumnNames: ["id"], + referencedTableName: "member", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + + await queryRunner.createForeignKey( + "protocol_printout", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + const tableProtocolVotings = await queryRunner.getTable("protocol_voting"); + const foreignKeyProtocolVotings = tableProtocolVotings.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_voting", foreignKeyProtocolVotings); + + const tableProtocolDecisions = await queryRunner.getTable("protocol_decision"); + const foreignKeyProtocolDecisions = tableProtocolDecisions.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_decision", foreignKeyProtocolDecisions); + + const tableProtocolAgenda = await queryRunner.getTable("protocol_agenda"); + const foreignKeyProtocolAgenda = tableProtocolAgenda.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_agenda", foreignKeyProtocolAgenda); + + const tableProtocolPresence_protcol = await queryRunner.getTable("protocol_presence"); + const foreignKeyProtocolPresence_protcol = tableProtocolPresence_protcol.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_presence", foreignKeyProtocolPresence_protcol); + + const tableProtocolPresence_member = await queryRunner.getTable("protocol_presence"); + const foreignKeyProtocolPresence_member = tableProtocolPresence_member.foreignKeys.find( + (fk) => fk.columnNames.indexOf("memberId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_presence", foreignKeyProtocolPresence_member); + + const tableProtocolPrintout = await queryRunner.getTable("protocol_printout"); + const foreignKeyProtocolPrintout = tableProtocolPrintout.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_printout", foreignKeyProtocolPrintout); + + await queryRunner.dropTable("protocol_printout"); + await queryRunner.dropTable("protocol_voting"); + await queryRunner.dropTable("protocol_presence"); + await queryRunner.dropTable("protocol_decision"); + await queryRunner.dropTable("protocol_agenda"); + await queryRunner.dropTable("protocol"); + } +} diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index 63a70ff..cdafa5e 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -8,6 +8,7 @@ import membershipStatus from "./membershipStatus"; import qualification from "./qualification"; import member from "./member"; +import protocol from "./protocol"; import calendar from "./calendar"; @@ -36,6 +37,7 @@ router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "setti router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "member"), member); +router.use("/protocol", PermissionHelper.passCheckMiddleware("read", "club", "protocol"), protocol); router.use("/calendar", PermissionHelper.passCheckMiddleware("read", "club", "calendar"), calendar); router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role); diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts new file mode 100644 index 0000000..48c5e6c --- /dev/null +++ b/src/routes/admin/protocol.ts @@ -0,0 +1,138 @@ +import express, { Request, Response } from "express"; +import { + createProtocol, + createProtocolAgendaById, + createProtocolDecisonsById, + createProtocolPrintoutById, + createProtocolVotingsById, + getAllProtocols, + getProtocolAgendaById, + getProtocolById, + getProtocolDecisonsById, + getProtocolPrecenseById, + getProtocolPrintoutByIdAndPrint, + getProtocolPrintoutsById, + getProtocolVotingsById, + synchronizeProtocolAgendaById, + synchronizeProtocolById, + synchronizeProtocolDecisonsById, + synchronizeProtocolPrecenseById, + synchronizeProtocolVotingsById, +} from "../../controller/admin/protocolController"; +import PermissionHelper from "../../helpers/permissionHelper"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllProtocols(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getProtocolById(req, res); +}); + +router.get("/:protocolId/agenda", async (req: Request, res: Response) => { + await getProtocolAgendaById(req, res); +}); + +router.get("/:protocolId/decisions", async (req: Request, res: Response) => { + await getProtocolDecisonsById(req, res); +}); + +router.get("/:protocolId/presence", async (req: Request, res: Response) => { + await getProtocolPrecenseById(req, res); +}); + +router.get("/:protocolId/votings", async (req: Request, res: Response) => { + await getProtocolVotingsById(req, res); +}); + +router.get("/:protocolId/printouts", async (req: Request, res: Response) => { + await getProtocolPrintoutsById(req, res); +}); + +router.get("/:protocolId/printout/:printoutId", async (req: Request, res: Response) => { + await getProtocolPrintoutByIdAndPrint(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "club", "protocol"), + async (req: Request, res: Response) => { + await createProtocol(req, res); + } +); + +router.post( + "/:protocolId/agenda", + PermissionHelper.passCheckMiddleware("create", "club", "protocol"), + async (req: Request, res: Response) => { + await createProtocolAgendaById(req, res); + } +); + +router.post( + "/:protocolId/decision", + PermissionHelper.passCheckMiddleware("create", "club", "protocol"), + async (req: Request, res: Response) => { + await createProtocolDecisonsById(req, res); + } +); + +router.post( + "/:protocolId/voting", + PermissionHelper.passCheckMiddleware("create", "club", "protocol"), + async (req: Request, res: Response) => { + await createProtocolVotingsById(req, res); + } +); + +router.post( + "/:protocolId/printout", + PermissionHelper.passCheckMiddleware("create", "club", "protocol"), + async (req: Request, res: Response) => { + await createProtocolPrintoutById(req, res); + } +); + +router.patch( + "/:id/synchronize", + PermissionHelper.passCheckMiddleware("update", "club", "protocol"), + async (req: Request, res: Response) => { + await synchronizeProtocolById(req, res); + } +); + +router.patch( + "/:protocolId/synchronize/agenda", + PermissionHelper.passCheckMiddleware("update", "club", "protocol"), + async (req: Request, res: Response) => { + await synchronizeProtocolAgendaById(req, res); + } +); + +router.patch( + "/:protocolId/synchronize/decisions", + PermissionHelper.passCheckMiddleware("update", "club", "protocol"), + async (req: Request, res: Response) => { + await synchronizeProtocolDecisonsById(req, res); + } +); + +router.patch( + "/:protocolId/synchronize/votings", + PermissionHelper.passCheckMiddleware("update", "club", "protocol"), + async (req: Request, res: Response) => { + await synchronizeProtocolVotingsById(req, res); + } +); + +router.put( + "/:protocolId/synchronize/presence", + PermissionHelper.passCheckMiddleware("update", "club", "protocol"), + async (req: Request, res: Response) => { + await synchronizeProtocolPrecenseById(req, res); + } +); + +export default router; diff --git a/src/service/protocolAgendaService.ts b/src/service/protocolAgendaService.ts new file mode 100644 index 0000000..4409d05 --- /dev/null +++ b/src/service/protocolAgendaService.ts @@ -0,0 +1,41 @@ +import { dataSource } from "../data-source"; +import { protocolAgenda } from "../entity/protocolAgenda"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolAgendaService { + /** + * @description get all protocolAgendas + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolAgenda) + .createQueryBuilder("protocolAgenda") + .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolAgendas not found", err); + }); + } + + /** + * @description get protocolAgenda by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolAgenda) + .createQueryBuilder("protocolAgenda") + .where("protocolAgenda.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolAgenda not found by id", err); + }); + } +} diff --git a/src/service/protocolDecisionService.ts b/src/service/protocolDecisionService.ts new file mode 100644 index 0000000..b818313 --- /dev/null +++ b/src/service/protocolDecisionService.ts @@ -0,0 +1,41 @@ +import { dataSource } from "../data-source"; +import { protocolDecision } from "../entity/protocolDecision"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolDecisionService { + /** + * @description get all protocolDecisionss + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolDecision) + .createQueryBuilder("protocolDecisions") + .where("protocolDecisions.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolDecisions not found", err); + }); + } + + /** + * @description get protocolDecision by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolDecision) + .createQueryBuilder("protocolDecisions") + .where("protocolDecisions.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolDecision not found by id", err); + }); + } +} diff --git a/src/service/protocolPrecenseService.ts b/src/service/protocolPrecenseService.ts new file mode 100644 index 0000000..c8840c3 --- /dev/null +++ b/src/service/protocolPrecenseService.ts @@ -0,0 +1,43 @@ +import { dataSource } from "../data-source"; +import { protocolPresence } from "../entity/protocolPresence"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolPresenceService { + /** + * @description get all protocolPresences + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolPresence) + .createQueryBuilder("protocolPresence") + .leftJoinAndSelect("protocolPresence.member", "member") + .where("protocolPresence.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPresence not found", err); + }); + } + + /** + * @description get protocolDecision by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolPresence) + .createQueryBuilder("protocolPresence") + .leftJoinAndSelect("protocolPresence.member", "member") + .where("protocolPresence.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolDecision not found by id", err); + }); + } +} diff --git a/src/service/protocolPrintoutService.ts b/src/service/protocolPrintoutService.ts new file mode 100644 index 0000000..aaf39ed --- /dev/null +++ b/src/service/protocolPrintoutService.ts @@ -0,0 +1,60 @@ +import { dataSource } from "../data-source"; +import { protocolPrintout } from "../entity/protocolPrintout"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolPrintoutService { + /** + * @description get all protocolPrintouts + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolPrintout) + .createQueryBuilder("protocolPrintout") + .where("protocolPrintout.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPrintouts not found", err); + }); + } + + /** + * @description get protocolPrintout by id + * @returns {Promise} + */ + static async getById(id: number, protocolId: number): Promise { + return await dataSource + .getRepository(protocolPrintout) + .createQueryBuilder("protocolPrintout") + .where("protocolPrintout.protocolId = :protocolId", { protocolId }) + .andWhere("protocolPrintout.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPrintout not found by id", err); + }); + } + + /** + * @description get count of printouts by id + * @returns {Promise} + */ + static async getCount(protocolId: number): Promise { + return await dataSource + .getRepository(protocolPrintout) + .createQueryBuilder("protocolPrintout") + .where("protocolPrintout.protocolId = :protocolId", { protocolId }) + .getCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPrintout not found by id", err); + }); + } +} diff --git a/src/service/protocolService.ts b/src/service/protocolService.ts new file mode 100644 index 0000000..53e0baf --- /dev/null +++ b/src/service/protocolService.ts @@ -0,0 +1,43 @@ +import { dataSource } from "../data-source"; +import { protocol } from "../entity/protocol"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolService { + /** + * @description get all protocols + * @returns {Promise<[Array, number]>} + */ + static async getAll(offset: number = 0, count: number = 25): Promise<[Array, number]> { + return await dataSource + .getRepository(protocol) + .createQueryBuilder("protocol") + .offset(offset) + .limit(count) + .orderBy("date", "DESC") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocols not found", err); + }); + } + + /** + * @description get protocol by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocol) + .createQueryBuilder("protocol") + .where("protocol.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocol not found by id", err); + }); + } +} diff --git a/src/service/protocolVotingService.ts b/src/service/protocolVotingService.ts new file mode 100644 index 0000000..55792fe --- /dev/null +++ b/src/service/protocolVotingService.ts @@ -0,0 +1,41 @@ +import { dataSource } from "../data-source"; +import { protocolVoting } from "../entity/protocolVoting"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolVotingService { + /** + * @description get all protocolVotingss + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolVoting) + .createQueryBuilder("protocolVotings") + .where("protocolVotings.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolVotings not found", err); + }); + } + + /** + * @description get protocolVoting by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolVoting) + .createQueryBuilder("protocolVotings") + .where("protocolVotings.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolVoting not found by id", err); + }); + } +} diff --git a/src/templates/protocol.template.html b/src/templates/protocol.template.html new file mode 100644 index 0000000..98a2a21 --- /dev/null +++ b/src/templates/protocol.template.html @@ -0,0 +1,68 @@ + + + + + Protokoll + + +

{{title}}

+

Am {{date}} von {{start}} Uhr bis {{end}} Uhr

+

Ausdruck Nr {{iteration}}

+
+

Zusammenfassung:

+

{{{summary}}}

+
+
+

Anwesenheit ({{presence.length}})

+
    + {{#each presence}} +
  • {{this.firstname}} {{this.lastname}}
  • + {{/each}} +
+
+

Agenda

+ {{#each agenda}} +
+

{{this.topic}}

+ {{{this.context}}} +
+ {{/each}} +
+

Entscheidungen

+ {{#each decisions}} +
+

{{this.topic}}

+ {{{this.context}}} +
+ {{/each}} +
+

Abstimmungen

+ {{#each votings}} +
+

{{this.topic}}

+

Ergebnis: dafür: {{this.favour}} | enthalten: {{this.abstain}} | dagegen: {{this.against}}

+ {{{this.context}}} +
+ {{/each}} + + + diff --git a/src/type/permissionTypes.ts b/src/type/permissionTypes.ts index f7f05a1..fc468a9 100644 --- a/src/type/permissionTypes.ts +++ b/src/type/permissionTypes.ts @@ -4,7 +4,7 @@ export type PermissionModule = | "member" | "calendar" | "newsletter" - | "protocoll" + | "protocol" | "qualification" | "award" | "executive_position" @@ -40,7 +40,7 @@ export const permissionModules: Array = [ "member", "calendar", "newsletter", - "protocoll", + "protocol", "qualification", "award", "executive_position", @@ -52,7 +52,7 @@ export const permissionModules: Array = [ ]; export const permissionTypes: Array = ["read", "create", "update", "delete"]; export const sectionsAndModules: SectionsAndModulesObject = { - club: ["member", "calendar", "newsletter", "protocoll"], + club: ["member", "calendar", "newsletter", "protocol"], settings: ["qualification", "award", "executive_position", "communication", "membership_status", "calendar_type"], user: ["user", "role"], }; diff --git a/src/types/pdf-creator-node.d.ts b/src/types/pdf-creator-node.d.ts new file mode 100644 index 0000000..4df4491 --- /dev/null +++ b/src/types/pdf-creator-node.d.ts @@ -0,0 +1,27 @@ +// types/pdf-creator-node.d.ts +declare module "pdf-creator-node" { + interface Document { + html: string; + data: any; + path: string; + type?: string; + } + + interface Options { + format: string; + orientation: string; + border: string; + header?: { + height: string; + contents: string; + }; + footer?: { + height: string; + contents: string | { [key: string]: string | number }; + }; + } + + function create(document: Document, options: Options): Promise; + + export { create, Document, Options }; +} diff --git a/src/viewmodel/admin/protocol.models.ts b/src/viewmodel/admin/protocol.models.ts new file mode 100644 index 0000000..c02e60c --- /dev/null +++ b/src/viewmodel/admin/protocol.models.ts @@ -0,0 +1,8 @@ +export interface ProtocolViewModel { + id: number; + title: string; + date: Date; + starttime: Date; + endtime: Date; + summary: string; +} diff --git a/src/viewmodel/admin/protocolAgenda.models.ts b/src/viewmodel/admin/protocolAgenda.models.ts new file mode 100644 index 0000000..3a59327 --- /dev/null +++ b/src/viewmodel/admin/protocolAgenda.models.ts @@ -0,0 +1,6 @@ +export interface ProtocolAgendaViewModel { + id: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/viewmodel/admin/protocolDecision.models.ts b/src/viewmodel/admin/protocolDecision.models.ts new file mode 100644 index 0000000..4a7212c --- /dev/null +++ b/src/viewmodel/admin/protocolDecision.models.ts @@ -0,0 +1,6 @@ +export interface ProtocolDecisionViewModel { + id: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/viewmodel/admin/protocolPresence.models.ts b/src/viewmodel/admin/protocolPresence.models.ts new file mode 100644 index 0000000..9c10f59 --- /dev/null +++ b/src/viewmodel/admin/protocolPresence.models.ts @@ -0,0 +1,7 @@ +import { MemberViewModel } from "./member.models"; + +export interface ProtocolPresenceViewModel { + memberId: number; + member: MemberViewModel; + protocolId: number; +} diff --git a/src/viewmodel/admin/protocolPrintout.models.ts b/src/viewmodel/admin/protocolPrintout.models.ts new file mode 100644 index 0000000..ff60cdb --- /dev/null +++ b/src/viewmodel/admin/protocolPrintout.models.ts @@ -0,0 +1,7 @@ +export interface ProtocolPrintoutViewModel { + id: number; + title: string; + iteration: number; + createdAt: Date; + protocolId: number; +} diff --git a/src/viewmodel/admin/protocolVoting.models.ts b/src/viewmodel/admin/protocolVoting.models.ts new file mode 100644 index 0000000..686f423 --- /dev/null +++ b/src/viewmodel/admin/protocolVoting.models.ts @@ -0,0 +1,9 @@ +export interface ProtocolVotingViewModel { + id: number; + topic: string; + context: string; + favour: number; + abstain: number; + against: number; + protocolId: number; +}