From fec8f82b13f208be4f86980c7f7a20e63bb60c2e Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:05 +0100 Subject: replace npm with pnpm --- package-lock.json | 7925 ----------------------------------------------------- pnpm-lock.yaml | 2817 +++++++++++++++++++ 2 files changed, 2817 insertions(+), 7925 deletions(-) delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b19767b..0000000 --- a/package-lock.json +++ /dev/null @@ -1,7925 +0,0 @@ -{ - "name": "gathio", - "version": "1.3.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "gathio", - "version": "1.3.0", - "license": "GPL-3.0-or-later", - "dependencies": { - "@sendgrid/mail": "^6.5.5", - "body-parser": "^1.18.3", - "cors": "^2.8.5", - "dotenv": "^6.1.0", - "express": "^4.16.4", - "express-fileupload": "^1.1.9", - "express-handlebars": "^6.0.5", - "express-jwt": "^8.4.0", - "express-session": "^1.17.1", - "express-validator": "^6.14.0", - "generate-rsa-keypair": "^0.2.1", - "greenlock": "^2.8.8", - "greenlock-express": "^2.7.18", - "ical": "^0.6.0", - "ical-generator": "^1.11.0", - "jimp": "^0.16.1", - "jsonwebtoken": "^9.0.0", - "marked": "^4.0.10", - "moment-timezone": "^0.5.31", - "mongoose": "^5.9.18", - "multer": "^1.4.5-lts.1", - "nanoid": "^3.1.9", - "niceware": "^3.0.0", - "node-schedule": "^1.3.1", - "nodemailer": "^6.4.8", - "randomstring": "^1.1.5", - "request": "^2.88.2", - "sanitize-html": "^2.6.1" - }, - "devDependencies": { - "eslint": "^8.4.1", - "nodemon": "^2.0.15" - }, - "engines": { - "node": ">=16.16.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@jimp/bmp": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.2.tgz", - "integrity": "sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "bmp-js": "^0.1.0" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/core": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.2.tgz", - "integrity": "sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - } - }, - "node_modules/@jimp/custom": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.2.tgz", - "integrity": "sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.16.2" - } - }, - "node_modules/@jimp/gif": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.2.tgz", - "integrity": "sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/jpeg": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.16.2.tgz", - "integrity": "sha512-BW5gZydgq6wdIwHd+3iUNgrTklvoQc/FUKSj9meM6A0FU21lUaansRX5BDdJqHkyXJLnnlDGwDt27J+hQuBAVw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "jpeg-js": "^0.4.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-blit": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.2.tgz", - "integrity": "sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-blur": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.2.tgz", - "integrity": "sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-circle": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.2.tgz", - "integrity": "sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-color": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.2.tgz", - "integrity": "sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "tinycolor2": "^1.4.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-contain": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.2.tgz", - "integrity": "sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5", - "@jimp/plugin-scale": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-cover": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.2.tgz", - "integrity": "sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-crop": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5", - "@jimp/plugin-scale": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-crop": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.2.tgz", - "integrity": "sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-displace": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.2.tgz", - "integrity": "sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-dither": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.2.tgz", - "integrity": "sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-fisheye": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.2.tgz", - "integrity": "sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-flip": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.2.tgz", - "integrity": "sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-rotate": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-gaussian": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.2.tgz", - "integrity": "sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-invert": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.2.tgz", - "integrity": "sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-mask": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.2.tgz", - "integrity": "sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-normalize": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.2.tgz", - "integrity": "sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-print": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.2.tgz", - "integrity": "sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "load-bmfont": "^1.4.0" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-resize": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.2.tgz", - "integrity": "sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-rotate": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.2.tgz", - "integrity": "sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5", - "@jimp/plugin-crop": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-scale": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.2.tgz", - "integrity": "sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-shadow": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.2.tgz", - "integrity": "sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blur": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" - } - }, - "node_modules/@jimp/plugin-threshold": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.2.tgz", - "integrity": "sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-color": ">=0.8.0", - "@jimp/plugin-resize": ">=0.8.0" - } - }, - "node_modules/@jimp/plugins": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.2.tgz", - "integrity": "sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.16.2", - "@jimp/plugin-blur": "^0.16.2", - "@jimp/plugin-circle": "^0.16.2", - "@jimp/plugin-color": "^0.16.2", - "@jimp/plugin-contain": "^0.16.2", - "@jimp/plugin-cover": "^0.16.2", - "@jimp/plugin-crop": "^0.16.2", - "@jimp/plugin-displace": "^0.16.2", - "@jimp/plugin-dither": "^0.16.2", - "@jimp/plugin-fisheye": "^0.16.2", - "@jimp/plugin-flip": "^0.16.2", - "@jimp/plugin-gaussian": "^0.16.2", - "@jimp/plugin-invert": "^0.16.2", - "@jimp/plugin-mask": "^0.16.2", - "@jimp/plugin-normalize": "^0.16.2", - "@jimp/plugin-print": "^0.16.2", - "@jimp/plugin-resize": "^0.16.2", - "@jimp/plugin-rotate": "^0.16.2", - "@jimp/plugin-scale": "^0.16.2", - "@jimp/plugin-shadow": "^0.16.2", - "@jimp/plugin-threshold": "^0.16.2", - "timm": "^1.6.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/png": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.2.tgz", - "integrity": "sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "pngjs": "^3.3.3" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/tiff": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.2.tgz", - "integrity": "sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/types": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.2.tgz", - "integrity": "sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.16.2", - "@jimp/gif": "^0.16.2", - "@jimp/jpeg": "^0.16.2", - "@jimp/png": "^0.16.2", - "@jimp/tiff": "^0.16.2", - "timm": "^1.6.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" - } - }, - "node_modules/@jimp/utils": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.16.2.tgz", - "integrity": "sha512-XENrPvmigiXZQ8E2nxJqO6UVvWBLzbNwyYi3Y8Q1IECoYhYI3kgOQ0fmy4G269Vz1V0omh1bNmC42r4OfXg1Jg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@root/mkdirp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", - "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" - }, - "node_modules/@root/request": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.2.tgz", - "integrity": "sha512-wVaL9yVV9oDR9UNbPZa20qgY+4Ch6YN8JUkaE4el/uuS5dmhD8Lusm/ku8qJVNtmQA56XLzEDCRS6/vfpiHK2A==" - }, - "node_modules/@sendgrid/client": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-6.5.5.tgz", - "integrity": "sha512-Nbfgo94gbWSL8PIgJfuHoifyOJJepvV8NQkkglctAEfb1hyozKhrzE6v1kPG/z4j0RodaTtXD5LJj/t0q/VhLA==", - "dependencies": { - "@sendgrid/helpers": "^6.5.5", - "@types/request": "^2.48.4", - "request": "^2.88.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@sendgrid/helpers": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-6.5.5.tgz", - "integrity": "sha512-uRFEanalfss5hDsuzVXZ1wm7i7eEXHh1py80piOXjobiQ+MxmtR19EU+gDSXZ+uMcEehBGhxnb7QDNN0q65qig==", - "dependencies": { - "chalk": "^2.0.1", - "deepmerge": "^4.2.2" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@sendgrid/mail": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-6.5.5.tgz", - "integrity": "sha512-DSu8oTPI0BJFH60jMOG9gM+oeNMoRALFmdAYg2PIXpL+Zbxd7L2GzQZtmf1jLy/8UBImkbB3D74TjiOBiLRK1w==", - "dependencies": { - "@sendgrid/client": "^6.5.5", - "@sendgrid/helpers": "^6.5.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "dependencies": { - "@types/bson": "*", - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" - }, - "node_modules/@types/request": { - "version": "2.48.8", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", - "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", - "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - } - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acme": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/acme/-/acme-1.3.5.tgz", - "integrity": "sha512-KIFVyMho7y3RxRSTzkuX031TmfXwzl0ioy8+r2pnfLz6YWFQ5q7a/cYUDTgIbrFMPe/syY26Qv1DOdHQ5ARWcw==", - "dependencies": { - "acme-v2": "^1.8.6" - } - }, - "node_modules/acme-dns-01-cli": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/acme-dns-01-cli/-/acme-dns-01-cli-3.0.7.tgz", - "integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w==" - }, - "node_modules/acme-v2": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.8.6.tgz", - "integrity": "sha512-LWdicUYHTGDtYX7LlgsQurmM9txwfAFydg7mQLPKHrFMnNNtfJEtHC2fWfr+pFGNb3XKIbvyFUoyFB6cOmWRpA==", - "hasInstallScript": true, - "dependencies": { - "@root/request": "^1.3.11", - "rsa-compat": "^2.0.8" - } - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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==", - "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", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-uniq": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz", - "integrity": "sha512-GVYjmpL05al4dNlKJm53mKE4w9OOLiuVHWorsIA3YVz+Hu0hcn6PtE3Ydl0EqU7v+7ABC4mjjWsnLUxbpno+CA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "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==", - "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==" - }, - "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==", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "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==", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/binary-search": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", - "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" - }, - "node_modules/bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "node_modules/bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", - "engines": { - "node": ">=0.4.0" - } - }, - "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==" - }, - "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==" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "node_modules/cert-info": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", - "integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==", - "bin": { - "cert-info": "bin/cert-info.js" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "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==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "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" - ], - "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", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cron-parser": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.18.0.tgz", - "integrity": "sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==", - "dependencies": { - "is-nan": "^1.3.0", - "moment-timezone": "^0.5.31" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "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==", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", - "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "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==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==", - "engines": { - "node": ">=6" - } - }, - "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==", - "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", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/eckles": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz", - "integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA==", - "bin": { - "eckles": "bin/eckles.js" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", - "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express-fileupload": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz", - "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==", - "dependencies": { - "busboy": "^1.6.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/express-handlebars": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.7.tgz", - "integrity": "sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg==", - "dependencies": { - "glob": "^8.1.0", - "graceful-fs": "^4.2.10", - "handlebars": "^4.7.7" - }, - "engines": { - "node": ">=v12.22.9" - } - }, - "node_modules/express-jwt": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.4.0.tgz", - "integrity": "sha512-AvIkHI6+wuwHQTgfnl4kEPWeMKo5yw4FnXJJK+jf/PRWAflmuJKTs06ENRNJ6sCQceIUVqAi/fy8Nav8alnv0w==", - "dependencies": { - "@types/jsonwebtoken": "^9", - "express-unless": "^2.1.3", - "jsonwebtoken": "^9.0.0" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", - "dependencies": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-unless": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz", - "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==" - }, - "node_modules/express-validator": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.3.tgz", - "integrity": "sha512-c4b9NMdhskfcLbH/FchsSfCt4Vb14gKzcotG9zLS+VoOJDox57aGhCL+kmAu7cl+ytaSed+HD5jdJhel8DQsdg==", - "dependencies": { - "lodash": "^4.17.21", - "validator": "^13.7.0" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "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" - ] - }, - "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==" - }, - "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==" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-type": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", - "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "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==", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "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", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/generate-rsa-keypair": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/generate-rsa-keypair/-/generate-rsa-keypair-0.2.1.tgz", - "integrity": "sha512-vxLfzfy6WbMLtkKV4AJtg7QH0ZqGGNkSYM6S0Q72Z70QXsztLklKFtX15te3YLIqmiQAYi3g3MWsTfXd6djkpg==", - "hasInstallScript": true, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "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==", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/gifwrap": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", - "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", - "dependencies": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/greenlock": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/greenlock/-/greenlock-2.8.8.tgz", - "integrity": "sha512-U2pqxXXf0naeZc2363Xe174C6/T9lXGZYQjXBqa/PMb1CYRQuHwXlAqFEUu75JkxyHAzFGj/uliqSyQwIc91Yg==", - "dependencies": { - "acme": "^1.3.5", - "acme-dns-01-cli": "^3.0.0", - "acme-v2": "^1.8.6", - "cert-info": "^1.5.1", - "greenlock-store-fs": "^3.0.2", - "keypairs": "^1.2.14", - "le-challenge-fs": "^2.0.2", - "le-sni-auto": "^2.1.9", - "le-store-certbot": "^2.2.3", - "rsa-compat": "^2.0.8" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/greenlock-express": { - "version": "2.7.18", - "resolved": "https://registry.npmjs.org/greenlock-express/-/greenlock-express-2.7.18.tgz", - "integrity": "sha512-8gOSo4twkd4Fdmc45jfHMCXdsYnYutcUGDKZ+FRZH8XQc712glqYNrWaeWThmh0QQ/ZEiV+WGROtAdGrY5sGlQ==", - "dependencies": { - "greenlock": "^2.8.8", - "redirect-https": "^1.1.5" - } - }, - "node_modules/greenlock-store-fs": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.2.tgz", - "integrity": "sha512-92ejLB4DyV4qv/2b6VLGF2nKfYQeIfg3o+e/1cIoYLjlIaUFdbBXkzLTRozFlHsQPZt2ALi5qYrpC9IwH7GK8A==", - "dependencies": { - "@root/mkdirp": "^1.0.0", - "safe-replace": "^1.1.0" - } - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "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==", - "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", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "entities": "^4.3.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "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==", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/ical": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/ical/-/ical-0.6.0.tgz", - "integrity": "sha512-P7R3uMhFnLY7Tfiz+2uZIninThWIoUM5BVO4f9ZsV36GYqoBkIg6RbkrBhmWCvcuK1vLzljLaNtpWeGM/5ZHRg==", - "dependencies": { - "request": "^2.88.0", - "rrule": "2.4.1" - } - }, - "node_modules/ical-generator": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-1.15.4.tgz", - "integrity": "sha512-drXe4RLkfNlvDvdy/E6BUI9p+01L3ySK1ufNEYI9TxNKG9ZA3G60QWoZvD1dtmH4scwDxYu6/sZBPJvYVNrj8A==", - "dependencies": { - "moment-timezone": "^0.5.32" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "@types/node": ">= 8.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "dependencies": { - "@types/node": "16.9.1" - } - }, - "node_modules/image-q/node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "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==" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, - "node_modules/jimp": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz", - "integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.16.2", - "@jimp/plugins": "^0.16.2", - "@jimp/types": "^0.16.2", - "regenerator-runtime": "^0.13.3" - } - }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" - }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "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==" - }, - "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==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": 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==" - }, - "node_modules/jsonwebtoken": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", - "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", - "dependencies": { - "jws": "^3.2.2", - "lodash": "^4.17.21", - "ms": "^2.1.1", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "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", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" - }, - "node_modules/keypairs": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz", - "integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==", - "dependencies": { - "eckles": "^1.4.1", - "rasha": "^1.2.4" - }, - "bin": { - "keypairs-install": "bin/keypairs.js" - } - }, - "node_modules/le-challenge-fs": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/le-challenge-fs/-/le-challenge-fs-2.0.9.tgz", - "integrity": "sha512-stzI6rxd+aXGxBl87QJKKY/i/wl3uz6EoWzX2xSazJvCPSYBQys1RVNgOcf0SfUQPh6TBCFJFSJkiR4mznb4sg==", - "dependencies": { - "@root/mkdirp": "^1.0.0" - } - }, - "node_modules/le-sni-auto": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/le-sni-auto/-/le-sni-auto-2.1.9.tgz", - "integrity": "sha512-QmQHNwQDi/56GY8+qczFZ06FZbxaeJQjbjEhwwQHhkJ9IHhIQFkPfCT/OyDfLj4gqLIrg5ZX8CemxxVZnLEYfg==" - }, - "node_modules/le-store-certbot": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/le-store-certbot/-/le-store-certbot-2.2.3.tgz", - "integrity": "sha512-c4ACR+v+JKMiAOOshLh6gdCKA7wIWR16+mROMLpQjq3rXJ3Vm8FaBHe2H+crT+flP+g7FmciAwUlfOJEJpIuCQ==", - "dependencies": { - "@root/mkdirp": "^1.0.0", - "pyconf": "^1.1.7", - "safe-replace": "^1.1.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/load-bmfont": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", - "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", - "dependencies": { - "buffer-equal": "0.0.1", - "mime": "^1.3.4", - "parse-bmfont-ascii": "^1.0.3", - "parse-bmfont-binary": "^1.0.5", - "parse-bmfont-xml": "^1.1.4", - "phin": "^2.9.1", - "xhr": "^2.0.1", - "xtend": "^4.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/long-timeout": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", - "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/luxon": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", - "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/marked": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", - "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.40", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", - "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", - "dependencies": { - "moment": ">= 2.9.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", - "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" - }, - "engines": { - "node": ">=4" - }, - "optionalDependencies": { - "saslprep": "^1.0.0" - }, - "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "mongodb-extjson": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/mongodb/node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "dependencies": { - "require-at": "^1.0.6" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mongoose": { - "version": "5.13.15", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.15.tgz", - "integrity": "sha512-cxp1Gbb8yUWkaEbajdhspSaKzAvsIvOtRlYD87GN/P2QEUhpd6bIvebi36T6M0tIVAMauNaK9SPA055N3PwF8Q==", - "dependencies": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.7.3", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" - }, - "engines": { - "node": ">=4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "peerDependencies": { - "mongoose": "*" - } - }, - "node_modules/mongoose/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", - "dependencies": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mquery/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "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==" - }, - "node_modules/niceware": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/niceware/-/niceware-3.0.0.tgz", - "integrity": "sha512-DbeDuqe836Ba4S9vjim4jTbbqmjCMwuAXFCVdh4QAvbmLOhmIQs84IakYrcXd/87VCsj1XKhSmmg7bAmwAEh5A==", - "dependencies": { - "binary-search": "^1.3.6", - "randombytes": "^2.0.6" - } - }, - "node_modules/node-schedule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.3.tgz", - "integrity": "sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw==", - "dependencies": { - "cron-parser": "^2.18.0", - "long-timeout": "0.1.1", - "sorted-array-functions": "^1.3.0" - } - }, - "node_modules/nodemailer": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", - "integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.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==", - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" - }, - "node_modules/parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" - }, - "node_modules/parse-bmfont-xml": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", - "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", - "dependencies": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.4.5" - } - }, - "node_modules/parse-headers": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", - "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" - }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "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==" - }, - "node_modules/phin": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", - "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pixelmatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", - "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", - "dependencies": { - "pngjs": "^3.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "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/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "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==" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pyconf": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pyconf/-/pyconf-1.1.7.tgz", - "integrity": "sha512-v4clh33m68sjtMsh8XMpjhGWb/MQODAYZ1y7ORG5Qv58UK25OddoB+oXyexgDkK8ttFui/lZm2sQDgA2Ftjfkw==", - "dependencies": { - "safe-replace": "^1.0.2" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomstring": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/randomstring/-/randomstring-1.2.3.tgz", - "integrity": "sha512-3dEFySepTzp2CvH6W/ASYGguPPveBuz5MpZ7MuoUkoVehmyNl9+F9c9GFVrz2QPbM9NXTIHGcmJDY/3j4677kQ==", - "dependencies": { - "array-uniq": "1.0.2", - "randombytes": "2.0.3" - }, - "bin": { - "randomstring": "bin/randomstring" - }, - "engines": { - "node": "*" - } - }, - "node_modules/randomstring/node_modules/randombytes": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz", - "integrity": "sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rasha": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.5.tgz", - "integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw==", - "bin": { - "rasha": "bin/rasha.js" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redirect-https": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.1.tgz", - "integrity": "sha512-Stex2nI+tMpZXKvy++32TiBXEy+GdpAfp3EUnl5BqCiJ5f5i6XvUSFrs7TR7IoRSlthM7ZtD89uYGTtJBXlFYg==", - "dependencies": { - "escape-html": "^1.0.3" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "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", - "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/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==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "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==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rrule": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz", - "integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==", - "optionalDependencies": { - "luxon": "^1.3.3" - } - }, - "node_modules/rsa-compat": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz", - "integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==", - "dependencies": { - "keypairs": "^1.2.14" - }, - "bin": { - "rsa-keygen-js": "bin/rsa-keygen.js" - }, - "engines": { - "node": ">=10.12" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", - "integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sanitize-html": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.9.0.tgz", - "integrity": "sha512-KY1hpSbqFNcpoLf+nP7iStbP5JfQZ2Nd19ZEE7qFsQqRdp+sO5yX/e5+HoG9puFAcSTEpzQuihfKUltDcLlQjg==", - "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, - "node_modules/sanitize-html/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" - }, - "node_modules/sorted-array-functions": { - "version": "1.3.0", - "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "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", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/timm": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", - "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" - }, - "node_modules/tinycolor2": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", - "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "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==", - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "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==" - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "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==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utif": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", - "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", - "dependencies": { - "pako": "^1.0.5" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "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.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "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" - ], - "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==" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", - "dependencies": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/xml-parse-from-string": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", - "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" - }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@jimp/bmp": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.2.tgz", - "integrity": "sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "bmp-js": "^0.1.0" - } - }, - "@jimp/core": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.2.tgz", - "integrity": "sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^9.0.0", - "load-bmfont": "^1.3.1", - "mkdirp": "^0.5.1", - "phin": "^2.9.1", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/custom": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.2.tgz", - "integrity": "sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.16.2" - } - }, - "@jimp/gif": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.2.tgz", - "integrity": "sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } - }, - "@jimp/jpeg": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.16.2.tgz", - "integrity": "sha512-BW5gZydgq6wdIwHd+3iUNgrTklvoQc/FUKSj9meM6A0FU21lUaansRX5BDdJqHkyXJLnnlDGwDt27J+hQuBAVw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "jpeg-js": "^0.4.2" - } - }, - "@jimp/plugin-blit": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.2.tgz", - "integrity": "sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-blur": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.2.tgz", - "integrity": "sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-circle": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.2.tgz", - "integrity": "sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-color": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.2.tgz", - "integrity": "sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "tinycolor2": "^1.4.1" - } - }, - "@jimp/plugin-contain": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.2.tgz", - "integrity": "sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-cover": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.2.tgz", - "integrity": "sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-crop": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.2.tgz", - "integrity": "sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-displace": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.2.tgz", - "integrity": "sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-dither": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.2.tgz", - "integrity": "sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-fisheye": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.2.tgz", - "integrity": "sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-flip": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.2.tgz", - "integrity": "sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-gaussian": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.2.tgz", - "integrity": "sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-invert": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.2.tgz", - "integrity": "sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-mask": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.2.tgz", - "integrity": "sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-normalize": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.2.tgz", - "integrity": "sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-print": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.2.tgz", - "integrity": "sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "load-bmfont": "^1.4.0" - } - }, - "@jimp/plugin-resize": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.2.tgz", - "integrity": "sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-rotate": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.2.tgz", - "integrity": "sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-scale": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.2.tgz", - "integrity": "sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-shadow": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.2.tgz", - "integrity": "sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugin-threshold": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.2.tgz", - "integrity": "sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2" - } - }, - "@jimp/plugins": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.2.tgz", - "integrity": "sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.16.2", - "@jimp/plugin-blur": "^0.16.2", - "@jimp/plugin-circle": "^0.16.2", - "@jimp/plugin-color": "^0.16.2", - "@jimp/plugin-contain": "^0.16.2", - "@jimp/plugin-cover": "^0.16.2", - "@jimp/plugin-crop": "^0.16.2", - "@jimp/plugin-displace": "^0.16.2", - "@jimp/plugin-dither": "^0.16.2", - "@jimp/plugin-fisheye": "^0.16.2", - "@jimp/plugin-flip": "^0.16.2", - "@jimp/plugin-gaussian": "^0.16.2", - "@jimp/plugin-invert": "^0.16.2", - "@jimp/plugin-mask": "^0.16.2", - "@jimp/plugin-normalize": "^0.16.2", - "@jimp/plugin-print": "^0.16.2", - "@jimp/plugin-resize": "^0.16.2", - "@jimp/plugin-rotate": "^0.16.2", - "@jimp/plugin-scale": "^0.16.2", - "@jimp/plugin-shadow": "^0.16.2", - "@jimp/plugin-threshold": "^0.16.2", - "timm": "^1.6.1" - } - }, - "@jimp/png": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.2.tgz", - "integrity": "sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.2", - "pngjs": "^3.3.3" - } - }, - "@jimp/tiff": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.2.tgz", - "integrity": "sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==", - "requires": { - "@babel/runtime": "^7.7.2", - "utif": "^2.0.1" - } - }, - "@jimp/types": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.2.tgz", - "integrity": "sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.16.2", - "@jimp/gif": "^0.16.2", - "@jimp/jpeg": "^0.16.2", - "@jimp/png": "^0.16.2", - "@jimp/tiff": "^0.16.2", - "timm": "^1.6.1" - } - }, - "@jimp/utils": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.16.2.tgz", - "integrity": "sha512-XENrPvmigiXZQ8E2nxJqO6UVvWBLzbNwyYi3Y8Q1IECoYhYI3kgOQ0fmy4G269Vz1V0omh1bNmC42r4OfXg1Jg==", - "requires": { - "@babel/runtime": "^7.7.2", - "regenerator-runtime": "^0.13.3" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@root/mkdirp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", - "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" - }, - "@root/request": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.2.tgz", - "integrity": "sha512-wVaL9yVV9oDR9UNbPZa20qgY+4Ch6YN8JUkaE4el/uuS5dmhD8Lusm/ku8qJVNtmQA56XLzEDCRS6/vfpiHK2A==" - }, - "@sendgrid/client": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-6.5.5.tgz", - "integrity": "sha512-Nbfgo94gbWSL8PIgJfuHoifyOJJepvV8NQkkglctAEfb1hyozKhrzE6v1kPG/z4j0RodaTtXD5LJj/t0q/VhLA==", - "requires": { - "@sendgrid/helpers": "^6.5.5", - "@types/request": "^2.48.4", - "request": "^2.88.0" - } - }, - "@sendgrid/helpers": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-6.5.5.tgz", - "integrity": "sha512-uRFEanalfss5hDsuzVXZ1wm7i7eEXHh1py80piOXjobiQ+MxmtR19EU+gDSXZ+uMcEehBGhxnb7QDNN0q65qig==", - "requires": { - "chalk": "^2.0.1", - "deepmerge": "^4.2.2" - } - }, - "@sendgrid/mail": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-6.5.5.tgz", - "integrity": "sha512-DSu8oTPI0BJFH60jMOG9gM+oeNMoRALFmdAYg2PIXpL+Zbxd7L2GzQZtmf1jLy/8UBImkbB3D74TjiOBiLRK1w==", - "requires": { - "@sendgrid/client": "^6.5.5", - "@sendgrid/helpers": "^6.5.5" - } - }, - "@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "requires": { - "@types/node": "*" - } - }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" - }, - "@types/jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", - "requires": { - "@types/node": "*" - } - }, - "@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "requires": { - "@types/bson": "*", - "@types/node": "*" - } - }, - "@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" - }, - "@types/request": { - "version": "2.48.8", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", - "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", - "requires": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - } - }, - "@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acme": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/acme/-/acme-1.3.5.tgz", - "integrity": "sha512-KIFVyMho7y3RxRSTzkuX031TmfXwzl0ioy8+r2pnfLz6YWFQ5q7a/cYUDTgIbrFMPe/syY26Qv1DOdHQ5ARWcw==", - "requires": { - "acme-v2": "^1.8.6" - } - }, - "acme-dns-01-cli": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/acme-dns-01-cli/-/acme-dns-01-cli-3.0.7.tgz", - "integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w==" - }, - "acme-v2": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.8.6.tgz", - "integrity": "sha512-LWdicUYHTGDtYX7LlgsQurmM9txwfAFydg7mQLPKHrFMnNNtfJEtHC2fWfr+pFGNb3XKIbvyFUoyFB6cOmWRpA==", - "requires": { - "@root/request": "^1.3.11", - "rsa-compat": "^2.0.8" - } - }, - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "array-uniq": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz", - "integrity": "sha512-GVYjmpL05al4dNlKJm53mKE4w9OOLiuVHWorsIA3YVz+Hu0hcn6PtE3Ydl0EqU7v+7ABC4mjjWsnLUxbpno+CA==" - }, - "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==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "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==" - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "binary-search": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", - "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" - }, - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==" - }, - "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==" - }, - "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==" - }, - "busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "requires": { - "streamsearch": "^1.1.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" - }, - "cert-info": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", - "integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cron-parser": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.18.0.tgz", - "integrity": "sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==", - "requires": { - "is-nan": "^1.3.0", - "moment-timezone": "^0.5.31" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", - "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==" - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" - } - }, - "dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "eckles": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz", - "integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA==" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "eslint": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", - "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "express-fileupload": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz", - "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==", - "requires": { - "busboy": "^1.6.0" - } - }, - "express-handlebars": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.7.tgz", - "integrity": "sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg==", - "requires": { - "glob": "^8.1.0", - "graceful-fs": "^4.2.10", - "handlebars": "^4.7.7" - } - }, - "express-jwt": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.4.0.tgz", - "integrity": "sha512-AvIkHI6+wuwHQTgfnl4kEPWeMKo5yw4FnXJJK+jf/PRWAflmuJKTs06ENRNJ6sCQceIUVqAi/fy8Nav8alnv0w==", - "requires": { - "@types/jsonwebtoken": "^9", - "express-unless": "^2.1.3", - "jsonwebtoken": "^9.0.0" - } - }, - "express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", - "requires": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "dependencies": { - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" - } - } - }, - "express-unless": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz", - "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==" - }, - "express-validator": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.3.tgz", - "integrity": "sha512-c4b9NMdhskfcLbH/FchsSfCt4Vb14gKzcotG9zLS+VoOJDox57aGhCL+kmAu7cl+ytaSed+HD5jdJhel8DQsdg==", - "requires": { - "lodash": "^4.17.21", - "validator": "^13.7.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" - }, - "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==" - }, - "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==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-type": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", - "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==" - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "generate-rsa-keypair": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/generate-rsa-keypair/-/generate-rsa-keypair-0.2.1.tgz", - "integrity": "sha512-vxLfzfy6WbMLtkKV4AJtg7QH0ZqGGNkSYM6S0Q72Z70QXsztLklKFtX15te3YLIqmiQAYi3g3MWsTfXd6djkpg==" - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "gifwrap": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", - "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", - "requires": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "greenlock": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/greenlock/-/greenlock-2.8.8.tgz", - "integrity": "sha512-U2pqxXXf0naeZc2363Xe174C6/T9lXGZYQjXBqa/PMb1CYRQuHwXlAqFEUu75JkxyHAzFGj/uliqSyQwIc91Yg==", - "requires": { - "acme": "^1.3.5", - "acme-dns-01-cli": "^3.0.0", - "acme-v2": "^1.8.6", - "cert-info": "^1.5.1", - "greenlock-store-fs": "^3.0.2", - "keypairs": "^1.2.14", - "le-challenge-fs": "^2.0.2", - "le-sni-auto": "^2.1.9", - "le-store-certbot": "^2.2.3", - "rsa-compat": "^2.0.8" - } - }, - "greenlock-express": { - "version": "2.7.18", - "resolved": "https://registry.npmjs.org/greenlock-express/-/greenlock-express-2.7.18.tgz", - "integrity": "sha512-8gOSo4twkd4Fdmc45jfHMCXdsYnYutcUGDKZ+FRZH8XQc712glqYNrWaeWThmh0QQ/ZEiV+WGROtAdGrY5sGlQ==", - "requires": { - "greenlock": "^2.8.8", - "redirect-https": "^1.1.5" - } - }, - "greenlock-store-fs": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.2.tgz", - "integrity": "sha512-92ejLB4DyV4qv/2b6VLGF2nKfYQeIfg3o+e/1cIoYLjlIaUFdbBXkzLTRozFlHsQPZt2ALi5qYrpC9IwH7GK8A==", - "requires": { - "@root/mkdirp": "^1.0.0", - "safe-replace": "^1.1.0" - } - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "entities": "^4.3.0" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "ical": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/ical/-/ical-0.6.0.tgz", - "integrity": "sha512-P7R3uMhFnLY7Tfiz+2uZIninThWIoUM5BVO4f9ZsV36GYqoBkIg6RbkrBhmWCvcuK1vLzljLaNtpWeGM/5ZHRg==", - "requires": { - "request": "^2.88.0", - "rrule": "2.4.1" - } - }, - "ical-generator": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/ical-generator/-/ical-generator-1.15.4.tgz", - "integrity": "sha512-drXe4RLkfNlvDvdy/E6BUI9p+01L3ySK1ufNEYI9TxNKG9ZA3G60QWoZvD1dtmH4scwDxYu6/sZBPJvYVNrj8A==", - "requires": { - "moment-timezone": "^0.5.32" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "requires": { - "@types/node": "16.9.1" - }, - "dependencies": { - "@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" - } - } - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" - }, - "jimp": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz", - "integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==", - "requires": { - "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.16.2", - "@jimp/plugins": "^0.16.2", - "@jimp/types": "^0.16.2", - "regenerator-runtime": "^0.13.3" - } - }, - "jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" - }, - "js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "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==" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "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==" - }, - "jsonwebtoken": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", - "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", - "requires": { - "jws": "^3.2.2", - "lodash": "^4.17.21", - "ms": "^2.1.1", - "semver": "^7.3.8" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" - }, - "keypairs": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz", - "integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==", - "requires": { - "eckles": "^1.4.1", - "rasha": "^1.2.4" - } - }, - "le-challenge-fs": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/le-challenge-fs/-/le-challenge-fs-2.0.9.tgz", - "integrity": "sha512-stzI6rxd+aXGxBl87QJKKY/i/wl3uz6EoWzX2xSazJvCPSYBQys1RVNgOcf0SfUQPh6TBCFJFSJkiR4mznb4sg==", - "requires": { - "@root/mkdirp": "^1.0.0" - } - }, - "le-sni-auto": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/le-sni-auto/-/le-sni-auto-2.1.9.tgz", - "integrity": "sha512-QmQHNwQDi/56GY8+qczFZ06FZbxaeJQjbjEhwwQHhkJ9IHhIQFkPfCT/OyDfLj4gqLIrg5ZX8CemxxVZnLEYfg==" - }, - "le-store-certbot": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/le-store-certbot/-/le-store-certbot-2.2.3.tgz", - "integrity": "sha512-c4ACR+v+JKMiAOOshLh6gdCKA7wIWR16+mROMLpQjq3rXJ3Vm8FaBHe2H+crT+flP+g7FmciAwUlfOJEJpIuCQ==", - "requires": { - "@root/mkdirp": "^1.0.0", - "pyconf": "^1.1.7", - "safe-replace": "^1.1.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "load-bmfont": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", - "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", - "requires": { - "buffer-equal": "0.0.1", - "mime": "^1.3.4", - "parse-bmfont-ascii": "^1.0.3", - "parse-bmfont-binary": "^1.0.5", - "parse-bmfont-xml": "^1.1.4", - "phin": "^2.9.1", - "xhr": "^2.0.1", - "xtend": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "long-timeout": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", - "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "luxon": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", - "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", - "optional": true - }, - "marked": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", - "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "moment-timezone": { - "version": "0.5.40", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", - "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", - "requires": { - "moment": ">= 2.9.0" - } - }, - "mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", - "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - }, - "dependencies": { - "optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "requires": { - "require-at": "^1.0.6" - } - } - } - }, - "mongoose": { - "version": "5.13.15", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.15.tgz", - "integrity": "sha512-cxp1Gbb8yUWkaEbajdhspSaKzAvsIvOtRlYD87GN/P2QEUhpd6bIvebi36T6M0tIVAMauNaK9SPA055N3PwF8Q==", - "requires": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.7.3", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "requires": {} - }, - "mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==" - }, - "mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", - "requires": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", - "requires": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - } - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "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==" - }, - "niceware": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/niceware/-/niceware-3.0.0.tgz", - "integrity": "sha512-DbeDuqe836Ba4S9vjim4jTbbqmjCMwuAXFCVdh4QAvbmLOhmIQs84IakYrcXd/87VCsj1XKhSmmg7bAmwAEh5A==", - "requires": { - "binary-search": "^1.3.6", - "randombytes": "^2.0.6" - } - }, - "node-schedule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.3.tgz", - "integrity": "sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw==", - "requires": { - "cron-parser": "^2.18.0", - "long-timeout": "0.1.1", - "sorted-array-functions": "^1.3.0" - } - }, - "nodemailer": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz", - "integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==" - }, - "nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "dev": true, - "requires": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" - }, - "parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" - }, - "parse-bmfont-xml": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", - "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", - "requires": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.4.5" - } - }, - "parse-headers": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", - "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" - }, - "parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" - }, - "phin": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", - "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pixelmatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", - "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", - "requires": { - "pngjs": "^3.0.0" - } - }, - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" - }, - "postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, - "pyconf": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pyconf/-/pyconf-1.1.7.tgz", - "integrity": "sha512-v4clh33m68sjtMsh8XMpjhGWb/MQODAYZ1y7ORG5Qv58UK25OddoB+oXyexgDkK8ttFui/lZm2sQDgA2Ftjfkw==", - "requires": { - "safe-replace": "^1.0.2" - } - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomstring": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/randomstring/-/randomstring-1.2.3.tgz", - "integrity": "sha512-3dEFySepTzp2CvH6W/ASYGguPPveBuz5MpZ7MuoUkoVehmyNl9+F9c9GFVrz2QPbM9NXTIHGcmJDY/3j4677kQ==", - "requires": { - "array-uniq": "1.0.2", - "randombytes": "2.0.3" - }, - "dependencies": { - "randombytes": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz", - "integrity": "sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==" - } - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "rasha": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.5.tgz", - "integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redirect-https": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.1.tgz", - "integrity": "sha512-Stex2nI+tMpZXKvy++32TiBXEy+GdpAfp3EUnl5BqCiJ5f5i6XvUSFrs7TR7IoRSlthM7ZtD89uYGTtJBXlFYg==", - "requires": { - "escape-html": "^1.0.3" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "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" - }, - "dependencies": { - "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==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" - } - } - }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "rrule": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.4.1.tgz", - "integrity": "sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==", - "requires": { - "luxon": "^1.3.3" - } - }, - "rsa-compat": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz", - "integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==", - "requires": { - "keypairs": "^1.2.14" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", - "integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sanitize-html": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.9.0.tgz", - "integrity": "sha512-KY1hpSbqFNcpoLf+nP7iStbP5JfQZ2Nd19ZEE7qFsQqRdp+sO5yX/e5+HoG9puFAcSTEpzQuihfKUltDcLlQjg==", - "requires": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - } - } - }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" - }, - "simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "requires": { - "semver": "~7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" - }, - "sorted-array-functions": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", - "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "requires": { - "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" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "timm": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", - "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" - }, - "tinycolor2": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", - "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, - "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==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "optional": true - }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } - }, - "undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "utif": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", - "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", - "requires": { - "pako": "^1.0.5" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "dependencies": { - "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==" - } - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", - "requires": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "xml-parse-from-string": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", - "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" - }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..4fad6cf --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2817 @@ +lockfileVersion: '6.0' + +dependencies: + '@sendgrid/mail': + specifier: ^6.5.5 + version: 6.5.5 + body-parser: + specifier: ^1.18.3 + version: 1.20.1 + cors: + specifier: ^2.8.5 + version: 2.8.5 + dotenv: + specifier: ^6.1.0 + version: 6.2.0 + express: + specifier: ^4.16.4 + version: 4.18.2 + express-fileupload: + specifier: ^1.1.9 + version: 1.4.0 + express-handlebars: + specifier: ^6.0.5 + version: 6.0.7 + express-session: + specifier: ^1.17.1 + version: 1.17.3 + express-validator: + specifier: ^6.14.0 + version: 6.14.3 + generate-rsa-keypair: + specifier: ^0.2.1 + version: 0.2.1 + ical: + specifier: ^0.6.0 + version: 0.6.0 + ical-generator: + specifier: ^1.11.0 + version: 1.15.4(@types/node@18.11.18) + jimp: + specifier: ^0.16.1 + version: 0.16.2 + marked: + specifier: ^4.0.10 + version: 4.2.12 + moment-timezone: + specifier: ^0.5.31 + version: 0.5.40 + mongoose: + specifier: ^5.9.18 + version: 5.13.15 + nanoid: + specifier: ^3.1.9 + version: 3.3.4 + niceware: + specifier: ^3.0.0 + version: 3.0.0 + node-schedule: + specifier: ^1.3.1 + version: 1.3.3 + nodemailer: + specifier: ^6.4.8 + version: 6.9.1 + randomstring: + specifier: ^1.1.5 + version: 1.2.3 + request: + specifier: ^2.88.2 + version: 2.88.2 + sanitize-html: + specifier: ^2.6.1 + version: 2.9.0 + +devDependencies: + eslint: + specifier: ^8.4.1 + version: 8.33.0 + nodemon: + specifier: ^2.0.15 + version: 2.0.20 + +packages: + + /@babel/runtime@7.20.13: + resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: false + + /@eslint/eslintrc@1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.1 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/config-array@0.11.8: + resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@jimp/bmp@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + bmp-js: 0.1.0 + dev: false + + /@jimp/core@0.16.2: + resolution: {integrity: sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==} + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/utils': 0.16.2 + any-base: 1.1.0 + buffer: 5.7.1 + exif-parser: 0.1.12 + file-type: 9.0.0 + load-bmfont: 1.4.1 + mkdirp: 0.5.6 + phin: 2.9.3 + pixelmatch: 4.0.2 + tinycolor2: 1.5.2 + dev: false + + /@jimp/custom@0.16.2: + resolution: {integrity: sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==} + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/core': 0.16.2 + dev: false + + /@jimp/gif@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + gifwrap: 0.9.4 + omggif: 1.0.10 + dev: false + + /@jimp/jpeg@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-BW5gZydgq6wdIwHd+3iUNgrTklvoQc/FUKSj9meM6A0FU21lUaansRX5BDdJqHkyXJLnnlDGwDt27J+hQuBAVw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + jpeg-js: 0.4.4 + dev: false + + /@jimp/plugin-blit@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-blur@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-circle@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-color@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + tinycolor2: 1.5.2 + dev: false + + /@jimp/plugin-contain@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2): + resolution: {integrity: sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-blit': '>=0.3.5' + '@jimp/plugin-resize': '>=0.3.5' + '@jimp/plugin-scale': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-scale': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2) + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-cover@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2): + resolution: {integrity: sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-crop': '>=0.3.5' + '@jimp/plugin-resize': '>=0.3.5' + '@jimp/plugin-scale': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-crop': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-scale': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2) + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-crop@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-displace@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-dither@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-fisheye@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-flip@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-rotate@0.16.2): + resolution: {integrity: sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-rotate': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-rotate': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2) + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-gaussian@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-invert@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-mask@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-normalize@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-print@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2): + resolution: {integrity: sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-blit': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) + '@jimp/utils': 0.16.2 + load-bmfont: 1.4.1 + dev: false + + /@jimp/plugin-resize@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-rotate@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2): + resolution: {integrity: sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-blit': '>=0.3.5' + '@jimp/plugin-crop': '>=0.3.5' + '@jimp/plugin-resize': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-crop': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-scale@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2): + resolution: {integrity: sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-resize': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-shadow@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blur@0.16.2)(@jimp/plugin-resize@0.16.2): + resolution: {integrity: sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-blur': '>=0.3.5' + '@jimp/plugin-resize': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-blur': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugin-threshold@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-color@0.16.2)(@jimp/plugin-resize@0.16.2): + resolution: {integrity: sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + '@jimp/plugin-color': '>=0.8.0' + '@jimp/plugin-resize': '>=0.8.0' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-color': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/utils': 0.16.2 + dev: false + + /@jimp/plugins@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-blur': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-circle': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-color': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-contain': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2) + '@jimp/plugin-cover': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2) + '@jimp/plugin-crop': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-displace': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-dither': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-fisheye': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-flip': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-rotate@0.16.2) + '@jimp/plugin-gaussian': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-invert': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-mask': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-normalize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-print': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2) + '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) + '@jimp/plugin-rotate': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2) + '@jimp/plugin-scale': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2) + '@jimp/plugin-shadow': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blur@0.16.2)(@jimp/plugin-resize@0.16.2) + '@jimp/plugin-threshold': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-color@0.16.2)(@jimp/plugin-resize@0.16.2) + timm: 1.7.1 + dev: false + + /@jimp/png@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/utils': 0.16.2 + pngjs: 3.4.0 + dev: false + + /@jimp/tiff@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + utif: 2.0.1 + dev: false + + /@jimp/types@0.16.2(@jimp/custom@0.16.2): + resolution: {integrity: sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==} + peerDependencies: + '@jimp/custom': '>=0.3.5' + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/bmp': 0.16.2(@jimp/custom@0.16.2) + '@jimp/custom': 0.16.2 + '@jimp/gif': 0.16.2(@jimp/custom@0.16.2) + '@jimp/jpeg': 0.16.2(@jimp/custom@0.16.2) + '@jimp/png': 0.16.2(@jimp/custom@0.16.2) + '@jimp/tiff': 0.16.2(@jimp/custom@0.16.2) + timm: 1.7.1 + dev: false + + /@jimp/utils@0.16.2: + resolution: {integrity: sha512-XENrPvmigiXZQ8E2nxJqO6UVvWBLzbNwyYi3Y8Q1IECoYhYI3kgOQ0fmy4G269Vz1V0omh1bNmC42r4OfXg1Jg==} + dependencies: + '@babel/runtime': 7.20.13 + regenerator-runtime: 0.13.11 + dev: false + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@sendgrid/client@6.5.5: + resolution: {integrity: sha512-Nbfgo94gbWSL8PIgJfuHoifyOJJepvV8NQkkglctAEfb1hyozKhrzE6v1kPG/z4j0RodaTtXD5LJj/t0q/VhLA==} + engines: {node: '>=6.0.0'} + dependencies: + '@sendgrid/helpers': 6.5.5 + '@types/request': 2.48.8 + request: 2.88.2 + dev: false + + /@sendgrid/helpers@6.5.5: + resolution: {integrity: sha512-uRFEanalfss5hDsuzVXZ1wm7i7eEXHh1py80piOXjobiQ+MxmtR19EU+gDSXZ+uMcEehBGhxnb7QDNN0q65qig==} + engines: {node: '>= 6.0.0'} + dependencies: + chalk: 2.4.2 + deepmerge: 4.3.0 + dev: false + + /@sendgrid/mail@6.5.5: + resolution: {integrity: sha512-DSu8oTPI0BJFH60jMOG9gM+oeNMoRALFmdAYg2PIXpL+Zbxd7L2GzQZtmf1jLy/8UBImkbB3D74TjiOBiLRK1w==} + engines: {node: '>=6.0.0'} + dependencies: + '@sendgrid/client': 6.5.5 + '@sendgrid/helpers': 6.5.5 + dev: false + + /@types/bson@4.0.5: + resolution: {integrity: sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==} + dependencies: + '@types/node': 18.11.18 + dev: false + + /@types/caseless@0.12.2: + resolution: {integrity: sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==} + dev: false + + /@types/mongodb@3.6.20: + resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} + dependencies: + '@types/bson': 4.0.5 + '@types/node': 18.11.18 + dev: false + + /@types/node@16.9.1: + resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} + dev: false + + /@types/node@18.11.18: + resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} + dev: false + + /@types/request@2.48.8: + resolution: {integrity: sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==} + dependencies: + '@types/caseless': 0.12.2 + '@types/node': 18.11.18 + '@types/tough-cookie': 4.0.2 + form-data: 2.5.1 + dev: false + + /@types/tough-cookie@4.0.2: + resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} + dev: false + + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /acorn-jsx@5.3.2(acorn@8.8.2): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.2 + dev: true + + /acorn@8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: false + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /any-base@1.1.0: + resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} + dev: false + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + + /array-uniq@1.0.2: + resolution: {integrity: sha512-GVYjmpL05al4dNlKJm53mKE4w9OOLiuVHWorsIA3YVz+Hu0hcn6PtE3Ydl0EqU7v+7ABC4mjjWsnLUxbpno+CA==} + engines: {node: '>=0.10.0'} + dev: false + + /asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + dev: false + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + dev: false + + /aws4@1.12.0: + resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false + + /bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + dependencies: + tweetnacl: 0.14.5 + dev: false + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /binary-search@1.3.6: + resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} + dev: false + + /bl@2.2.1: + resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} + dependencies: + readable-stream: 2.3.7 + safe-buffer: 5.2.1 + dev: false + + /bluebird@3.5.1: + resolution: {integrity: sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==} + dev: false + + /bmp-js@0.1.0: + resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==} + dev: false + + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /bson@1.1.6: + resolution: {integrity: sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==} + engines: {node: '>=0.6.19'} + dev: false + + /buffer-equal@0.0.1: + resolution: {integrity: sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==} + engines: {node: '>=0.4.0'} + dev: false + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.0 + dev: false + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: false + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: false + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: false + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + dev: false + + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false + + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: false + + /cron-parser@2.18.0: + resolution: {integrity: sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==} + engines: {node: '>=0.8'} + dependencies: + is-nan: 1.3.2 + moment-timezone: 0.5.40 + dev: false + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + dependencies: + assert-plus: 1.0.0 + dev: false + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /debug@3.1.0: + resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /debug@3.2.7(supports-color@5.5.0): + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + supports-color: 5.5.0 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /deepmerge@4.3.0: + resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} + engines: {node: '>=0.10.0'} + dev: false + + /define-properties@1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: false + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /denque@1.5.1: + resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} + engines: {node: '>=0.10'} + dev: false + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.4.0 + dev: false + + /dom-walk@0.1.2: + resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} + dev: false + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils@3.0.1: + resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: false + + /dotenv@6.2.0: + resolution: {integrity: sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==} + engines: {node: '>=6'} + dev: false + + /ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + dev: false + + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + + /entities@4.4.0: + resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + engines: {node: '>=0.12'} + dev: false + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: false + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + /eslint-scope@7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils@3.0.0(eslint@8.33.0): + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.33.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys@3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.33.0: + resolution: {integrity: sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0(eslint@8.33.0) + eslint-visitor-keys: 3.3.0 + espree: 9.4.1 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.3.0 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.4.1: + resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.2 + acorn-jsx: 5.3.2(acorn@8.8.2) + eslint-visitor-keys: 3.3.0 + dev: true + + /esquery@1.4.0: + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /exif-parser@0.1.12: + resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} + dev: false + + /express-fileupload@1.4.0: + resolution: {integrity: sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==} + engines: {node: '>=12.0.0'} + dependencies: + busboy: 1.6.0 + dev: false + + /express-handlebars@6.0.7: + resolution: {integrity: sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg==} + engines: {node: '>=v12.22.9'} + dependencies: + glob: 8.1.0 + graceful-fs: 4.2.10 + handlebars: 4.7.7 + dev: false + + /express-session@1.17.3: + resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==} + engines: {node: '>= 0.8.0'} + dependencies: + cookie: 0.4.2 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + on-headers: 1.0.2 + parseurl: 1.3.3 + safe-buffer: 5.2.1 + uid-safe: 2.1.5 + transitivePeerDependencies: + - supports-color + dev: false + + /express-validator@6.14.3: + resolution: {integrity: sha512-c4b9NMdhskfcLbH/FchsSfCt4Vb14gKzcotG9zLS+VoOJDox57aGhCL+kmAu7cl+ytaSed+HD5jdJhel8DQsdg==} + engines: {node: '>= 8.0.0'} + dependencies: + lodash: 4.17.21 + validator: 13.7.0 + dev: false + + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + + /extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + dev: false + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /file-type@9.0.0: + resolution: {integrity: sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==} + engines: {node: '>=6'} + dev: false + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + dev: false + + /form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /form-data@2.5.1: + resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: false + + /generate-rsa-keypair@0.2.1: + resolution: {integrity: sha512-vxLfzfy6WbMLtkKV4AJtg7QH0ZqGGNkSYM6S0Q72Z70QXsztLklKFtX15te3YLIqmiQAYi3g3MWsTfXd6djkpg==} + engines: {node: '>=8.6.0'} + requiresBuild: true + dev: false + + /get-intrinsic@1.2.0: + resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: false + + /getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + dependencies: + assert-plus: 1.0.0 + dev: false + + /gifwrap@0.9.4: + resolution: {integrity: sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==} + dependencies: + image-q: 4.0.0 + omggif: 1.0.10 + dev: false + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: false + + /global@4.4.0: + resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} + dependencies: + min-document: 2.19.0 + process: 0.11.10 + dev: false + + /globals@13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: false + + /grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /handlebars@4.7.7: + resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.7 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: false + + /har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + dev: false + + /har-validator@5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + dev: false + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.0 + dev: false + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: false + + /htmlparser2@8.0.1: + resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 + entities: 4.4.0 + dev: false + + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.17.0 + dev: false + + /ical-generator@1.15.4(@types/node@18.11.18): + resolution: {integrity: sha512-drXe4RLkfNlvDvdy/E6BUI9p+01L3ySK1ufNEYI9TxNKG9ZA3G60QWoZvD1dtmH4scwDxYu6/sZBPJvYVNrj8A==} + engines: {node: '>=6.0.0'} + peerDependencies: + '@types/node': '>= 8.0.0' + dependencies: + '@types/node': 18.11.18 + moment-timezone: 0.5.40 + dev: false + + /ical@0.6.0: + resolution: {integrity: sha512-P7R3uMhFnLY7Tfiz+2uZIninThWIoUM5BVO4f9ZsV36GYqoBkIg6RbkrBhmWCvcuK1vLzljLaNtpWeGM/5ZHRg==} + dependencies: + request: 2.88.2 + rrule: 2.4.1 + dev: false + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + + /ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + dev: true + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /image-q@4.0.0: + resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} + dependencies: + '@types/node': 16.9.1 + dev: false + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-function@1.0.2: + resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} + dev: false + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + dev: false + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + dev: false + + /is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + dev: false + + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: false + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + dev: false + + /jimp@0.16.2: + resolution: {integrity: sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==} + dependencies: + '@babel/runtime': 7.20.13 + '@jimp/custom': 0.16.2 + '@jimp/plugins': 0.16.2(@jimp/custom@0.16.2) + '@jimp/types': 0.16.2(@jimp/custom@0.16.2) + regenerator-runtime: 0.13.11 + dev: false + + /jpeg-js@0.4.4: + resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} + dev: false + + /js-sdsl@4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + dev: false + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + /json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: false + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: false + + /jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + dev: false + + /kareem@2.3.2: + resolution: {integrity: sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==} + dev: false + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /load-bmfont@1.4.1: + resolution: {integrity: sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==} + dependencies: + buffer-equal: 0.0.1 + mime: 1.6.0 + parse-bmfont-ascii: 1.0.6 + parse-bmfont-binary: 1.0.6 + parse-bmfont-xml: 1.1.4 + phin: 2.9.3 + xhr: 2.6.0 + xtend: 4.0.2 + dev: false + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /long-timeout@0.1.1: + resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} + dev: false + + /luxon@1.28.1: + resolution: {integrity: sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==} + requiresBuild: true + dev: false + optional: true + + /marked@4.2.12: + resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} + engines: {node: '>= 12'} + hasBin: true + dev: false + + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + dev: false + optional: true + + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /min-document@2.19.0: + resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} + dependencies: + dom-walk: 0.1.2 + dev: false + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimist@1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: false + + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.7 + dev: false + + /moment-timezone@0.5.40: + resolution: {integrity: sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==} + dependencies: + moment: 2.29.4 + dev: false + + /moment@2.29.4: + resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} + dev: false + + /mongodb@3.7.3: + resolution: {integrity: sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==} + engines: {node: '>=4'} + peerDependencies: + aws4: '*' + bson-ext: '*' + kerberos: '*' + mongodb-client-encryption: '*' + mongodb-extjson: '*' + snappy: '*' + peerDependenciesMeta: + aws4: + optional: true + bson-ext: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + mongodb-extjson: + optional: true + snappy: + optional: true + dependencies: + bl: 2.2.1 + bson: 1.1.6 + denque: 1.5.1 + optional-require: 1.1.8 + safe-buffer: 5.2.1 + optionalDependencies: + saslprep: 1.0.3 + dev: false + + /mongoose-legacy-pluralize@1.0.2(mongoose@5.13.15): + resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==} + peerDependencies: + mongoose: '*' + dependencies: + mongoose: 5.13.15 + dev: false + + /mongoose@5.13.15: + resolution: {integrity: sha512-cxp1Gbb8yUWkaEbajdhspSaKzAvsIvOtRlYD87GN/P2QEUhpd6bIvebi36T6M0tIVAMauNaK9SPA055N3PwF8Q==} + engines: {node: '>=4.0.0'} + dependencies: + '@types/bson': 4.0.5 + '@types/mongodb': 3.6.20 + bson: 1.1.6 + kareem: 2.3.2 + mongodb: 3.7.3 + mongoose-legacy-pluralize: 1.0.2(mongoose@5.13.15) + mpath: 0.8.4 + mquery: 3.2.5 + ms: 2.1.2 + optional-require: 1.0.3 + regexp-clone: 1.0.0 + safe-buffer: 5.2.1 + sift: 13.5.2 + sliced: 1.0.1 + transitivePeerDependencies: + - aws4 + - bson-ext + - kerberos + - mongodb-client-encryption + - mongodb-extjson + - snappy + - supports-color + dev: false + + /mpath@0.8.4: + resolution: {integrity: sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==} + engines: {node: '>=4.0.0'} + dev: false + + /mquery@3.2.5: + resolution: {integrity: sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==} + engines: {node: '>=4.0.0'} + dependencies: + bluebird: 3.5.1 + debug: 3.1.0 + regexp-clone: 1.0.0 + safe-buffer: 5.1.2 + sliced: 1.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /nanoid@3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: false + + /niceware@3.0.0: + resolution: {integrity: sha512-DbeDuqe836Ba4S9vjim4jTbbqmjCMwuAXFCVdh4QAvbmLOhmIQs84IakYrcXd/87VCsj1XKhSmmg7bAmwAEh5A==} + dependencies: + binary-search: 1.3.6 + randombytes: 2.1.0 + dev: false + + /node-schedule@1.3.3: + resolution: {integrity: sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw==} + dependencies: + cron-parser: 2.18.0 + long-timeout: 0.1.1 + sorted-array-functions: 1.3.0 + dev: false + + /nodemailer@6.9.1: + resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==} + engines: {node: '>=6.0.0'} + dev: false + + /nodemon@2.0.20: + resolution: {integrity: sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==} + engines: {node: '>=8.10.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + debug: 3.2.7(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 5.7.1 + simple-update-notifier: 1.1.0 + supports-color: 5.5.0 + touch: 3.1.0 + undefsafe: 2.0.5 + dev: true + + /nopt@1.0.10: + resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + dev: false + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: false + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: false + + /omggif@1.0.10: + resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} + dev: false + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + dev: false + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /optional-require@1.0.3: + resolution: {integrity: sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==} + engines: {node: '>=4'} + dev: false + + /optional-require@1.1.8: + resolution: {integrity: sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==} + engines: {node: '>=4'} + dependencies: + require-at: 1.0.6 + dev: false + + /optionator@0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-bmfont-ascii@1.0.6: + resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} + dev: false + + /parse-bmfont-binary@1.0.6: + resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} + dev: false + + /parse-bmfont-xml@1.1.4: + resolution: {integrity: sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==} + dependencies: + xml-parse-from-string: 1.0.1 + xml2js: 0.4.23 + dev: false + + /parse-headers@2.0.5: + resolution: {integrity: sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==} + dev: false + + /parse-srcset@1.0.2: + resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} + dev: false + + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + + /performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + dev: false + + /phin@2.9.3: + resolution: {integrity: sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==} + dev: false + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pixelmatch@4.0.2: + resolution: {integrity: sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==} + hasBin: true + dependencies: + pngjs: 3.4.0 + dev: false + + /pngjs@3.4.0: + resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + engines: {node: '>=4.0.0'} + dev: false + + /postcss@8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: false + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: false + + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: false + + /pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + dev: true + + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + + /qs@6.5.3: + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} + dev: false + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /random-bytes@1.0.0: + resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} + engines: {node: '>= 0.8'} + dev: false + + /randombytes@2.0.3: + resolution: {integrity: sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==} + dev: false + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /randomstring@1.2.3: + resolution: {integrity: sha512-3dEFySepTzp2CvH6W/ASYGguPPveBuz5MpZ7MuoUkoVehmyNl9+F9c9GFVrz2QPbM9NXTIHGcmJDY/3j4677kQ==} + hasBin: true + dependencies: + array-uniq: 1.0.2 + randombytes: 2.0.3 + dev: false + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /readable-stream@2.3.7: + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: false + + /regexp-clone@1.0.0: + resolution: {integrity: sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==} + dev: false + + /regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + + /request@2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + dependencies: + aws-sign2: 0.7.0 + aws4: 1.12.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + dev: false + + /require-at@1.0.6: + resolution: {integrity: sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==} + engines: {node: '>=4'} + dev: false + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rrule@2.4.1: + resolution: {integrity: sha512-+NcvhETefswZq13T8nkuEnnQ6YgUeZaqMqVbp+ZiFDPCbp3AVgQIwUvNVDdMNrP05bKZG9ddDULFp0qZZYDrxg==} + optionalDependencies: + luxon: 1.28.1 + dev: false + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: false + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + + /sanitize-html@2.9.0: + resolution: {integrity: sha512-KY1hpSbqFNcpoLf+nP7iStbP5JfQZ2Nd19ZEE7qFsQqRdp+sO5yX/e5+HoG9puFAcSTEpzQuihfKUltDcLlQjg==} + dependencies: + deepmerge: 4.3.0 + escape-string-regexp: 4.0.0 + htmlparser2: 8.0.1 + is-plain-object: 5.0.0 + parse-srcset: 1.0.2 + postcss: 8.4.21 + dev: false + + /saslprep@1.0.3: + resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + sparse-bitfield: 3.0.3 + dev: false + optional: true + + /sax@1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + dev: false + + /semver@5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + + /semver@7.0.0: + resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} + hasBin: true + dev: true + + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + object-inspect: 1.12.3 + dev: false + + /sift@13.5.2: + resolution: {integrity: sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==} + dev: false + + /simple-update-notifier@1.1.0: + resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} + engines: {node: '>=8.10.0'} + dependencies: + semver: 7.0.0 + dev: true + + /sliced@1.0.1: + resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==} + dev: false + + /sorted-array-functions@1.3.0: + resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} + dev: false + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: false + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + + /sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + dependencies: + memory-pager: 1.5.0 + dev: false + optional: true + + /sshpk@1.17.0: + resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + dev: false + + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: false + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /timm@1.7.1: + resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==} + dev: false + + /tinycolor2@1.5.2: + resolution: {integrity: sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==} + dev: false + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /touch@3.1.0: + resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} + hasBin: true + dependencies: + nopt: 1.0.10 + dev: true + + /tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + dependencies: + psl: 1.9.0 + punycode: 2.3.0 + dev: false + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + dev: false + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: false + optional: true + + /uid-safe@2.1.5: + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} + dependencies: + random-bytes: 1.0.0 + dev: false + + /undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + dev: true + + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + + /utif@2.0.1: + resolution: {integrity: sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==} + dependencies: + pako: 1.0.11 + dev: false + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: false + + /uuid@3.4.0: + resolution: {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. + hasBin: true + dev: false + + /validator@13.7.0: + resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==} + engines: {node: '>= 0.10'} + dev: false + + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + dev: false + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /word-wrap@1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: false + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /xhr@2.6.0: + resolution: {integrity: sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==} + dependencies: + global: 4.4.0 + is-function: 1.0.2 + parse-headers: 2.0.5 + xtend: 4.0.2 + dev: false + + /xml-parse-from-string@1.0.1: + resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} + dev: false + + /xml2js@0.4.23: + resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} + engines: {node: '>=4.0.0'} + dependencies: + sax: 1.2.4 + xmlbuilder: 11.0.1 + dev: false + + /xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + dev: false + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true -- cgit v1.2.3 From 87d52fd6baeb782564a04ef28329879da689659d Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:05 +0100 Subject: remove unused packages --- package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package.json b/package.json index 8963fd2..481967c 100644 --- a/package.json +++ b/package.json @@ -20,20 +20,15 @@ "express": "^4.16.4", "express-fileupload": "^1.1.9", "express-handlebars": "^6.0.5", - "express-jwt": "^8.4.0", "express-session": "^1.17.1", "express-validator": "^6.14.0", "generate-rsa-keypair": "^0.2.1", - "greenlock": "^2.8.8", - "greenlock-express": "^2.7.18", "ical": "^0.6.0", "ical-generator": "^1.11.0", "jimp": "^0.16.1", - "jsonwebtoken": "^9.0.0", "marked": "^4.0.10", "moment-timezone": "^0.5.31", "mongoose": "^5.9.18", - "multer": "^1.4.5-lts.1", "nanoid": "^3.1.9", "niceware": "^3.0.0", "node-schedule": "^1.3.1", -- cgit v1.2.3 From 69b4c854b399554ed413cc7ff9d625aee5053927 Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:05 +0100 Subject: upgrade dependencies --- package.json | 38 +-- pnpm-lock.yaml | 767 +++++++++++++++++++++++++++++++-------------------------- 2 files changed, 443 insertions(+), 362 deletions(-) diff --git a/package.json b/package.json index 481967c..a5c41b5 100644 --- a/package.json +++ b/package.json @@ -14,31 +14,31 @@ "license": "GPL-3.0-or-later", "dependencies": { "@sendgrid/mail": "^6.5.5", - "body-parser": "^1.18.3", + "body-parser": "^1.20.2", "cors": "^2.8.5", - "dotenv": "^6.1.0", - "express": "^4.16.4", - "express-fileupload": "^1.1.9", - "express-handlebars": "^6.0.5", - "express-session": "^1.17.1", - "express-validator": "^6.14.0", + "dotenv": "^6.2.0", + "express": "^4.18.2", + "express-fileupload": "^1.4.0", + "express-handlebars": "^6.0.7", + "express-session": "^1.17.3", + "express-validator": "^6.15.0", "generate-rsa-keypair": "^0.2.1", "ical": "^0.6.0", - "ical-generator": "^1.11.0", - "jimp": "^0.16.1", - "marked": "^4.0.10", - "moment-timezone": "^0.5.31", - "mongoose": "^5.9.18", - "nanoid": "^3.1.9", + "ical-generator": "^1.15.4", + "jimp": "^0.16.13", + "marked": "^4.3.0", + "moment-timezone": "^0.5.43", + "mongoose": "^5.13.17", + "nanoid": "^3.3.6", "niceware": "^3.0.0", - "node-schedule": "^1.3.1", - "nodemailer": "^6.4.8", - "randomstring": "^1.1.5", + "node-schedule": "^1.3.3", + "nodemailer": "^6.9.2", + "randomstring": "^1.2.3", "request": "^2.88.2", - "sanitize-html": "^2.6.1" + "sanitize-html": "^2.10.0" }, "devDependencies": { - "eslint": "^8.4.1", - "nodemon": "^2.0.15" + "eslint": "^8.40.0", + "nodemon": "^2.0.22" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4fad6cf..484ba4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,29 +5,29 @@ dependencies: specifier: ^6.5.5 version: 6.5.5 body-parser: - specifier: ^1.18.3 - version: 1.20.1 + specifier: ^1.20.2 + version: 1.20.2 cors: specifier: ^2.8.5 version: 2.8.5 dotenv: - specifier: ^6.1.0 + specifier: ^6.2.0 version: 6.2.0 express: - specifier: ^4.16.4 + specifier: ^4.18.2 version: 4.18.2 express-fileupload: - specifier: ^1.1.9 + specifier: ^1.4.0 version: 1.4.0 express-handlebars: - specifier: ^6.0.5 + specifier: ^6.0.7 version: 6.0.7 express-session: - specifier: ^1.17.1 + specifier: ^1.17.3 version: 1.17.3 express-validator: - specifier: ^6.14.0 - version: 6.14.3 + specifier: ^6.15.0 + version: 6.15.0 generate-rsa-keypair: specifier: ^0.2.1 version: 0.2.1 @@ -35,66 +35,81 @@ dependencies: specifier: ^0.6.0 version: 0.6.0 ical-generator: - specifier: ^1.11.0 - version: 1.15.4(@types/node@18.11.18) + specifier: ^1.15.4 + version: 1.15.4 jimp: - specifier: ^0.16.1 - version: 0.16.2 + specifier: ^0.16.13 + version: 0.16.13 marked: - specifier: ^4.0.10 - version: 4.2.12 + specifier: ^4.3.0 + version: 4.3.0 moment-timezone: - specifier: ^0.5.31 - version: 0.5.40 + specifier: ^0.5.43 + version: 0.5.43 mongoose: - specifier: ^5.9.18 - version: 5.13.15 + specifier: ^5.13.17 + version: 5.13.17 nanoid: - specifier: ^3.1.9 - version: 3.3.4 + specifier: ^3.3.6 + version: 3.3.6 niceware: specifier: ^3.0.0 version: 3.0.0 node-schedule: - specifier: ^1.3.1 + specifier: ^1.3.3 version: 1.3.3 nodemailer: - specifier: ^6.4.8 - version: 6.9.1 + specifier: ^6.9.2 + version: 6.9.2 randomstring: - specifier: ^1.1.5 + specifier: ^1.2.3 version: 1.2.3 request: specifier: ^2.88.2 version: 2.88.2 sanitize-html: - specifier: ^2.6.1 - version: 2.9.0 + specifier: ^2.10.0 + version: 2.10.0 devDependencies: eslint: - specifier: ^8.4.1 - version: 8.33.0 + specifier: ^8.40.0 + version: 8.40.0 nodemon: - specifier: ^2.0.15 - version: 2.0.20 + specifier: ^2.0.22 + version: 2.0.22 packages: - /@babel/runtime@7.20.13: - resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} + /@babel/runtime@7.21.5: + resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 dev: false - /@eslint/eslintrc@1.4.1: - resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + /@eslint-community/eslint-utils@4.4.0(eslint@8.40.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.40.0 + eslint-visitor-keys: 3.4.1 + dev: true + + /@eslint-community/regexpp@4.5.1: + resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.0.3: + resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.4.1 + espree: 9.5.2 globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 @@ -105,6 +120,11 @@ packages: - supports-color dev: true + /@eslint/js@8.40.0: + resolution: {integrity: sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@humanwhocodes/config-array@0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -125,378 +145,378 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@jimp/bmp@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==} + /@jimp/bmp@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-9edAxu7N2FX7vzkdl5Jo1BbACfycUtBQX+XBMcHA2bk62P8R0otgkHg798frgAk/WxQIzwxqOH6wMiCwrlAzdQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 bmp-js: 0.1.0 dev: false - /@jimp/core@0.16.2: - resolution: {integrity: sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==} + /@jimp/core@0.16.13: + resolution: {integrity: sha512-qXpA1tzTnlkTku9yqtuRtS/wVntvE6f3m3GNxdTdtmc+O+Wcg9Xo2ABPMh7Nc0AHbMKzwvwgB2JnjZmlmJEObg==} dependencies: - '@babel/runtime': 7.20.13 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/utils': 0.16.13 any-base: 1.1.0 buffer: 5.7.1 exif-parser: 0.1.12 - file-type: 9.0.0 + file-type: 16.5.4 load-bmfont: 1.4.1 mkdirp: 0.5.6 phin: 2.9.3 pixelmatch: 4.0.2 - tinycolor2: 1.5.2 + tinycolor2: 1.6.0 dev: false - /@jimp/custom@0.16.2: - resolution: {integrity: sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==} + /@jimp/custom@0.16.13: + resolution: {integrity: sha512-LTATglVUPGkPf15zX1wTMlZ0+AU7cGEGF6ekVF1crA8eHUWsGjrYTB+Ht4E3HTrCok8weQG+K01rJndCp/l4XA==} dependencies: - '@babel/runtime': 7.20.13 - '@jimp/core': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/core': 0.16.13 dev: false - /@jimp/gif@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==} + /@jimp/gif@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-yFAMZGv3o+YcjXilMWWwS/bv1iSqykFahFMSO169uVMtfQVfa90kt4/kDwrXNR6Q9i6VHpFiGZMlF2UnHClBvg==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 gifwrap: 0.9.4 omggif: 1.0.10 dev: false - /@jimp/jpeg@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-BW5gZydgq6wdIwHd+3iUNgrTklvoQc/FUKSj9meM6A0FU21lUaansRX5BDdJqHkyXJLnnlDGwDt27J+hQuBAVw==} + /@jimp/jpeg@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-BJHlDxzTlCqP2ThqP8J0eDrbBfod7npWCbJAcfkKqdQuFk0zBPaZ6KKaQKyKxmWJ87Z6ohANZoMKEbtvrwz1AA==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 jpeg-js: 0.4.4 dev: false - /@jimp/plugin-blit@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==} + /@jimp/plugin-blit@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-8Z1k96ZFxlhK2bgrY1JNWNwvaBeI/bciLM0yDOni2+aZwfIIiC7Y6PeWHTAvjHNjphz+XCt01WQmOYWCn0ML6g==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-blur@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==} + /@jimp/plugin-blur@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-PvLrfa8vkej3qinlebyhLpksJgCF5aiysDMSVhOZqwH5nQLLtDE9WYbnsofGw4r0VVpyw3H/ANCIzYTyCtP9Cg==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-circle@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==} + /@jimp/plugin-circle@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-RNave7EFgZrb5V5EpdvJGAEHMnDAJuwv05hKscNfIYxf0kR3KhViBTDy+MoTnMlIvaKFULfwIgaZWzyhuINMzA==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-color@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==} + /@jimp/plugin-color@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-xW+9BtEvoIkkH/Wde9ql4nAFbYLkVINhpgAE7VcBUsuuB34WUbcBl/taOuUYQrPEFQJ4jfXiAJZ2H/rvKjCVnQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 - tinycolor2: 1.5.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 + tinycolor2: 1.6.0 dev: false - /@jimp/plugin-contain@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2): - resolution: {integrity: sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==} + /@jimp/plugin-contain@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13)(@jimp/plugin-resize@0.16.13)(@jimp/plugin-scale@0.16.13): + resolution: {integrity: sha512-QayTXw4tXMwU6q6acNTQrTTFTXpNRBe+MgTGMDU0lk+23PjlFCO/9sacflelG8lsp7vNHhAxFeHptDMAksEYzg==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-blit': '>=0.3.5' '@jimp/plugin-resize': '>=0.3.5' '@jimp/plugin-scale': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-scale': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-scale': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13) + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-cover@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2): - resolution: {integrity: sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==} + /@jimp/plugin-cover@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-crop@0.16.13)(@jimp/plugin-resize@0.16.13)(@jimp/plugin-scale@0.16.13): + resolution: {integrity: sha512-BSsP71GTNaqWRcvkbWuIVH+zK7b3TSNebbhDkFK0fVaUTzHuKMS/mgY4hDZIEVt7Rf5FjadAYtsujHN9w0iSYA==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-crop': '>=0.3.5' '@jimp/plugin-resize': '>=0.3.5' '@jimp/plugin-scale': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-crop': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-scale': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-scale': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13) + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-crop@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==} + /@jimp/plugin-crop@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-WEl2tPVYwzYL8OKme6Go2xqiWgKsgxlMwyHabdAU4tXaRwOCnOI7v4021gCcBb9zn/oWwguHuKHmK30Fw2Z/PA==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-displace@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==} + /@jimp/plugin-displace@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-qt9WKq8vWrcjySa9DyQ0x/RBMHQeiVjdVSY1SJsMjssPUf0pS74qorcuAkGi89biN3YoGUgPkpqECnAWnYwgGA==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-dither@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==} + /@jimp/plugin-dither@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-5/N3yJggbWQTlGZHQYJPmQXEwR52qaXjEzkp1yRBbtdaekXE3BG/suo0fqeoV/csf8ooI78sJzYmIrxNoWVtgQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-fisheye@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==} + /@jimp/plugin-fisheye@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-2rZmTdFbT/cF9lEZIkXCYO0TsT114Q27AX5IAo0Sju6jVQbvIk1dFUTnwLDadTo8wkJlFzGqMQ24Cs8cHWOliA==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-flip@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-rotate@0.16.2): - resolution: {integrity: sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==} + /@jimp/plugin-flip@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-rotate@0.16.13): + resolution: {integrity: sha512-EmcgAA74FTc5u7Z+hUO/sRjWwfPPLuOQP5O64x5g4j0T12Bd29IgsYZxoutZo/rb3579+JNa/3wsSEmyVv1EpA==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-rotate': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-rotate': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-rotate': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13)(@jimp/plugin-crop@0.16.13)(@jimp/plugin-resize@0.16.13) + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-gaussian@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==} + /@jimp/plugin-gaussian@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-A1XKfGQD0iDdIiKqFYi8nZMv4dDVYdxbrmgR7y/CzUHhSYdcmoljLIIsZZM3Iks/Wa353W3vtvkWLuDbQbch1w==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-invert@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==} + /@jimp/plugin-invert@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-xFMrIn7czEZbdbMzZWuaZFnlLGJDVJ82y5vlsKsXRTG2kcxRsMPXvZRWHV57nSs1YFsNqXSbrC8B98n0E32njQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-mask@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==} + /@jimp/plugin-mask@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-wLRYKVBXql2GAYgt6FkTnCfE+q5NomM7Dlh0oIPGAoMBWDyTx0eYutRK6PlUrRK2yMHuroAJCglICTbxqGzowQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-normalize@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==} + /@jimp/plugin-normalize@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-3tfad0n9soRna4IfW9NzQdQ2Z3ijkmo21DREHbE6CGcMIxOSvfRdSvf1qQPApxjTSo8LTU4MCi/fidx/NZ0GqQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-print@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2): - resolution: {integrity: sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==} + /@jimp/plugin-print@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13): + resolution: {integrity: sha512-0m6i3p01PGRkGAK9r53hDYrkyMq+tlhLOIbsSTmZyh6HLshUKlTB7eXskF5OpVd5ZUHoltlNc6R+ggvKIzxRFw==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-blit': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) + '@jimp/utils': 0.16.13 load-bmfont: 1.4.1 dev: false - /@jimp/plugin-resize@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==} + /@jimp/plugin-resize@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-qoqtN8LDknm3fJm9nuPygJv30O3vGhSBD2TxrsCnhtOsxKAqVPJtFVdGd/qVuZ8nqQANQmTlfqTiK9mVWQ7MiQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-rotate@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2): - resolution: {integrity: sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==} + /@jimp/plugin-rotate@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13)(@jimp/plugin-crop@0.16.13)(@jimp/plugin-resize@0.16.13): + resolution: {integrity: sha512-Ev+Jjmj1nHYw897z9C3R9dYsPv7S2/nxdgfFb/h8hOwK0Ovd1k/+yYS46A0uj/JCKK0pQk8wOslYBkPwdnLorw==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-blit': '>=0.3.5' '@jimp/plugin-crop': '>=0.3.5' '@jimp/plugin-resize': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-crop': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-scale@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2): - resolution: {integrity: sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==} + /@jimp/plugin-scale@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13): + resolution: {integrity: sha512-05POQaEJVucjTiSGMoH68ZiELc7QqpIpuQlZ2JBbhCV+WCbPFUBcGSmE7w4Jd0E2GvCho/NoMODLwgcVGQA97A==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-resize': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-shadow@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blur@0.16.2)(@jimp/plugin-resize@0.16.2): - resolution: {integrity: sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==} + /@jimp/plugin-shadow@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blur@0.16.13)(@jimp/plugin-resize@0.16.13): + resolution: {integrity: sha512-nmu5VSZ9hsB1JchTKhnnCY+paRBnwzSyK5fhkhtQHHoFD5ArBQ/5wU8y6tCr7k/GQhhGq1OrixsECeMjPoc8Zw==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-blur': '>=0.3.5' '@jimp/plugin-resize': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-blur': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-blur': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugin-threshold@0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-color@0.16.2)(@jimp/plugin-resize@0.16.2): - resolution: {integrity: sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==} + /@jimp/plugin-threshold@0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-color@0.16.13)(@jimp/plugin-resize@0.16.13): + resolution: {integrity: sha512-+3zArBH0OE3Rhjm4HyAokMsZlIq5gpQec33CncyoSwxtRBM2WAhUVmCUKuBo+Lr/2/4ISoY4BWpHKhMLDix6cA==} peerDependencies: '@jimp/custom': '>=0.3.5' '@jimp/plugin-color': '>=0.8.0' '@jimp/plugin-resize': '>=0.8.0' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-color': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-color': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/utils': 0.16.13 dev: false - /@jimp/plugins@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==} + /@jimp/plugins@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-CJLdqODEhEVs4MgWCxpWL5l95sCBlkuSLz65cxEm56X5akIsn4LOlwnKoSEZioYcZUBvHhCheH67AyPTudfnQQ==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugin-blit': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-blur': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-circle': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-color': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-contain': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2) - '@jimp/plugin-cover': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2)(@jimp/plugin-scale@0.16.2) - '@jimp/plugin-crop': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-displace': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-dither': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-fisheye': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-flip': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-rotate@0.16.2) - '@jimp/plugin-gaussian': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-invert': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-mask': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-normalize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-print': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2) - '@jimp/plugin-resize': 0.16.2(@jimp/custom@0.16.2) - '@jimp/plugin-rotate': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blit@0.16.2)(@jimp/plugin-crop@0.16.2)(@jimp/plugin-resize@0.16.2) - '@jimp/plugin-scale': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-resize@0.16.2) - '@jimp/plugin-shadow': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-blur@0.16.2)(@jimp/plugin-resize@0.16.2) - '@jimp/plugin-threshold': 0.16.2(@jimp/custom@0.16.2)(@jimp/plugin-color@0.16.2)(@jimp/plugin-resize@0.16.2) + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugin-blit': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-blur': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-circle': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-color': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-contain': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13)(@jimp/plugin-resize@0.16.13)(@jimp/plugin-scale@0.16.13) + '@jimp/plugin-cover': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-crop@0.16.13)(@jimp/plugin-resize@0.16.13)(@jimp/plugin-scale@0.16.13) + '@jimp/plugin-crop': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-displace': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-dither': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-fisheye': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-flip': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-rotate@0.16.13) + '@jimp/plugin-gaussian': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-invert': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-mask': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-normalize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-print': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13) + '@jimp/plugin-resize': 0.16.13(@jimp/custom@0.16.13) + '@jimp/plugin-rotate': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blit@0.16.13)(@jimp/plugin-crop@0.16.13)(@jimp/plugin-resize@0.16.13) + '@jimp/plugin-scale': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-resize@0.16.13) + '@jimp/plugin-shadow': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-blur@0.16.13)(@jimp/plugin-resize@0.16.13) + '@jimp/plugin-threshold': 0.16.13(@jimp/custom@0.16.13)(@jimp/plugin-color@0.16.13)(@jimp/plugin-resize@0.16.13) timm: 1.7.1 dev: false - /@jimp/png@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==} + /@jimp/png@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-8cGqINvbWJf1G0Her9zbq9I80roEX0A+U45xFby3tDWfzn+Zz8XKDF1Nv9VUwVx0N3zpcG1RPs9hfheG4Cq2kg==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/utils': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/utils': 0.16.13 pngjs: 3.4.0 dev: false - /@jimp/tiff@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==} + /@jimp/tiff@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-oJY8d9u95SwW00VPHuCNxPap6Q1+E/xM5QThb9Hu+P6EGuu6lIeLaNBMmFZyblwFbwrH+WBOZlvIzDhi4Dm/6Q==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 utif: 2.0.1 dev: false - /@jimp/types@0.16.2(@jimp/custom@0.16.2): - resolution: {integrity: sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==} + /@jimp/types@0.16.13(@jimp/custom@0.16.13): + resolution: {integrity: sha512-mC0yVNUobFDjoYLg4hoUwzMKgNlxynzwt3cDXzumGvRJ7Kb8qQGOWJQjQFo5OxmGExqzPphkirdbBF88RVLBCg==} peerDependencies: '@jimp/custom': '>=0.3.5' dependencies: - '@babel/runtime': 7.20.13 - '@jimp/bmp': 0.16.2(@jimp/custom@0.16.2) - '@jimp/custom': 0.16.2 - '@jimp/gif': 0.16.2(@jimp/custom@0.16.2) - '@jimp/jpeg': 0.16.2(@jimp/custom@0.16.2) - '@jimp/png': 0.16.2(@jimp/custom@0.16.2) - '@jimp/tiff': 0.16.2(@jimp/custom@0.16.2) + '@babel/runtime': 7.21.5 + '@jimp/bmp': 0.16.13(@jimp/custom@0.16.13) + '@jimp/custom': 0.16.13 + '@jimp/gif': 0.16.13(@jimp/custom@0.16.13) + '@jimp/jpeg': 0.16.13(@jimp/custom@0.16.13) + '@jimp/png': 0.16.13(@jimp/custom@0.16.13) + '@jimp/tiff': 0.16.13(@jimp/custom@0.16.13) timm: 1.7.1 dev: false - /@jimp/utils@0.16.2: - resolution: {integrity: sha512-XENrPvmigiXZQ8E2nxJqO6UVvWBLzbNwyYi3Y8Q1IECoYhYI3kgOQ0fmy4G269Vz1V0omh1bNmC42r4OfXg1Jg==} + /@jimp/utils@0.16.13: + resolution: {integrity: sha512-VyCpkZzFTHXtKgVO35iKN0sYR10psGpV6SkcSeV4oF7eSYlR8Bl6aQLCzVeFjvESF7mxTmIiI3/XrMobVrtxDA==} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.5 regenerator-runtime: 0.13.11 dev: false @@ -535,7 +555,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: chalk: 2.4.2 - deepmerge: 4.3.0 + deepmerge: 4.3.1 dev: false /@sendgrid/mail@6.5.5: @@ -546,10 +566,14 @@ packages: '@sendgrid/helpers': 6.5.5 dev: false + /@tokenizer/token@0.3.0: + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + dev: false + /@types/bson@4.0.5: resolution: {integrity: sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==} dependencies: - '@types/node': 18.11.18 + '@types/node': 20.1.3 dev: false /@types/caseless@0.12.2: @@ -560,22 +584,22 @@ packages: resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} dependencies: '@types/bson': 4.0.5 - '@types/node': 18.11.18 + '@types/node': 20.1.3 dev: false /@types/node@16.9.1: resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} dev: false - /@types/node@18.11.18: - resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} + /@types/node@20.1.3: + resolution: {integrity: sha512-NP2yfZpgmf2eDRPmgGq+fjGjSwFgYbihA8/gK+ey23qT9RkxsgNTZvGOEpXgzIGqesTYkElELLgtKoMQTys5vA==} dev: false /@types/request@2.48.8: resolution: {integrity: sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==} dependencies: '@types/caseless': 0.12.2 - '@types/node': 18.11.18 + '@types/node': 20.1.3 '@types/tough-cookie': 4.0.2 form-data: 2.5.1 dev: false @@ -710,7 +734,7 @@ packages: /bl@2.2.1: resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} dependencies: - readable-stream: 2.3.7 + readable-stream: 2.3.8 safe-buffer: 5.2.1 dev: false @@ -742,6 +766,26 @@ packages: - supports-color dev: false + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -918,7 +962,7 @@ packages: engines: {node: '>=0.8'} dependencies: is-nan: 1.3.2 - moment-timezone: 0.5.40 + moment-timezone: 0.5.43 dev: false /cross-spawn@7.0.3: @@ -987,13 +1031,13 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /deepmerge@4.3.0: - resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} dev: false - /define-properties@1.1.4: - resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + /define-properties@1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} dependencies: has-property-descriptors: 1.0.0 @@ -1032,7 +1076,7 @@ packages: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 - entities: 4.4.0 + entities: 4.5.0 dev: false /dom-walk@0.1.2: @@ -1050,8 +1094,8 @@ packages: domelementtype: 2.3.0 dev: false - /domutils@3.0.1: - resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 @@ -1079,8 +1123,8 @@ packages: engines: {node: '>= 0.8'} dev: false - /entities@4.4.0: - resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} dev: false @@ -1097,40 +1141,28 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - /eslint-scope@7.1.1: - resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + /eslint-scope@7.2.0: + resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true - /eslint-utils@3.0.0(eslint@8.33.0): - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 8.33.0 - eslint-visitor-keys: 2.1.0 - dev: true - - /eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - dev: true - - /eslint-visitor-keys@3.3.0: - resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + /eslint-visitor-keys@3.4.1: + resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.33.0: - resolution: {integrity: sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==} + /eslint@8.40.0: + resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint/eslintrc': 1.4.1 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) + '@eslint-community/regexpp': 4.5.1 + '@eslint/eslintrc': 2.0.3 + '@eslint/js': 8.40.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -1140,11 +1172,10 @@ packages: debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 - eslint-utils: 3.0.0(eslint@8.33.0) - eslint-visitor-keys: 3.3.0 - espree: 9.4.1 - esquery: 1.4.0 + eslint-scope: 7.2.0 + eslint-visitor-keys: 3.4.1 + espree: 9.5.2 + esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 @@ -1157,7 +1188,7 @@ packages: imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-sdsl: 4.3.0 + js-sdsl: 4.4.0 js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -1165,7 +1196,6 @@ packages: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.1 - regexpp: 3.2.0 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 text-table: 0.2.0 @@ -1173,17 +1203,17 @@ packages: - supports-color dev: true - /espree@9.4.1: - resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + /espree@9.5.2: + resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.8.2 acorn-jsx: 5.3.2(acorn@8.8.2) - eslint-visitor-keys: 3.3.0 + eslint-visitor-keys: 3.4.1 dev: true - /esquery@1.4.0: - resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 @@ -1227,7 +1257,7 @@ packages: engines: {node: '>=v12.22.9'} dependencies: glob: 8.1.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 handlebars: 4.7.7 dev: false @@ -1247,12 +1277,12 @@ packages: - supports-color dev: false - /express-validator@6.14.3: - resolution: {integrity: sha512-c4b9NMdhskfcLbH/FchsSfCt4Vb14gKzcotG9zLS+VoOJDox57aGhCL+kmAu7cl+ytaSed+HD5jdJhel8DQsdg==} + /express-validator@6.15.0: + resolution: {integrity: sha512-r05VYoBL3i2pswuehoFSy+uM8NBuVaY7avp5qrYjQBDzagx2Z5A77FZqPT8/gNLF3HopWkIzaTFaC4JysWXLqg==} engines: {node: '>= 8.0.0'} dependencies: lodash: 4.17.21 - validator: 13.7.0 + validator: 13.9.0 dev: false /express@4.18.2: @@ -1326,9 +1356,13 @@ packages: flat-cache: 3.0.4 dev: true - /file-type@9.0.0: - resolution: {integrity: sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==} - engines: {node: '>=6'} + /file-type@16.5.4: + resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} + engines: {node: '>=10'} + dependencies: + readable-web-to-node-stream: 3.0.2 + strtok3: 6.3.0 + token-types: 4.2.1 dev: false /fill-range@7.0.1: @@ -1497,8 +1531,8 @@ packages: type-fest: 0.20.2 dev: true - /graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: false /grapheme-splitter@1.0.4: @@ -1510,7 +1544,7 @@ packages: engines: {node: '>=0.4.7'} hasBin: true dependencies: - minimist: 1.2.7 + minimist: 1.2.8 neo-async: 2.6.2 source-map: 0.6.1 wordwrap: 1.0.0 @@ -1559,13 +1593,13 @@ packages: function-bind: 1.1.1 dev: false - /htmlparser2@8.0.1: - resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} + /htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.0.1 - entities: 4.4.0 + domutils: 3.1.0 + entities: 4.5.0 dev: false /http-errors@2.0.0: @@ -1588,14 +1622,13 @@ packages: sshpk: 1.17.0 dev: false - /ical-generator@1.15.4(@types/node@18.11.18): + /ical-generator@1.15.4: resolution: {integrity: sha512-drXe4RLkfNlvDvdy/E6BUI9p+01L3ySK1ufNEYI9TxNKG9ZA3G60QWoZvD1dtmH4scwDxYu6/sZBPJvYVNrj8A==} engines: {node: '>=6.0.0'} peerDependencies: '@types/node': '>= 8.0.0' dependencies: - '@types/node': 18.11.18 - moment-timezone: 0.5.40 + moment-timezone: 0.5.43 dev: false /ical@0.6.0: @@ -1686,7 +1719,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.4 + define-properties: 1.2.0 dev: false /is-number@7.0.0: @@ -1720,13 +1753,13 @@ packages: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} dev: false - /jimp@0.16.2: - resolution: {integrity: sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==} + /jimp@0.16.13: + resolution: {integrity: sha512-Bxz8q7V4rnCky9A0ktTNGA9SkNFVWRHodddI/DaAWZJzF7sVUlFYKQ60y9JGqrKpi48ECA/TnfMzzc5C70VByA==} dependencies: - '@babel/runtime': 7.20.13 - '@jimp/custom': 0.16.2 - '@jimp/plugins': 0.16.2(@jimp/custom@0.16.2) - '@jimp/types': 0.16.2(@jimp/custom@0.16.2) + '@babel/runtime': 7.21.5 + '@jimp/custom': 0.16.13 + '@jimp/plugins': 0.16.13(@jimp/custom@0.16.13) + '@jimp/types': 0.16.13(@jimp/custom@0.16.13) regenerator-runtime: 0.13.11 dev: false @@ -1734,8 +1767,8 @@ packages: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} dev: false - /js-sdsl@4.3.0: - resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + /js-sdsl@4.4.0: + resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==} dev: true /js-yaml@4.1.0: @@ -1824,8 +1857,8 @@ packages: dev: false optional: true - /marked@4.2.12: - resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} + /marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} hasBin: true dev: false @@ -1886,19 +1919,19 @@ packages: brace-expansion: 2.0.1 dev: false - /minimist@1.2.7: - resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: false /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true dependencies: - minimist: 1.2.7 + minimist: 1.2.8 dev: false - /moment-timezone@0.5.40: - resolution: {integrity: sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==} + /moment-timezone@0.5.43: + resolution: {integrity: sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==} dependencies: moment: 2.29.4 dev: false @@ -1940,16 +1973,16 @@ packages: saslprep: 1.0.3 dev: false - /mongoose-legacy-pluralize@1.0.2(mongoose@5.13.15): + /mongoose-legacy-pluralize@1.0.2(mongoose@5.13.17): resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==} peerDependencies: mongoose: '*' dependencies: - mongoose: 5.13.15 + mongoose: 5.13.17 dev: false - /mongoose@5.13.15: - resolution: {integrity: sha512-cxp1Gbb8yUWkaEbajdhspSaKzAvsIvOtRlYD87GN/P2QEUhpd6bIvebi36T6M0tIVAMauNaK9SPA055N3PwF8Q==} + /mongoose@5.13.17: + resolution: {integrity: sha512-kzlwQgrWaQflFSdENNGN4+FQEm/yOMgR1T1okIp5fARGQ8YKdjO+0U9Ikzsv5OVSFIkE0ATyJj2XxawYbf2bpA==} engines: {node: '>=4.0.0'} dependencies: '@types/bson': 4.0.5 @@ -1957,7 +1990,7 @@ packages: bson: 1.1.6 kareem: 2.3.2 mongodb: 3.7.3 - mongoose-legacy-pluralize: 1.0.2(mongoose@5.13.15) + mongoose-legacy-pluralize: 1.0.2(mongoose@5.13.17) mpath: 0.8.4 mquery: 3.2.5 ms: 2.1.2 @@ -2004,8 +2037,8 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /nanoid@3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: false @@ -2038,13 +2071,13 @@ packages: sorted-array-functions: 1.3.0 dev: false - /nodemailer@6.9.1: - resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==} + /nodemailer@6.9.2: + resolution: {integrity: sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==} engines: {node: '>=6.0.0'} dev: false - /nodemon@2.0.20: - resolution: {integrity: sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==} + /nodemon@2.0.22: + resolution: {integrity: sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==} engines: {node: '>=8.10.0'} hasBin: true dependencies: @@ -2207,6 +2240,11 @@ packages: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false + /peek-readable@4.1.0: + resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} + engines: {node: '>=8'} + dev: false + /performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} dev: false @@ -2236,11 +2274,11 @@ packages: engines: {node: '>=4.0.0'} dev: false - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + /postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.4 + nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 dev: false @@ -2333,8 +2371,18 @@ packages: unpipe: 1.0.0 dev: false - /readable-stream@2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -2345,6 +2393,22 @@ packages: util-deprecate: 1.0.2 dev: false + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /readable-web-to-node-stream@3.0.2: + resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} + engines: {node: '>=8'} + dependencies: + readable-stream: 3.6.2 + dev: false + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2360,11 +2424,6 @@ packages: resolution: {integrity: sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==} dev: false - /regexpp@3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - dev: true - /request@2.88.2: resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} engines: {node: '>= 6'} @@ -2438,15 +2497,15 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false - /sanitize-html@2.9.0: - resolution: {integrity: sha512-KY1hpSbqFNcpoLf+nP7iStbP5JfQZ2Nd19ZEE7qFsQqRdp+sO5yX/e5+HoG9puFAcSTEpzQuihfKUltDcLlQjg==} + /sanitize-html@2.10.0: + resolution: {integrity: sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==} dependencies: - deepmerge: 4.3.0 + deepmerge: 4.3.1 escape-string-regexp: 4.0.0 - htmlparser2: 8.0.1 + htmlparser2: 8.0.2 is-plain-object: 5.0.0 parse-srcset: 1.0.2 - postcss: 8.4.21 + postcss: 8.4.23 dev: false /saslprep@1.0.3: @@ -2597,6 +2656,12 @@ packages: safe-buffer: 5.1.2 dev: false + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2609,6 +2674,14 @@ packages: engines: {node: '>=8'} dev: true + /strtok3@6.3.0: + resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} + engines: {node: '>=10'} + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 4.1.0 + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2630,8 +2703,8 @@ packages: resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==} dev: false - /tinycolor2@1.5.2: - resolution: {integrity: sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==} + /tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} dev: false /to-regex-range@5.0.1: @@ -2646,6 +2719,14 @@ packages: engines: {node: '>=0.6'} dev: false + /token-types@4.2.1: + resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} + engines: {node: '>=10'} + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + dev: false + /touch@3.1.0: resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} hasBin: true @@ -2741,8 +2822,8 @@ packages: hasBin: true dev: false - /validator@13.7.0: - resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==} + /validator@13.9.0: + resolution: {integrity: sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==} engines: {node: '>= 0.10'} dev: false -- cgit v1.2.3 From bfe708d48f603998a1f2c4cad4a6f9f8683dc18f Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:06 +0100 Subject: Migrate to Typescript --- .gitignore | 7 +- activitypub.js | 941 ------------------- app.js | 54 -- config/api-example.js | 8 - config/database-docker.js | 3 - config/database-example.js | 3 - config/domain-example.js | 13 - config/gathio.service | 13 - helpers.js | 57 -- models/Event.js | 254 ------ models/EventGroup.js | 57 -- models/Log.js | 26 - package.json | 7 +- pnpm-lock.yaml | 9 + routes.js | 1976 ---------------------------------------- src/activitypub.js | 941 +++++++++++++++++++ src/app.js | 54 ++ src/config/api-example.js | 8 + src/config/database-docker.js | 3 + src/config/database-example.js | 3 + src/config/domain-example.js | 13 + src/config/gathio.service | 13 + src/helpers.js | 57 ++ src/models/Event.js | 254 ++++++ src/models/EventGroup.js | 57 ++ src/models/Log.js | 26 + src/routes.js | 1976 ++++++++++++++++++++++++++++++++++++++++ src/start.js | 32 + start.js | 32 - tsconfig.json | 22 + 30 files changed, 3477 insertions(+), 3442 deletions(-) delete mode 100644 activitypub.js delete mode 100755 app.js delete mode 100644 config/api-example.js delete mode 100644 config/database-docker.js delete mode 100644 config/database-example.js delete mode 100644 config/domain-example.js delete mode 100644 config/gathio.service delete mode 100644 helpers.js delete mode 100755 models/Event.js delete mode 100755 models/EventGroup.js delete mode 100755 models/Log.js delete mode 100755 routes.js create mode 100644 src/activitypub.js create mode 100755 src/app.js create mode 100644 src/config/api-example.js create mode 100644 src/config/database-docker.js create mode 100644 src/config/database-example.js create mode 100644 src/config/domain-example.js create mode 100644 src/config/gathio.service create mode 100644 src/helpers.js create mode 100755 src/models/Event.js create mode 100755 src/models/EventGroup.js create mode 100755 src/models/Log.js create mode 100755 src/routes.js create mode 100755 src/start.js delete mode 100755 start.js create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 16d3fa8..763f664 100755 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,10 @@ # gathio custom -config/api.js -config/database.js -config/domain.js +dist +src/config/api.js +src/config/database.js +src/config/domain.js public/events/* !public/events/.gitkeep diff --git a/activitypub.js b/activitypub.js deleted file mode 100644 index 442f03c..0000000 --- a/activitypub.js +++ /dev/null @@ -1,941 +0,0 @@ -const domain = require('./config/domain.js').domain; -const contactEmail = require('./config/domain.js').email; -const siteName = require('./config/domain.js').sitename; -const isFederated = require('./config/domain.js').isFederated; -const request = require('request'); -const addToLog = require('./helpers.js').addToLog; -const crypto = require('crypto'); -// This alphabet (used to generate all event, group, etc. IDs) is missing '-' -// because ActivityPub doesn't like it in IDs -const { customAlphabet } = require('nanoid'); -const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_', 21); -var moment = require('moment-timezone'); -const mongoose = require('mongoose'); -const Event = mongoose.model('Event'); -const EventGroup = mongoose.model('EventGroup'); -var sanitizeHtml = require('sanitize-html'); - -function createActivityPubActor(eventID, domain, pubkey, description, name, location, imageFilename, startUTC, endUTC, timezone) { - let actor = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], - - 'id': `https://${domain}/${eventID}`, - 'type': 'Person', - 'preferredUsername': `${eventID}`, - 'inbox': `https://${domain}/activitypub/inbox`, - 'outbox': `https://${domain}/${eventID}/outbox`, - 'followers': `https://${domain}/${eventID}/followers`, - 'summary': `

${description}

`, - 'name': name, - 'featured': `https://${domain}/${eventID}/featured`, - - 'publicKey': { - 'id': `https://${domain}/${eventID}#main-key`, - 'owner': `https://${domain}/${eventID}`, - 'publicKeyPem': pubkey - } - }; - if (location) { - actor.summary += `

Location: ${location}.

` - } - let displayDate; - if (startUTC && timezone) { - displayDate = moment.tz(startUTC, timezone).format('D MMMM YYYY h:mm a'); - actor.summary += `

Starting ${displayDate} ${timezone}.

`; - } - if (imageFilename) { - actor.icon = { - 'type': 'Image', - 'mediaType': 'image/jpg', - 'url': `https://${domain}/events/${imageFilename}`, - }; - } - return JSON.stringify(actor); -} - -function createActivityPubEvent(name, startUTC, endUTC, timezone, description, location) { - const guid = crypto.randomBytes(16).toString('hex'); - let eventObject = { - "@context": "https://www.w3.org/ns/activitystreams", - 'id': `https://${domain}/${guid}`, - "name": name, - "type": "Event", - "startTime": moment.tz(startUTC, timezone).format(), - "endTime": moment.tz(endUTC, timezone).format(), - "content": description, - "location": location - } - return JSON.stringify(eventObject); -} - -function createFeaturedPost(eventID, name, startUTC, endUTC, timezone, description, location) { - const featured = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${eventID}/m/featuredPost`, - "type": "Note", - "name": "Test", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `

This is an event that was posted on ${siteName}. If you follow this account, you'll see updates in your timeline about the event. If your software supports polls, you should get a poll in your DMs asking if you want to RSVP. You can reply and RSVP right from there. If your software has an event calendar built in, you should get an event in your inbox that you can RSVP to like you respond to any event.

For more information on how to interact with this, check out this link.

`, - 'attributedTo': `https://${domain}/${eventID}`, - } - return featured; -} - -function updateActivityPubEvent(oldEvent, name, startUTC, endUTC, timezone, description, location) { - // we want to persist the old ID no matter what happens to the Event itself - const id = oldEvent.id; - let eventObject = { - "@context": "https://www.w3.org/ns/activitystreams", - 'id': id, - "name": name, - "type": "Event", - "startTime": moment.tz(startUTC, timezone).format(), - "endTime": moment.tz(endUTC, timezone).format(), - "content": description, - "location": location - } - return JSON.stringify(eventObject); -} - - -function updateActivityPubActor(actor, description, name, location, imageFilename, startUTC, endUTC, timezone) { - if (!actor) return; - actor.summary = `

${description}

`; - actor.name = name; - if (location) { - actor.summary += `

Location: ${location}.

` - } - let displayDate; - if (startUTC && timezone) { - displayDate = moment.tz(startUTC, timezone).format('D MMMM YYYY h:mm a'); - actor.summary += `

Starting ${displayDate} ${timezone}.

`; - } - if (imageFilename) { - actor.icon = { - 'type': 'Image', - 'mediaType': 'image/jpg', - 'url': `https://${domain}/events/${imageFilename}`, - }; - } - return JSON.stringify(actor); -} - -function signAndSend(message, eventID, targetDomain, inbox, callback) { - if (!isFederated) return; - let inboxFragment = inbox.replace('https://' + targetDomain, ''); - // get the private key - Event.findOne({ - id: eventID - }) - .then((event) => { - if (event) { - const digest = crypto.createHash('sha256').update(JSON.stringify(message)).digest('base64'); - const privateKey = event.privateKey; - const signer = crypto.createSign('sha256'); - let d = new Date(); - let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}\ndigest: SHA-256=${digest}`; - signer.update(stringToSign); - signer.end(); - const signature = signer.sign(privateKey); - const signature_b64 = signature.toString('base64'); - const algorithm = 'rsa-sha256'; - let header = `keyId="https://${domain}/${eventID}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; - request({ - url: inbox, - headers: { - 'Host': targetDomain, - 'Date': d.toUTCString(), - 'Signature': header, - 'Digest': `SHA-256=${digest}`, - 'Content-Type': 'application/activity+json', - 'Accept': 'application/activity+json' - }, - method: 'POST', - json: true, - body: message - }, function (error, response) { - if (error) { - callback(error, null, 500); - } - else { - // Add the message to the database - const messageID = message.id; - const newMessage = { - id: message.id, - content: JSON.stringify(message) - }; - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - event.activityPubMessages.push(newMessage); - // also add the message's object if it has one - if (message.object && message.object.id) { - event.activityPubMessages.push({ - id: message.object.id, - content: JSON.stringify(message.object) - }); - } - event.save() - .then(() => { - addToLog("addActivityPubMessage", "success", "ActivityPubMessage added to event " + eventID); - callback(null, message.id, 200); - }) - .catch((err) => { - addToLog("addActivityPubMessage", "error", "Attempt to add ActivityPubMessage to event " + eventID + " failed with error: " + err); - callback(err, null, 500); - }); - }) - } - }); - } - else { - callback(`No record found for ${eventID}.`, null, 404); - } - }); -} - -// this function sends something to the timeline of every follower in the followers array -// it's also an unlisted public message, meaning non-followers can see the message if they look at -// the profile but it doesn't spam federated timelines -function broadcastCreateMessage(apObject, followers, eventID) { - if (!isFederated) return; - let guidCreate = crypto.randomBytes(16).toString('hex'); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - // iterate over followers - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find(el => el.actorId === actorId); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], - 'id': `https://${domain}/${eventID}/m/${guidCreate}`, - 'type': 'Create', - 'actor': `https://${domain}/${eventID}`, - 'to': [actorId], - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - 'object': apObject - }; - signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - } - }); - } - else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } // end if event - else { - console.log(`No event found with the id ${eventID}`); - } - }); -} - - -// sends an Announce for the apObject -function broadcastAnnounceMessage(apObject, followers, eventID) { - if (!isFederated) return; - let guidUpdate = crypto.randomBytes(16).toString('hex'); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - // iterate over followers - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find(el => el.actorId === actorId); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const announceMessage = { - '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], - 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - 'type': 'Announce', - 'actor': `https://${domain}/${eventID}`, - 'object': apObject, - 'to': actorId - }; - signAndSend(announceMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - } - }); - } - else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } // end if event - else { - console.log(`No event found with the id ${eventID}`); - } - }); -} - -// sends an Update for the apObject -function broadcastUpdateMessage(apObject, followers, eventID) { - if (!isFederated) return; - let guidUpdate = crypto.randomBytes(16).toString('hex'); - // iterate over followers - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find(el => el.actorId === actorId); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, - 'type': 'Update', - 'actor': `https://${domain}/${eventID}`, - 'object': apObject - }; - signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - } - }); - } - else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } - else { - console.log(`No event found with the id ${eventID}`); - } - }); -} - -function broadcastDeleteMessage(apObject, followers, eventID, callback) { - callback = callback || function () { }; - if (!isFederated) { - callback([]); - return; - } - // we need to build an array of promises for each message we're sending, run Promise.all(), and then that will resolve when every message has been sent (or failed) - // per spec, each promise will execute *as it is built*, which is fine, we just need the guarantee that they are all done - let promises = []; - - let guidUpdate = crypto.randomBytes(16).toString('hex'); - // iterate over followers - for (const follower of followers) { - promises.push(new Promise((resolve, reject) => { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - const follower = event.followers.find(el => el.actorId === actorId); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, - 'type': 'Delete', - 'actor': `https://${domain}/${eventID}`, - 'object': apObject - }; - signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - reject(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - resolve('sent to', actorId); - } - }); - } - else { - console.log(`No follower found with the id ${actorId}`, null, 404); - reject(`No follower found with the id ${actorId}`, null, 404); - } - } - else { - console.log(`No event found with the id ${eventID}`, null, 404); - reject(`No event found with the id ${eventID}`, null, 404); - } - }); // end event - })); - } // end followers - - Promise.all(promises.map(p => p.catch(e => e))).then(statuses => { - callback(statuses); - }); -} - -// this sends a message "to:" an individual fediverse user -function sendDirectMessage(apObject, actorId, eventID, callback) { - if (!isFederated) return; - callback = callback || function () { }; - const guidCreate = crypto.randomBytes(16).toString('hex'); - const guidObject = crypto.randomBytes(16).toString('hex'); - let d = new Date(); - - apObject.published = d.toISOString(); - apObject.attributedTo = `https://${domain}/${eventID}`; - apObject.to = actorId; - apObject.id = `https://${domain}/${eventID}/m/${guidObject}`; - apObject.content = unescape(apObject.content) - - let createMessage = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${eventID}/m/${guidCreate}`, - 'type': 'Create', - 'actor': `https://${domain}/${eventID}`, - 'to': [actorId], - 'object': apObject - }; - - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - const follower = event.followers.find(el => el.actorId === actorId); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - signAndSend(createMessage, eventID, targetDomain, inbox, callback); - } - else { - callback(`No follower found with the id ${actorId}`, null, 404); - } - } - else { - callback(`No event found with the id ${eventID}`, null, 404); - } - }); -} - -function sendAcceptMessage(thebody, eventID, targetDomain, callback) { - if (!isFederated) return; - callback = callback || function () { }; - const guid = crypto.randomBytes(16).toString('hex'); - const actorId = thebody.actor; - let message = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${guid}`, - 'type': 'Accept', - 'actor': `https://${domain}/${eventID}`, - 'object': thebody, - }; - // get the inbox - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - const follower = event.followers.find(el => el.actorId === actorId); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - signAndSend(message, eventID, targetDomain, inbox, callback); - } - } - else { - callback(`Could not find event ${eventID}`, null, 404); - } - }); -} - -function _handleFollow(req, res) { - const myURL = new URL(req.body.actor); - let targetDomain = myURL.hostname; - let eventID = req.body.object.replace(`https://${domain}/`, ''); - // Add the user to the DB of accounts that follow the account - // get the follower's username - request({ - url: req.body.actor, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, body) { - body = JSON.parse(body) - const name = body.preferredUsername || body.name || body.attributedTo; - const newFollower = { - actorId: req.body.actor, - followId: req.body.id, - name: name, - actorJson: JSON.stringify(body) - }; - Event.findOne({ - id: eventID, - }, function (err, event) { - // if this account is NOT already in our followers list, add it - if (event && !event.followers.map(el => el.actorId).includes(req.body.actor)) { - event.followers.push(newFollower); - event.save() - .then(() => { - addToLog("addEventFollower", "success", "Follower added to event " + eventID); - // Accept the follow request - sendAcceptMessage(req.body, eventID, targetDomain, function (err, resp, status) { - if (err) { - console.log(`Didn't send Accept to ${req.body.actor}, status ${status} with error ${err}`); - } - else { - console.log('sent Accept to', req.body.actor); - // ALSO send an ActivityPub Event activity since this person is "interested" in the event, as indicated by the Follow - const jsonEventObject = JSON.parse(event.activityPubEvent); - // send direct message to user - sendDirectMessage(jsonEventObject, newFollower.actorId, event.id); - - // if users can self-RSVP, send a Question to the new follower - if (event.usersCanAttend) { - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Question", - "content": `@${name} Will you attend ${event.name}? (If you reply "Yes", you'll be listed as an attendee on the event page.)`, - "oneOf": [ - { "type": "Note", "name": "Yes" }, - ], - "endTime": event.start.toISOString(), - "tag": [{ "type": "Mention", "href": req.body.actor, "name": name }] - } - // send direct message to user - sendDirectMessage(jsonObject, req.body.actor, eventID, function (error, response, statuscode) { - if (error) { - console.log('Error sending direct message:', error); - return res.status(statuscode).json(error); - } - else { - return res.status(statuscode).json({ messageid: response }); - } - }); - } - } - }); - }) - .catch((err) => { - addToLog("addEventFollower", "error", "Attempt to add follower to event " + eventID + " failed with error: " + err); - return res.status(500).send('Database error, please try again :('); - }); - } - else { - // this person is already a follower so just say "ok" - return res.status(200); - } - }) - }) //end request -} - -function _handleUndoFollow(req, res) { - // get the record of all followers for this account - const eventID = req.body.object.object.replace(`https://${domain}/`, ''); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // check to see if the Follow object's id matches the id we have on record - // is this even someone who follows us - const indexOfFollower = event.followers.findIndex(el => el.actorId === req.body.object.actor); - if (indexOfFollower !== -1) { - // does the id we have match the id we are being given - if (event.followers[indexOfFollower].followId === req.body.object.id) { - // we have a match and can trust the Undo! remove this person from the followers list - event.followers.splice(indexOfFollower, 1); - event.save() - .then(() => { - addToLog("removeEventFollower", "success", "Follower removed from event " + eventID); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("removeEventFollower", "error", "Attempt to remove follower from event " + eventID + " failed with error: " + err); - return res.send('Database error, please try again :('); - }); - } - } - }); -} - -function _handleAcceptEvent(req, res) { - let { name, attributedTo, inReplyTo, to, actor } = req.body; - if (Array.isArray(to)) { - to = to[0]; - } - const eventID = to.replace(`https://${domain}/`, ''); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // does the id we got match the id of a thing we sent out - const message = event.activityPubMessages.find(el => el.id === req.body.object); - if (message) { - // it's a match - request({ - url: actor, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, body) { - body = JSON.parse(body) - // if this account is NOT already in our attendees list, add it - if (!event.attendees.map(el => el.id).includes(actor)) { - const attendeeName = body.preferredUsername || body.name || actor; - const newAttendee = { - name: attendeeName, - status: 'attending', - id: actor, - number: 1, - }; - event.attendees.push(newAttendee); - event.save() - .then((fullEvent) => { - addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); - // get the new attendee with its hidden id from the full event - let fullAttendee = fullEvent.attendees.find(el => el.id === actor); - // send a "click here to remove yourself" link back to the user as a DM - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Note", - "content": `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, - "tag": [{ "type": "Mention", "href": newAttendee.id, "name": newAttendee.name }] - } - // send direct message to user - sendDirectMessage(jsonObject, newAttendee.id, event.id); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + err); - return res.status(500).send('Database error, please try again :('); - }); - } - else { - // it's a duplicate and this person is already rsvped so just say OK - return res.status(200).send("Attendee is already registered."); - } - }); - } - }); -} - -function _handleUndoAcceptEvent(req, res) { - let { name, attributedTo, inReplyTo, to, actor } = req.body; - if (Array.isArray(to)) { - to = to[0]; - } - const eventID = to.replace(`https://${domain}/`, ''); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // does the id we got match the id of a thing we sent out - const message = event.activityPubMessages.find(el => el.id === req.body.object.object); - if (message) { - // it's a match - Event.update( - { id: eventID }, - { $pull: { attendees: { id: actor } } } - ) - .then(response => { - addToLog("oneClickUnattend", "success", "Attendee removed via one click unattend " + req.params.eventID); - }); - } - }); -} - -function _handleCreateNote(req, res) { - // figure out what this is in reply to -- it should be addressed specifically to us - let { name, attributedTo, inReplyTo, to } = req.body.object; - // if it's an array just grab the first element, since a poll should only broadcast back to the pollster - if (Array.isArray(to)) { - to = to[0]; - } - const eventID = to.replace(`https://${domain}/`, ''); - // make sure this person is actually a follower - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // is this even someone who follows us - const indexOfFollower = event.followers.findIndex(el => el.actorId === req.body.object.attributedTo); - if (indexOfFollower !== -1) { - // compare the inReplyTo to its stored message, if it exists and it's going to the right follower then this is a valid reply - const message = event.activityPubMessages.find(el => { - const content = JSON.parse(el.content); - return inReplyTo === (content.object && content.object.id); - }); - if (message) { - const content = JSON.parse(message.content); - // check if the message we sent out was sent to the actor this incoming message is attributedTo - if (content.to[0] === attributedTo) { - // it's a match, this is a valid poll response, add RSVP to database - // fetch the profile information of the user - request({ - url: attributedTo, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, body) { - body = JSON.parse(body) - // if this account is NOT already in our attendees list, add it - if (!event.attendees.map(el => el.id).includes(attributedTo)) { - const attendeeName = body.preferredUsername || body.name || attributedTo; - const newAttendee = { - name: attendeeName, - status: 'attending', - id: attributedTo, - number: 1, - }; - event.attendees.push(newAttendee); - event.save() - .then((fullEvent) => { - addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); - // get the new attendee with its hidden id from the full event - let fullAttendee = fullEvent.attendees.find(el => el.id === attributedTo); - // send a "click here to remove yourself" link back to the user as a DM - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Note", - "content": `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, - "tag": [{ "type": "Mention", "href": newAttendee.id, "name": newAttendee.name }] - } - // send direct message to user - sendDirectMessage(jsonObject, newAttendee.id, event.id); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + err); - return res.status(500).send('Database error, please try again :('); - }); - } - else { - // it's a duplicate and this person is already rsvped so just say OK - return res.status(200).send("Attendee is already registered."); - } - }); - } - } - } - }); -} - -function _handleDelete(req, res) { - const deleteObjectId = req.body.object.id; - // find all events with comments from the author - Event.find({ - "comments.actorId": req.body.actor - }, function (err, events) { - if (!events) { - return res.sendStatus(404); - } - - // find the event with THIS comment from the author - let eventWithComment = events.find(event => { - let comments = event.comments; - return comments.find(comment => { - if (!comment.activityJson) { - return false; - } - return JSON.parse(comment.activityJson).object.id === req.body.object.id; - }) - }); - - if (!eventWithComment) { - return res.sendStatus(404); - } - - // delete the comment - // find the index of the comment, it should have an activityJson field because from an AP server you can only delete an AP-originated comment (and of course it needs to be yours) - let indexOfComment = eventWithComment.comments.findIndex(comment => { - return comment.activityJson && JSON.parse(comment.activityJson).object.id === req.body.object.id; - }); - eventWithComment.comments.splice(indexOfComment, 1); - eventWithComment.save() - .then(() => { - addToLog("deleteComment", "success", "Comment deleted from event " + eventWithComment.id); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("deleteComment", "error", "Attempt to delete comment " + req.body.object.id + "from event " + eventWithComment.id + " failed with error: " + err); - return res.sendStatus(500); - }); - }); -} - -function _handleCreateNoteComment(req, res) { - // figure out what this is in reply to -- it should be addressed specifically to us - let { attributedTo, inReplyTo, to, cc } = req.body.object; - // normalize cc into an array - if (typeof cc === 'string') { - cc = [cc]; - } - // normalize to into an array - if (typeof to === 'string') { - to = [to]; - } - - // if this is a public message (in the to or cc fields) - if (to.includes('https://www.w3.org/ns/activitystreams#Public') || (Array.isArray(cc) && cc.includes('https://www.w3.org/ns/activitystreams#Public'))) { - // figure out which event(s) of ours it was addressing - let ourEvents = cc.filter(el => el.includes(`https://${domain}/`)) - .map(el => el.replace(`https://${domain}/`, '')); - // comments should only be on one event. if more than one, ignore (spam, probably) - if (ourEvents.length === 1) { - let eventID = ourEvents[0]; - // add comment - let commentID = nanoid(); - // get the actor for the commenter - request({ - url: req.body.actor, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, actor) { - if (!error) { - const parsedActor = JSON.parse(actor); - const name = parsedActor.preferredUsername || parsedActor.name || req.body.actor; - const newComment = { - id: commentID, - actorId: req.body.actor, - activityId: req.body.object.id, - author: name, - content: sanitizeHtml(req.body.object.content, { allowedTags: [], allowedAttributes: {} }).replace('@' + eventID, ''), - timestamp: moment(), - activityJson: JSON.stringify(req.body), - actorJson: actor - }; - - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) { - return res.sendStatus(404); - } - if (!event.usersCanComment) { - return res.sendStatus(200); - } - event.comments.push(newComment); - event.save() - .then(() => { - addToLog("addEventComment", "success", "Comment added to event " + eventID); - const guidObject = crypto.randomBytes(16).toString('hex'); - const jsonObject = req.body.object; - jsonObject.attributedTo = newComment.actorId; - broadcastAnnounceMessage(jsonObject, event.followers, eventID) - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("addEventComment", "error", "Attempt to add comment to event " + eventID + " failed with error: " + err); - res.status(500).send('Database error, please try again :(' + err); - }); - }); - } - }); - } // end ourevent - } // end public message -} - -function processInbox(req, res) { - if (!isFederated) return res.sendStatus(404); - try { - // if a Follow activity hits the inbox - if (typeof req.body.object === 'string' && req.body.type === 'Follow') { - _handleFollow(req, res); - } - // if an Undo activity with a Follow object hits the inbox - if (req.body && req.body.type === 'Undo' && req.body.object && req.body.object.type === 'Follow') { - _handleUndoFollow(req, res); - } - // if an Accept activity with the id of the Event we sent out hits the inbox, it is an affirmative RSVP - if (req.body && req.body.type === 'Accept' && req.body.object && typeof req.body.object === 'string') { - _handleAcceptEvent(req, res); - } - // if an Undo activity containing an Accept containing the id of the Event we sent out hits the inbox, it is an undo RSVP - if (req.body && req.body.type === 'Undo' && req.body.object && req.body.object.object && typeof req.body.object.object === 'string' && req.body.object.type === 'Accept') { - _handleUndoAcceptEvent(req, res); - } - // if a Create activity with a Note object hits the inbox, and it's a reply, it might be a vote in a poll - if (req.body && req.body.type === 'Create' && req.body.object && req.body.object.type === 'Note' && req.body.object.inReplyTo && req.body.object.to) { - _handleCreateNote(req, res); - } - // if a Delete activity hits the inbox, it might a deletion of a comment - if (req.body && req.body.type === 'Delete') { - _handleDelete(req, res); - } - // if we are CC'ed on a public or unlisted Create/Note, then this is a comment to us we should boost (Announce) to our followers - if (req.body && req.body.type === 'Create' && req.body.object && req.body.object.type === 'Note' && req.body.object.to) { - _handleCreateNoteComment(req, res); - } // CC'ed - } - catch (e) { - console.log('Error in processing inbox:', e) - } -} - -function createWebfinger(eventID, domain) { - return { - 'subject': `acct:${eventID}@${domain}`, - - 'links': [ - { - 'rel': 'self', - 'type': 'application/activity+json', - 'href': `https://${domain}/${eventID}` - } - ] - }; -} - -module.exports = { - processInbox, - sendAcceptMessage, - sendDirectMessage, - broadcastAnnounceMessage, - broadcastUpdateMessage, - broadcastDeleteMessage, - broadcastCreateMessage, - signAndSend, - createActivityPubActor, - updateActivityPubActor, - createActivityPubEvent, - updateActivityPubEvent, - createFeaturedPost, - createWebfinger, -} diff --git a/app.js b/app.js deleted file mode 100755 index c6e0647..0000000 --- a/app.js +++ /dev/null @@ -1,54 +0,0 @@ -const express = require('express'); -const path = require('path'); -const session = require('express-session'); -const cors = require('cors'); -const routes = require('./routes'); -const hbs = require('express-handlebars'); -const bodyParser = require('body-parser'); - -const app = express(); - -// Configuration // - -//app.use(cors()); -//app.use(bodyParser.json()); -//app.use(session({ secret: 'slartibartfast', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); - - -// View engine // -hbsInstance = hbs.create({ - defaultLayout: 'main', - partialsDir: ['views/partials/'], - layoutsDir: 'views/layouts/', - helpers: { - plural: function(number, text) { - var singular = number === 1; - // If no text parameter was given, just return a conditional s. - if (typeof text !== 'string') return singular ? '' : 's'; - // Split with regex into group1/group2 or group1(group3) - var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); - // If no match, just append a conditional s. - if (!match) return text + (singular ? '' : 's'); - // We have a good match, so fire away - return singular && match[1] // Singular case - || - match[2] // Plural case: 'bagel/bagels' --> bagels - || - match[1] + (match[3] || 's'); // Plural case: 'bagel(s)' or 'bagel' --> bagels - } - } -}); -app.engine('handlebars', hbsInstance.engine); -app.set('view engine', 'handlebars'); -app.set('hbsInstance', hbsInstance); - -// Static files // - -app.use(express.static('public')); - -// Router // -app.use(bodyParser.json({ type: "application/activity+json" })); // support json encoded bodies -app.use(bodyParser.urlencoded({ extended: true })); -app.use('/', routes); - -module.exports = app; diff --git a/config/api-example.js b/config/api-example.js deleted file mode 100644 index 9202f0a..0000000 --- a/config/api-example.js +++ /dev/null @@ -1,8 +0,0 @@ -// Which of these fields are used depends on the 'mailService' config entry in config/domain.js -module.exports = { - 'sendgrid' : '', // If using SendGrid, the Sendgrid API key goes here - 'smtpServer': '', // If using Nodemailer, your SMTP server hostname goes here - 'smtpPort': '', // If using Nodemailer, your SMTP server port goes here - 'smtpUsername': '', // If using Nodemailer, your SMTP server username goes here - 'smtpPassword': '' // If using Nodemailer, your SMTP password goes here -}; diff --git a/config/database-docker.js b/config/database-docker.js deleted file mode 100644 index 7847097..0000000 --- a/config/database-docker.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - 'url' : 'mongodb://mongo:27017/gathio' // For dockerised MongoDB connection -}; diff --git a/config/database-example.js b/config/database-example.js deleted file mode 100644 index 4aa4c4d..0000000 --- a/config/database-example.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - 'url' : 'mongodb://localhost:27017/gathio' // For local MongoDB connection -}; diff --git a/config/domain-example.js b/config/domain-example.js deleted file mode 100644 index 19c797a..0000000 --- a/config/domain-example.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - // Your domain goes here. If there is a port it should be 'domain:port', but otherwise just 'domain' - 'domain' : 'localhost:3000' , - 'port': '3000', - 'email': 'contact@example.com', - 'mailService': 'nodemailer', // Which mail service to use to send emails to attendees. Options are 'nodemailer' or 'sendgrid'. Configure settings for the mail service in config/api.js.z - 'sitename': 'gathio', - 'isFederated': true, - // If left blank, this defaults to https://yourdomain.com/images/gathio-email-logo.gif. Set a full URL here to change it to your own logo (or just change the file itself) - 'logo_url': '', - // Show a Ko-Fi box to donate money to Raphael Kabo (Gathio's creator) on the front page - 'showKofi': false, -}; diff --git a/config/gathio.service b/config/gathio.service deleted file mode 100644 index 447d44f..0000000 --- a/config/gathio.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=GathIO -After=network.target - -[Service] -Type=simple -User=gathio -WorkingDirectory=/srv/gathio -ExecStart=/usr/bin/npm start -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/helpers.js b/helpers.js deleted file mode 100644 index bf95e27..0000000 --- a/helpers.js +++ /dev/null @@ -1,57 +0,0 @@ -const domain = require('./config/domain.js').domain; -const siteName = require('./config/domain.js').sitename; - -const mongoose = require('mongoose'); -const Log = mongoose.model('Log'); -var moment = require('moment-timezone'); -const icalGenerator = require('ical-generator'); - -// LOGGING - -function addToLog(process, status, message) { - let logEntry = new Log({ - status: status, - process: process, - message: message, - timestamp: moment() - }); - logEntry.save().catch(() => { console.log("Error saving log entry!") }); -} - -function exportIcal(events, calendarName) { - // Create a new icalGenerator... generator - const cal = icalGenerator({ - name: calendarName || siteName, - x: { - 'X-WR-CALNAME': calendarName || siteName, - }, - }); - if (events instanceof Array === false) { - events = [ events ]; - } - events.forEach(event => { - // Add the event to the generator - cal.createEvent({ - start: moment.tz(event.start, event.timezone), - end: moment.tz(event.end, event.timezone), - timezone: event.timezone, - timestamp: moment(), - summary: event.name, - description: event.description, - organizer: { - name: event.hostName || "Anonymous", - email: event.creatorEmail || 'anonymous@anonymous.com', - }, - location: event.location, - url: 'https://' + domain + '/' + event.id - }); - }); - // Stringify it! - const string = cal.toString(); - return string; -} - -module.exports = { - addToLog, - exportIcal, -} diff --git a/models/Event.js b/models/Event.js deleted file mode 100755 index d800077..0000000 --- a/models/Event.js +++ /dev/null @@ -1,254 +0,0 @@ -const mongoose = require('mongoose'); - -const Attendees = new mongoose.Schema({ - name: { - type: String, - trim: true - }, - status: { - type: String, - trim: true - }, - email: { - type: String, - trim: true - }, - removalPassword: { - type: String, - trim: true, - unique: true, - sparse: true, - }, - id: { - type: String, - trim: true, - unique: true, - sparse: true, - }, - // The number of people that are attending under one 'attendee' object - number: { - type: Number, - trim: true, - default: 1 - }, - created: Date, -}) - -const Followers = new mongoose.Schema({ - // this is the id of the original follow *request*, which we use to validate Undo events - followId: { - type: String, - trim: true - }, - // this is the actual remote user profile id - actorId: { - type: String, - trim: true - }, - // this is the stringified JSON of the entire user profile - actorJson: { - type: String, - trim: true - }, - name: { - type: String, - trim: true - }, -}, { _id: false }) - -const ReplySchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - sparse: true - }, - author: { - type: String, - trim: true, - required: true - }, - content: { - type: String, - trim: true, - required: true - }, - timestamp: { - type: Date, - trim: true, - required: true - } -}) - -const ActivityPubMessages = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - sparse: true - }, - content: { - type: String, - trim: true, - required: true - } -}) - -const CommentSchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - sparse: true - }, - author: { - type: String, - trim: true, - required: true - }, - content: { - type: String, - trim: true, - required: true - }, - timestamp: { - type: Date, - trim: true, - required: true - }, - activityJson: { - type: String, - trim: true - }, - actorJson: { - type: String, - trim: true - }, - activityId: { - type: String, - trim: true - }, - actorId: { - type: String, - trim: true - }, - replies: [ReplySchema] -}) - -const EventSchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true - }, - type: { - type: String, - trim: true, - required: true - }, - name: { - type: String, - trim: true, - required: true - }, - location: { - type: String, - trim: true, - required: true - }, - start: { // Stored as a UTC timestamp - type: Date, - trim: true, - required: true - }, - end: { // Stored as a UTC timestamp - type: Date, - trim: true, - required: true - }, - timezone: { - type: String, - default: 'Etc/UTC' - }, - description: { - type: String, - trim: true, - required: true - }, - image: { - type: String, - trim: true - }, - url: { - type: String, - trim: true - }, - creatorEmail: { - type: String, - trim: true - }, - hostName: { - type: String, - trim: true - }, - viewPassword: { - type: String, - trim: true - }, - editPassword: { - type: String, - trim: true - }, - editToken: { - type: String, - trim: true, - minlength: 32, - maxlength: 32 - }, - eventGroup: { type: mongoose.Schema.Types.ObjectId, ref: 'EventGroup' }, - usersCanAttend: { - type: Boolean, - trim: true, - default: false - }, - showUsersList: { - type: Boolean, - trim: true, - default: false - }, - usersCanComment: { - type: Boolean, - trim: true, - default: false - }, - firstLoad: { - type: Boolean, - trim: true, - default: true - }, - attendees: [Attendees], - maxAttendees: { - type: Number - }, - comments: [CommentSchema], - activityPubActor: { - type: String, - trim: true - }, - activityPubEvent: { - type: String, - trim: true - }, - publicKey: { - type: String, - trim: true - }, - privateKey: { - type: String, - trim: true - }, - followers: [Followers], - activityPubMessages: [ActivityPubMessages] -}); - -module.exports = mongoose.model('Event', EventSchema); diff --git a/models/EventGroup.js b/models/EventGroup.js deleted file mode 100755 index c70ef95..0000000 --- a/models/EventGroup.js +++ /dev/null @@ -1,57 +0,0 @@ -const mongoose = require('mongoose'); - -const Subscriber = new mongoose.Schema({ - email: { - type: String, - trim: true - }, -}) - -const EventGroupSchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true - }, - name: { - type: String, - trim: true, - required: true - }, - description: { - type: String, - trim: true, - required: true - }, - image: { - type: String, - trim: true - }, - url: { - type: String, - trim: true - }, - creatorEmail: { - type: String, - trim: true - }, - hostName: { - type: String, - trim: true - }, - editToken: { - type: String, - trim: true, - minlength: 32, - maxlength: 32 - }, - firstLoad: { - type: Boolean, - trim: true, - default: true - }, - events: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Event' }], - subscribers: [Subscriber], -}); - -module.exports = mongoose.model('EventGroup', EventGroupSchema); diff --git a/models/Log.js b/models/Log.js deleted file mode 100755 index 95a3ab3..0000000 --- a/models/Log.js +++ /dev/null @@ -1,26 +0,0 @@ -const mongoose = require('mongoose'); - -const LogSchema = new mongoose.Schema({ - status: { - type: String, - trim: true, - required: true - }, - process: { - type: String, - trim: true, - required: true - }, - message: { - type: String, - trim: true, - required: true - }, - timestamp: { - type: Date, - trim: true, - required: true - } -}); - -module.exports = mongoose.model('Log', LogSchema); diff --git a/package.json b/package.json index a5c41b5..0a3d018 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "start": "nodemon ./start.js" + "build": "tsc", + "start": "node dist/start.js", + "dev": "nodemon -e ts,js --watch src --exec \"pnpm run build ; pnpm run start\"" }, "engines": { "node": ">=16.16.0" @@ -39,6 +41,7 @@ }, "devDependencies": { "eslint": "^8.40.0", - "nodemon": "^2.0.22" + "nodemon": "^2.0.22", + "typescript": "^5.0.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 484ba4f..972ab1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ devDependencies: nodemon: specifier: ^2.0.22 version: 2.0.22 + typescript: + specifier: ^5.0.4 + version: 5.0.4 packages: @@ -2772,6 +2775,12 @@ packages: mime-types: 2.1.35 dev: false + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + dev: true + /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} diff --git a/routes.js b/routes.js deleted file mode 100755 index c9867e3..0000000 --- a/routes.js +++ /dev/null @@ -1,1976 +0,0 @@ -const fs = require('fs'); - -const express = require('express'); - -const mongoose = require('mongoose'); - -// This alphabet (used to generate all event, group, etc. IDs) is missing '-' -// because ActivityPub doesn't like it in IDs -const { customAlphabet } = require('nanoid'); -const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_', 21); - -const randomstring = require("randomstring"); - -const { body, validationResult } = require('express-validator'); - -const router = express.Router(); - -const Event = mongoose.model('Event'); -const EventGroup = mongoose.model('EventGroup'); -const addToLog = require('./helpers.js').addToLog; - -var moment = require('moment-timezone'); - -const marked = require('marked'); - -const generateRSAKeypair = require('generate-rsa-keypair'); -const crypto = require('crypto'); -const request = require('request'); -const niceware = require('niceware'); - -const domain = require('./config/domain.js').domain; -const contactEmail = require('./config/domain.js').email; -const mailService = require('./config/domain.js').mailService; -const siteName = require('./config/domain.js').sitename; -const siteLogo = require('./config/domain.js').logo_url; -let isFederated = require('./config/domain.js').isFederated; -let showKofi = require('./config/domain.js').showKofi; -// if the federation config isn't set, things are federated by default -if (isFederated === undefined) { - isFederated = true; -} -const ap = require('./activitypub.js'); - -// Extra marked renderer (used to render plaintext event description for page metadata) -// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/ -// ? to ? helper -function htmlEscapeToText(text) { - return text.replace(/\&\#[0-9]*;|&/g, function (escapeCode) { - if (escapeCode.match(/amp/)) { - return '&'; - } - return String.fromCharCode(escapeCode.match(/[0-9]+/)); - }); -} - -function render_plain() { - var render = new marked.Renderer(); - // render just the text of a link, strong, em - render.link = function (href, title, text) { - return text; - }; - render.strong = function (text) { - return text; - } - render.em = function (text) { - return text; - } - // render just the text of a paragraph - render.paragraph = function (text) { - return htmlEscapeToText(text) + '\r\n'; - }; - // render nothing for headings, images, and br - render.heading = function (text, level) { - return ''; - }; - render.image = function (href, title, text) { - return ''; - }; - render.br = function () { - return ''; - }; - return render; -} - -const ical = require('ical'); -const { exportIcal } = require('./helpers.js'); - -const sgMail = require('@sendgrid/mail'); -const nodemailer = require("nodemailer"); - -const apiCredentials = require('./config/api.js'); - -let sendEmails = false; -let nodemailerTransporter; -if (mailService) { - switch (mailService) { - case 'sendgrid': - sgMail.setApiKey(apiCredentials.sendgrid); - console.log("Sendgrid is ready to send emails."); - sendEmails = true; - break; - case 'nodemailer': - nodemailerTransporter = nodemailer.createTransport({ - host: apiCredentials.smtpServer, - port: apiCredentials.smtpPort, - secure: false, // true for 465, false for other ports - auth: { - user: apiCredentials.smtpUsername, // generated ethereal user - pass: apiCredentials.smtpPassword, // generated ethereal password - }, - }); - nodemailerTransporter.verify((error, success) => { - if (error) { - console.log(error); - } else { - console.log("Nodemailer SMTP server is ready to send emails."); - sendEmails = true; - } - }); - break; - default: - console.error('You have not configured this Gathio instance to send emails! This means that event creators will not receive emails when their events are created, which means they may end up locked out of editing events. Consider setting up an email service.') - } -} - -const fileUpload = require('express-fileupload'); -var Jimp = require('jimp'); -router.use(fileUpload()); - -// SCHEDULED DELETION -const schedule = require('node-schedule'); -schedule.scheduleJob('59 23 * * *', function (fireDate) { - const too_old = moment.tz('Etc/UTC').subtract(7, 'days').toDate(); - console.log("Old event deletion running! Deleting all events concluding before ", too_old); - - Event.find({ end: { $lte: too_old } }).then((oldEvents) => { - oldEvents.forEach(event => { - const deleteEventFromDB = (id) => { - Event.remove({ "_id": id }) - .then(response => { - addToLog("deleteOldEvents", "success", "Old event " + id + " deleted"); - }).catch((err) => { - addToLog("deleteOldEvents", "error", "Attempt to delete old event " + id + " failed with error: " + err); - }); - } - - if (event.image) { - fs.unlink(global.appRoot + '/public/events/' + event.image, (err) => { - if (err) { - addToLog("deleteOldEvents", "error", "Attempt to delete event image for old event " + event.id + " failed with error: " + err); - } - // Image removed - addToLog("deleteOldEvents", "error", "Image deleted for old event " + event.id); - }) - } - // Check if event has ActivityPub fields - if (event.activityPubActor && event.activityPubEvent) { - // Broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information - const guidUpdateObject = crypto.randomBytes(16).toString('hex'); - const jsonUpdateObject = JSON.parse(event.activityPubActor); - const jsonEventObject = JSON.parse(event.activityPubEvent); - // first broadcast AP messages, THEN delete from DB - ap.broadcastDeleteMessage(jsonUpdateObject, event.followers, event.id, function (statuses) { - ap.broadcastDeleteMessage(jsonEventObject, event.followers, event.id, function (statuses) { - deleteEventFromDB(event._id); - }); - }); - } else { - // No ActivityPub data - simply delete the event - deleteEventFromDB(event._id); - } - }) - }).catch((err) => { - addToLog("deleteOldEvents", "error", "Attempt to delete old event " + event.id + " failed with error: " + err); - }); - - // TODO: While we're here, also remove all provisioned event attendees over a day - // old (they're not going to become active) -}); - -// FRONTEND ROUTES - -router.get('/', (req, res) => { - res.render('home', { - domain, - email: contactEmail, - siteName, - showKofi, - }); -}); - -router.get('/new', (req, res) => { - res.render('home'); -}); - -router.get('/new/event', (req, res) => { - res.render('newevent', { - domain: domain, - email: contactEmail, - siteName: siteName, - }); -}); -router.get('/new/event/public', (req, res) => { - let isPrivate = false; - let isPublic = true; - let isOrganisation = false; - let isUnknownType = false; - res.render('newevent', { - title: 'New event', - isPrivate: isPrivate, - isPublic: isPublic, - isOrganisation: isOrganisation, - isUnknownType: isUnknownType, - eventType: 'public', - domain: domain, - email: contactEmail, - siteName: siteName, - }); -}) - -// return the JSON for the featured/pinned post for this event -router.get('/:eventID/featured', (req, res) => { - if (!isFederated) return res.sendStatus(404); - const { eventID } = req.params; - const guidObject = crypto.randomBytes(16).toString('hex'); - const featured = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${eventID}/featured`, - "type": "OrderedCollection", - "orderedItems": [ - ap.createFeaturedPost(eventID) - ] - } - res.json(featured); -}); - -// return the JSON for a given activitypub message -router.get('/:eventID/m/:hash', (req, res) => { - if (!isFederated) return res.sendStatus(404); - const { hash, eventID } = req.params; - const id = `https://${domain}/${eventID}/m/${hash}`; - - Event.findOne({ - id: eventID - }) - .then((event) => { - if (!event) { - res.status(404); - res.render('404', { url: req.url }); - } - else { - const message = event.activityPubMessages.find(el => el.id === id); - if (message) { - return res.json(JSON.parse(message.content)); - } - else { - res.status(404); - return res.render('404', { url: req.url }); - } - } - }) - .catch((err) => { - addToLog("getActivityPubMessage", "error", "Attempt to get Activity Pub Message for " + id + " failed with error: " + err); - res.status(404); - res.render('404', { url: req.url }); - return; - }); -}); - -// return the webfinger record required for the initial activitypub handshake -router.get('/.well-known/webfinger', (req, res) => { - if (!isFederated) return res.sendStatus(404); - let resource = req.query.resource; - if (!resource || !resource.includes('acct:')) { - return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.'); - } - else { - // "foo@domain" - let activityPubAccount = resource.replace('acct:', ''); - // "foo" - let eventID = activityPubAccount.replace(/@.*/, ''); - Event.findOne({ - id: eventID - }) - .then((event) => { - if (!event) { - res.status(404); - res.render('404', { url: req.url }); - } - else { - res.json(ap.createWebfinger(eventID, domain)); - } - }) - .catch((err) => { - addToLog("renderWebfinger", "error", "Attempt to render webfinger for " + req.params.eventID + " failed with error: " + err); - res.status(404); - res.render('404', { url: req.url }); - return; - }); - } -}); - -router.get('/:eventID', (req, res) => { - Event.findOne({ - id: req.params.eventID - }) - .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is - .populate('eventGroup') - .then((event) => { - if (event) { - const parsedLocation = event.location.replace(/\s+/g, '+'); - let displayDate; - if (moment.tz(event.end, event.timezone).isSame(event.start, 'day')) { - // Happening during one day - displayDate = moment.tz(event.start, event.timezone).format('dddd D MMMM YYYY [from] h:mm a') + moment.tz(event.end, event.timezone).format(' [to] h:mm a [](z)[]'); - } - else { - displayDate = moment.tz(event.start, event.timezone).format('dddd D MMMM YYYY [at] h:mm a') + moment.tz(event.end, event.timezone).format(' [–] dddd D MMMM YYYY [at] h:mm a [](z)[]'); - } - let eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString(); - let eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString(); - let parsedStart = moment.tz(event.start, event.timezone).format('YYYYMMDD[T]HHmmss'); - let parsedEnd = moment.tz(event.end, event.timezone).format('YYYYMMDD[T]HHmmss'); - let eventHasConcluded = false; - if (moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone))) { - eventHasConcluded = true; - } - let eventHasBegun = false; - if (moment.tz(event.start, event.timezone).isBefore(moment.tz(event.timezone))) { - eventHasBegun = true; - } - let fromNow = moment.tz(event.start, event.timezone).fromNow(); - let parsedDescription = marked.parse(event.description); - let eventEditToken = event.editToken; - - let escapedName = event.name.replace(/\s+/g, '+'); - - let eventHasCoverImage = false; - if (event.image) { - eventHasCoverImage = true; - } - else { - eventHasCoverImage = false; - } - let eventHasHost = false; - if (event.hostName) { - eventHasHost = true; - } - else { - eventHasHost = false; - } - let firstLoad = false; - if (event.firstLoad === true) { - firstLoad = true; - Event.findOneAndUpdate({ id: req.params.eventID }, { firstLoad: false }, function (err, raw) { - if (err) { - res.send(err); - } - }); - } - let editingEnabled = false; - if (Object.keys(req.query).length !== 0) { - if (!req.query.e) { - editingEnabled = false; - console.log("No edit token set"); - } - else { - if (req.query.e === eventEditToken) { - editingEnabled = true; - } - else { - editingEnabled = false; - } - } - } - let eventAttendees = event.attendees.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)) - .map(el => { - if (!el.id) { - el.id = el._id; - } - if (el.number > 1) { - el.name = `${el.name} (${el.number} people)`; - } - return el; - }) - .filter((obj, pos, arr) => { - return obj.status === 'attending' && arr.map(mapObj => mapObj.id).indexOf(obj.id) === pos; - }); - - let spotsRemaining, noMoreSpots; - let numberOfAttendees = eventAttendees.reduce((acc, attendee) => { - if (attendee.status === 'attending') { - return acc + attendee.number || 1; - } - return acc; - }, 0); - if (event.maxAttendees) { - spotsRemaining = event.maxAttendees - numberOfAttendees; - if (spotsRemaining <= 0) { - noMoreSpots = true; - } - } - let metadata = { - title: event.name, - description: marked.parse(event.description, { renderer: render_plain() }).split(" ").splice(0, 40).join(" ").trim(), - image: (eventHasCoverImage ? `https://${domain}/events/` + event.image : null), - url: `https://${domain}/` + req.params.eventID - }; - if (req.headers.accept && (req.headers.accept.includes('application/activity+json') || req.headers.accept.includes('application/json') || req.headers.accept.includes('application/json+ld'))) { - res.json(JSON.parse(event.activityPubActor)); - } - else { - res.set("X-Robots-Tag", "noindex"); - res.render('event', { - domain: domain, - isFederated: isFederated, - email: contactEmail, - title: event.name, - escapedName: escapedName, - eventData: event, - eventAttendees: eventAttendees, - numberOfAttendees, - spotsRemaining: spotsRemaining, - noMoreSpots: noMoreSpots, - eventStartISO: eventStartISO, - eventEndISO: eventEndISO, - parsedLocation: parsedLocation, - parsedStart: parsedStart, - parsedEnd: parsedEnd, - displayDate: displayDate, - fromNow: fromNow, - timezone: event.timezone, - parsedDescription: parsedDescription, - editingEnabled: editingEnabled, - eventHasCoverImage: eventHasCoverImage, - eventHasHost: eventHasHost, - firstLoad: firstLoad, - eventHasConcluded: eventHasConcluded, - eventHasBegun: eventHasBegun, - metadata: metadata, - siteName: siteName - }) - } - } - else { - res.status(404); - res.render('404', { url: req.url }); - } - - }) - .catch((err) => { - addToLog("displayEvent", "error", "Attempt to display event " + req.params.eventID + " failed with error: " + err); - console.log(err) - res.status(404); - res.render('404', { url: req.url }); - return; - }); -}) - -router.get('/:eventID/followers', (req, res) => { - if (!isFederated) return res.sendStatus(404); - const eventID = req.params.eventID; - Event.findOne({ - id: eventID - }) - .then((event) => { - if (event) { - const followers = event.followers.map(el => el.actorId); - let followersCollection = { - "type": "OrderedCollection", - "totalItems": followers.length, - "id": `https://${domain}/${eventID}/followers`, - "first": { - "type": "OrderedCollectionPage", - "totalItems": followers.length, - "partOf": `https://${domain}/${eventID}/followers`, - "orderedItems": followers, - "id": `https://${domain}/${eventID}/followers?page=1` - }, - "@context": ["https://www.w3.org/ns/activitystreams"] - }; - return res.json(followersCollection); - } - else { - return res.status(400).send('Bad request.'); - } - }) -}) - -router.get('/group/:eventGroupID', (req, res) => { - EventGroup.findOne({ - id: req.params.eventGroupID - }) - .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is - .then(async (eventGroup) => { - if (eventGroup) { - let parsedDescription = marked.parse(eventGroup.description); - let eventGroupEditToken = eventGroup.editToken; - - let escapedName = eventGroup.name.replace(/\s+/g, '+'); - - let eventGroupHasCoverImage = false; - if (eventGroup.image) { - eventGroupHasCoverImage = true; - } - else { - eventGroupHasCoverImage = false; - } - let eventGroupHasHost = false; - if (eventGroup.hostName) { - eventGroupHasHost = true; - } - else { - eventGroupHasHost = false; - } - - let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); - - events.map(event => { - if (moment.tz(event.end, event.timezone).isSame(event.start, 'day')) { - // Happening during one day - event.displayDate = moment.tz(event.start, event.timezone).format('D MMM YYYY'); - } - else { - event.displayDate = moment.tz(event.start, event.timezone).format('D MMM YYYY') + moment.tz(event.end, event.timezone).format(' - D MMM YYYY'); - } - if (moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone))) { - event.eventHasConcluded = true; - } else { - event.eventHasConcluded = false; - } - return (({ id, name, displayDate, eventHasConcluded }) => ({ id, name, displayDate, eventHasConcluded }))(event); - }); - - let upcomingEventsExist = false; - if (events.some(e => e.eventHasConcluded === false)) { - upcomingEventsExist = true; - } - - let firstLoad = false; - if (eventGroup.firstLoad === true) { - firstLoad = true; - EventGroup.findOneAndUpdate({ id: req.params.eventGroupID }, { firstLoad: false }, function (err, raw) { - if (err) { - res.send(err); - } - }); - } - let editingEnabled = false; - if (Object.keys(req.query).length !== 0) { - if (!req.query.e) { - editingEnabled = false; - console.log("No edit token set"); - } - else { - if (req.query.e === eventGroupEditToken) { - editingEnabled = true; - } - else { - editingEnabled = false; - } - } - } - let metadata = { - title: eventGroup.name, - description: marked.parse(eventGroup.description, { renderer: render_plain() }).split(" ").splice(0, 40).join(" ").trim(), - image: (eventGroupHasCoverImage ? `https://${domain}/events/` + eventGroup.image : null), - url: `https://${domain}/` + req.params.eventID - }; - res.set("X-Robots-Tag", "noindex"); - res.render('eventgroup', { - domain: domain, - title: eventGroup.name, - eventGroupData: eventGroup, - escapedName: escapedName, - events: events, - upcomingEventsExist: upcomingEventsExist, - parsedDescription: parsedDescription, - editingEnabled: editingEnabled, - eventGroupHasCoverImage: eventGroupHasCoverImage, - eventGroupHasHost: eventGroupHasHost, - firstLoad: firstLoad, - metadata: metadata - }) - } - else { - res.status(404); - res.render('404', { url: req.url }); - } - - }) - .catch((err) => { - addToLog("displayEventGroup", "error", "Attempt to display event group " + req.params.eventGroupID + " failed with error: " + err); - console.log(err) - res.status(404); - res.render('404', { url: req.url }); - return; - }); -}) - -router.get('/group/:eventGroupID/feed.ics', (req, res) => { - EventGroup.findOne({ - id: req.params.eventGroupID - }) - .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is - .then(async (eventGroup) => { - if (eventGroup) { - let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); - const string = exportIcal(events, eventGroup.name); - res.set('Content-Type', 'text/calendar'); - return res.send(string); - } - }) - .catch((err) => { - addToLog("eventGroupFeed", "error", "Attempt to display event group feed for " + req.params.eventGroupID + " failed with error: " + err); - console.log(err) - res.status(404); - res.render('404', { url: req.url }); - return; - }); -}); - -router.get('/exportevent/:eventID', (req, res) => { - Event.findOne({ - id: req.params.eventID - }) - .populate('eventGroup') - .then((event) => { - if (event) { - const string = exportIcal([event]); - res.send(string); - } - }) - .catch((err) => { - addToLog("exportEvent", "error", "Attempt to export event " + req.params.eventID + " failed with error: " + err); - console.log(err) - res.status(404); - res.render('404', { url: req.url }); - return; - }); -}); - -router.get('/exportgroup/:eventGroupID', (req, res) => { - EventGroup.findOne({ - id: req.params.eventGroupID - }) - .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is - .then(async (eventGroup) => { - if (eventGroup) { - let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); - const string = exportIcal(events); - res.send(string); - } - }) - .catch((err) => { - addToLog("exportEvent", "error", "Attempt to export event group " + req.params.eventGroupID + " failed with error: " + err); - console.log(err) - res.status(404); - res.render('404', { url: req.url }); - return; - }); -}); - -// BACKEND ROUTES - -router.post('/newevent', async (req, res) => { - let eventID = nanoid(); - let editToken = randomstring.generate(); - let eventImageFilename = ""; - let isPartOfEventGroup = false; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.imageUpload.data; - eventImageFilename = await Jimp.read(eventImageBuffer) - .then(img => { - img - .resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write('./public/events/' + eventID + '.jpg'); // save - const filename = eventID + '.jpg'; - return filename; - }) - .catch(err => { - addToLog("Jimp", "error", "Attempt to edit image failed with error: " + err); - }); - } - let startUTC = moment.tz(req.body.eventStart, 'D MMMM YYYY, hh:mm a', req.body.timezone); - let endUTC = moment.tz(req.body.eventEnd, 'D MMMM YYYY, hh:mm a', req.body.timezone); - let eventGroup; - if (req.body.eventGroupCheckbox) { - eventGroup = await EventGroup.findOne({ - id: req.body.eventGroupID, - editToken: req.body.eventGroupEditToken - }) - if (eventGroup) { - isPartOfEventGroup = true; - } - } - - // generate RSA keypair for ActivityPub - let pair = generateRSAKeypair(); - - const event = new Event({ - id: eventID, - type: req.body.eventType, - name: req.body.eventName, - location: req.body.eventLocation, - start: startUTC, - end: endUTC, - timezone: req.body.timezone, - description: req.body.eventDescription, - image: eventImageFilename, - creatorEmail: req.body.creatorEmail, - url: req.body.eventURL, - hostName: req.body.hostName, - viewPassword: req.body.viewPassword, - editPassword: req.body.editPassword, - editToken: editToken, - eventGroup: isPartOfEventGroup ? eventGroup._id : null, - usersCanAttend: req.body.joinCheckbox ? true : false, - showUsersList: req.body.guestlistCheckbox ? true : false, - usersCanComment: req.body.interactionCheckbox ? true : false, - maxAttendees: req.body.maxAttendees, - firstLoad: true, - activityPubActor: ap.createActivityPubActor(eventID, domain, pair.public, marked.parse(req.body.eventDescription), req.body.eventName, req.body.eventLocation, eventImageFilename, startUTC, endUTC, req.body.timezone), - activityPubEvent: ap.createActivityPubEvent(req.body.eventName, startUTC, endUTC, req.body.timezone, req.body.eventDescription, req.body.eventLocation), - activityPubMessages: [{ id: `https://${domain}/${eventID}/m/featuredPost`, content: JSON.stringify(ap.createFeaturedPost(eventID, req.body.eventName, startUTC, endUTC, req.body.timezone, req.body.eventDescription, req.body.eventLocation)) }], - publicKey: pair.public, - privateKey: pair.private - }); - event.save() - .then((event) => { - addToLog("createEvent", "success", "Event " + eventID + "created"); - // Send email with edit link - if (req.body.creatorEmail && sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/createevent.handlebars', { eventID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail - }, - subject: `${siteName}: ${req.body.eventName}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - // If the event was added to a group, send an email to any group - // subscribers - if (event.eventGroup && sendEmails) { - EventGroup.findOne({ _id: event.eventGroup._id }) - .then((eventGroup) => { - const subscribers = eventGroup.subscribers.reduce((acc, current) => { - if (acc.includes(current.email)) { - return acc; - } - return [current.email, ...acc]; - }, []); - subscribers.forEach(emailAddress => { - req.app.get('hbsInstance').renderView('./views/emails/eventgroupupdated.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, eventGroupName: eventGroup.name, eventName: event.name, eventID: event.id, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(emailAddress), cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: emailAddress, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New event in ${eventGroup.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - }); - }); - } - res.writeHead(302, { - 'Location': '/' + eventID + '?e=' + editToken - }); - res.end(); - }) - .catch((err) => { res.status(500).send('Database error, please try again :( - ' + err); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); -}); - -router.post('/importevent', (req, res) => { - let eventID = nanoid(); - let editToken = randomstring.generate(); - if (req.files && Object.keys(req.files).length !== 0) { - let iCalObject = ical.parseICS(req.files.icsImportControl.data.toString('utf8')); - let importedEventData = iCalObject[Object.keys(iCalObject)]; - - let creatorEmail; - if (req.body.creatorEmail) { - creatorEmail = req.body.creatorEmail; - } else if (importedEventData.organizer) { - creatorEmail = importedEventData.organizer.val.replace("MAILTO:", ""); - } - - const event = new Event({ - id: eventID, - type: 'public', - name: importedEventData.summary, - location: importedEventData.location, - start: importedEventData.start, - end: importedEventData.end, - timezone: typeof importedEventData.start.tz !== 'undefined' ? importedEventData.start.tz : "Etc/UTC", - description: importedEventData.description, - image: '', - creatorEmail: creatorEmail, - url: '', - hostName: importedEventData.organizer ? importedEventData.organizer.params.CN.replace(/["]+/g, '') : "", - viewPassword: '', - editPassword: '', - editToken: editToken, - usersCanAttend: false, - showUsersList: false, - usersCanComment: false, - firstLoad: true - }); - event.save() - .then(() => { - addToLog("createEvent", "success", "Event " + eventID + " created"); - // Send email with edit link - if (creatorEmail && sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/createevent.handlebars', { eventID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail - }, - subject: `${siteName}: ${importedEventData.summary}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - res.writeHead(302, { - 'Location': '/' + eventID + '?e=' + editToken - }); - res.end(); - }) - .catch((err) => { res.send('Database error, please try again :('); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); - } - else { - console.log("Files array is empty!") - res.status(500).end(); - } -}); - -router.post('/neweventgroup', (req, res) => { - let eventGroupID = nanoid(); - let editToken = randomstring.generate(); - let eventGroupImageFilename = ""; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.imageUpload.data; - Jimp.read(eventImageBuffer, (err, img) => { - if (err) addToLog("Jimp", "error", "Attempt to edit image failed with error: " + err); - img - .resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write('./public/events/' + eventGroupID + '.jpg'); // save - }); - eventGroupImageFilename = eventGroupID + '.jpg'; - } - const eventGroup = new EventGroup({ - id: eventGroupID, - name: req.body.eventGroupName, - description: req.body.eventGroupDescription, - image: eventGroupImageFilename, - creatorEmail: req.body.creatorEmail, - url: req.body.eventGroupURL, - hostName: req.body.hostName, - editToken: editToken, - firstLoad: true - }); - eventGroup.save() - .then(() => { - addToLog("createEventGroup", "success", "Event group " + eventGroupID + " created"); - // Send email with edit link - if (req.body.creatorEmail && sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/createeventgroup.handlebars', { eventGroupID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail - }, - subject: `${siteName}: ${req.body.eventGroupName}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - res.writeHead(302, { - 'Location': '/group/' + eventGroupID + '?e=' + editToken - }); - res.end(); - }) - .catch((err) => { res.send('Database error, please try again :( - ' + err); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); -}); - -router.post('/verifytoken/event/:eventID', (req, res) => { - Event.findOne({ - id: req.params.eventID, - editToken: req.body.editToken, - }) - .then(event => { - if (event) return res.sendStatus(200); - return res.sendStatus(404); - }) -}); - -router.post('/verifytoken/group/:eventGroupID', (req, res) => { - EventGroup.findOne({ - id: req.params.eventGroupID, - editToken: req.body.editToken, - }) - .then(group => { - if (group) return res.sendStatus(200); - return res.sendStatus(404); - }) -}); - - -router.post('/editevent/:eventID/:editToken', (req, res) => { - let submittedEditToken = req.params.editToken; - Event.findOne(({ - id: req.params.eventID, - })) - .then(async (event) => { - if (event.editToken === submittedEditToken) { - // Token matches - - // If there is a new image, upload that first - let eventID = req.params.eventID; - let eventImageFilename = event.image; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.imageUpload.data; - Jimp.read(eventImageBuffer, (err, img) => { - if (err) throw err; - img - .resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG - .write('./public/events/' + eventID + '.jpg'); // save - }); - eventImageFilename = eventID + '.jpg'; - } - let startUTC = moment.tz(req.body.eventStart, 'D MMMM YYYY, hh:mm a', req.body.timezone); - let endUTC = moment.tz(req.body.eventEnd, 'D MMMM YYYY, hh:mm a', req.body.timezone); - - let isPartOfEventGroup = false; - let eventGroup; - if (req.body.eventGroupCheckbox) { - eventGroup = await EventGroup.findOne({ - id: req.body.eventGroupID, - editToken: req.body.eventGroupEditToken - }) - if (eventGroup) { - isPartOfEventGroup = true; - } - } - const updatedEvent = { - name: req.body.eventName, - location: req.body.eventLocation, - start: startUTC, - end: endUTC, - timezone: req.body.timezone, - description: req.body.eventDescription, - url: req.body.eventURL, - hostName: req.body.hostName, - image: eventImageFilename, - usersCanAttend: req.body.joinCheckbox ? true : false, - showUsersList: req.body.guestlistCheckbox ? true : false, - usersCanComment: req.body.interactionCheckbox ? true : false, - maxAttendees: req.body.maxAttendeesCheckbox ? req.body.maxAttendees : null, - eventGroup: isPartOfEventGroup ? eventGroup._id : null, - activityPubActor: event.activityPubActor ? ap.updateActivityPubActor(JSON.parse(event.activityPubActor), req.body.eventDescription, req.body.eventName, req.body.eventLocation, eventImageFilename, startUTC, endUTC, req.body.timezone) : null, - activityPubEvent: event.activityPubEvent ? ap.updateActivityPubEvent(JSON.parse(event.activityPubEvent), req.body.eventName, req.body.startUTC, req.body.endUTC, req.body.timezone) : null, - } - let diffText = '

This event was just updated with new information.

    '; - let displayDate; - if (event.name !== updatedEvent.name) { - diffText += `
  • the event name changed to ${updatedEvent.name}
  • `; - } - if (event.location !== updatedEvent.location) { - diffText += `
  • the location changed to ${updatedEvent.location}
  • `; - } - if (event.start.toISOString() !== updatedEvent.start.toISOString()) { - displayDate = moment.tz(updatedEvent.start, updatedEvent.timezone).format('dddd D MMMM YYYY h:mm a'); - diffText += `
  • the start time changed to ${displayDate}
  • `; - } - if (event.end.toISOString() !== updatedEvent.end.toISOString()) { - displayDate = moment.tz(updatedEvent.end, updatedEvent.timezone).format('dddd D MMMM YYYY h:mm a'); - diffText += `
  • the end time changed to ${displayDate}
  • `; - } - if (event.timezone !== updatedEvent.timezone) { - diffText += `
  • the time zone changed to ${updatedEvent.timezone}
  • `; - } - if (event.description !== updatedEvent.description) { - diffText += `
  • the event description changed
  • `; - } - diffText += `
`; - Event.findOneAndUpdate({ id: req.params.eventID }, updatedEvent, function (err, raw) { - if (err) { - addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); - res.send(err); - } - }) - .then(() => { - addToLog("editEvent", "success", "Event " + req.params.eventID + " edited"); - // send update to ActivityPub subscribers - Event.findOne({ id: req.params.eventID }, function (err, event) { - if (!event) return; - let attendees = event.attendees.filter(el => el.id); - if (!err) { - // broadcast an identical message to all followers, will show in home timeline - const guidObject = crypto.randomBytes(16).toString('hex'); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, - "name": `RSVP to ${event.name}`, - "type": "Note", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `${diffText} See here: https://${domain}/${req.params.eventID}`, - } - ap.broadcastCreateMessage(jsonObject, event.followers, eventID) - // also broadcast an Update profile message to all followers so that at least Mastodon servers will update the local profile information - const jsonUpdateObject = JSON.parse(event.activityPubActor); - ap.broadcastUpdateMessage(jsonUpdateObject, event.followers, eventID) - // also broadcast an Update/Event for any calendar apps that are consuming our Events - const jsonEventObject = JSON.parse(event.activityPubEvent); - ap.broadcastUpdateMessage(jsonEventObject, event.followers, eventID) - - // DM to attendees - for (const attendee of attendees) { - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Note", - "content": `@${attendee.name} ${diffText} See here: https://${domain}/${req.params.eventID}`, - "tag": [{ "type": "Mention", "href": attendee.id, "name": attendee.name }] - } - // send direct message to user - ap.sendDirectMessage(jsonObject, attendee.id, eventID); - } - } - }) - // Send update to all attendees - if (sendEmails) { - Event.findOne({ id: req.params.eventID }).then((event) => { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/editevent.handlebars', { diffText, eventID: req.params.eventID, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - address: contactEmail - }, - subject: `${siteName}: ${event.name} was just edited`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { - console.log("Nothing to send!"); - } - }) - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID + '?e=' + req.params.editToken - }); - res.end(); - }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); }); - } - else { - // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: token does not match"); - } - }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); }); -}); - -router.post('/editeventgroup/:eventGroupID/:editToken', (req, res) => { - let submittedEditToken = req.params.editToken; - EventGroup.findOne(({ - id: req.params.eventGroupID, - })) - .then((eventGroup) => { - if (eventGroup.editToken === submittedEditToken) { - // Token matches - - // If there is a new image, upload that first - let eventGroupID = req.params.eventGroupID; - let eventGroupImageFilename = eventGroup.image; - if (req.files && Object.keys(req.files).length !== 0) { - let eventImageBuffer = req.files.eventGroupImageUpload.data; - Jimp.read(eventImageBuffer, (err, img) => { - if (err) throw err; - img - .resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG - .write('./public/events/' + eventGroupID + '.jpg'); // save - }); - eventGroupImageFilename = eventGroupID + '.jpg'; - } - const updatedEventGroup = { - name: req.body.eventGroupName, - description: req.body.eventGroupDescription, - url: req.body.eventGroupURL, - hostName: req.body.hostName, - image: eventGroupImageFilename - } - EventGroup.findOneAndUpdate({ id: req.params.eventGroupID }, updatedEventGroup, function (err, raw) { - if (err) { - addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); - res.send(err); - } - }) - .then(() => { - addToLog("editEventGroup", "success", "Event group " + req.params.eventGroupID + " edited"); - res.writeHead(302, { - 'Location': '/group/' + req.params.eventGroupID + '?e=' + req.params.editToken - }); - res.end(); - }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); }); - } - else { - // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: token does not match"); - } - }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); }); -}); - -router.post('/deleteimage/:eventID/:editToken', (req, res) => { - let submittedEditToken = req.params.editToken; - Event.findOne(({ - id: req.params.eventID, - })) - .then((event) => { - if (event.editToken === submittedEditToken) { - // Token matches - if (event.image) { - eventImage = event.image; - } else { - res.status(500).send('This event doesn\'t have a linked image. What are you even doing'); - } - fs.unlink(global.appRoot + '/public/events/' + eventImage, (err) => { - if (err) { - res.status(500).send(err); - addToLog("deleteEventImage", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); - } - // Image removed - addToLog("deleteEventImage", "success", "Image for event " + req.params.eventID + " deleted"); - event.image = ""; - event.save() - .then(response => { - res.status(200).send('Success'); - }) - .catch(err => { - res.status(500).send(err); - addToLog("deleteEventImage", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); - }) - }); - } - }); -}); - -router.post('/deleteevent/:eventID/:editToken', (req, res) => { - let submittedEditToken = req.params.editToken; - let eventImage; - Event.findOne(({ - id: req.params.eventID, - })) - .then((event) => { - if (event.editToken === submittedEditToken) { - // Token matches - - let eventImage; - if (event.image) { - eventImage = event.image; - } - - // broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information - const guidUpdateObject = crypto.randomBytes(16).toString('hex'); - const jsonUpdateObject = JSON.parse(event.activityPubActor); - // first broadcast AP messages, THEN delete from DB - ap.broadcastDeleteMessage(jsonUpdateObject, event.followers, req.params.eventID, function (statuses) { - Event.deleteOne({ id: req.params.eventID }, function (err, raw) { - if (err) { - res.send(err); - addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); - } - }) - .then(() => { - // Delete image - if (eventImage) { - fs.unlink(global.appRoot + '/public/events/' + eventImage, (err) => { - if (err) { - res.send(err); - addToLog("deleteEvent", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); - } - // Image removed - addToLog("deleteEvent", "success", "Event " + req.params.eventID + " deleted"); - }) - } - res.writeHead(302, { - 'Location': '/' - }); - res.end(); - - // Send emails here otherwise they don't exist lol - if (sendEmails) { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/deleteevent.handlebars', { siteName, siteLogo, domain, eventName: event.name, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - address: contactEmail - }, - subject: `${siteName}: ${event.name} was deleted`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { - console.log("Nothing to send!"); - } - } - }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); }); - }); - } - else { - // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: token does not match"); - } - }) - .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); }); -}); - -router.post('/deleteeventgroup/:eventGroupID/:editToken', (req, res) => { - let submittedEditToken = req.params.editToken; - EventGroup.findOne(({ - id: req.params.eventGroupID, - })) - .then(async (eventGroup) => { - if (eventGroup.editToken === submittedEditToken) { - // Token matches - - let linkedEvents = await Event.find({ eventGroup: eventGroup._id }); - - let linkedEventIDs = linkedEvents.map(event => event._id); - let eventGroupImage = false; - if (eventGroup.image) { - eventGroupImage = eventGroup.image; - } - - EventGroup.deleteOne({ id: req.params.eventGroupID }, function (err, raw) { - if (err) { - res.send(err); - addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); - } - }) - .then(() => { - // Delete image - if (eventGroupImage) { - fs.unlink(global.appRoot + '/public/events/' + eventGroupImage, (err) => { - if (err) { - res.send(err); - addToLog("deleteEventGroup", "error", "Attempt to delete event image for event group " + req.params.eventGroupID + " failed with error: " + err); - } - }) - } - Event.update({ _id: { $in: linkedEventIDs } }, { $set: { eventGroup: null } }, { multi: true }) - .then(response => { - console.log(response); - addToLog("deleteEventGroup", "success", "Event group " + req.params.eventGroupID + " deleted"); - res.writeHead(302, { - 'Location': '/' - }); - res.end(); - }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); - }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); - } - else { - // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: token does not match"); - } - }) - .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); -}); - -router.post('/attendee/provision', async (req, res) => { - const removalPassword = niceware.generatePassphrase(6).join('-'); - const newAttendee = { - status: 'provisioned', - removalPassword, - created: Date.now(), - }; - - const event = await Event.findOne({ id: req.query.eventID }).catch(e => { - addToLog("provisionEventAttendee", "error", "Attempt to provision attendee in event " + req.query.eventID + " failed with error: " + e); - return res.sendStatus(500); - }); - - if (!event) { - return res.sendStatus(404); - } - - event.attendees.push(newAttendee); - await event.save().catch(e => { - console.log(e); - addToLog("provisionEventAttendee", "error", "Attempt to provision attendee in event " + req.query.eventID + " failed with error: " + e); - return res.sendStatus(500); - }); - addToLog("provisionEventAttendee", "success", "Attendee provisioned in event " + req.query.eventID); - - // Return the removal password and the number of free spots remaining - let freeSpots; - if (event.maxAttendees !== null && event.maxAttendees !== undefined) { - freeSpots = event.maxAttendees - event.attendees.reduce((acc, a) => acc + (a.status === 'attending' ? (a.number || 1) : 0), 0); - } else { - freeSpots = undefined; - } - return res.json({ removalPassword, freeSpots }); -}); - -router.post('/attendevent/:eventID', async (req, res) => { - // Do not allow empty removal passwords - if (!req.body.removalPassword) { - return res.sendStatus(500); - } - const event = await Event.findOne({ id: req.params.eventID }).catch(e => { - addToLog("attendEvent", "error", "Attempt to attend event " + req.params.eventID + " failed with error: " + e); - return res.sendStatus(500); - }); - if (!event) { - return res.sendStatus(404); - } - const attendee = event.attendees.find(a => a.removalPassword === req.body.removalPassword); - if (!attendee) { - return res.sendStatus(404); - } - // Do we have enough free spots in this event to accomodate this attendee? - // First, check if the event has a max number of attendees - if (event.maxAttendees !== null && event.maxAttendees !== undefined) { - const freeSpots = event.maxAttendees - event.attendees.reduce((acc, a) => acc + (a.status === 'attending' ? (a.number || 1) : 0), 0); - if (req.body.attendeeNumber > freeSpots) { - return res.sendStatus(403); - } - } - - Event.findOneAndUpdate({ id: req.params.eventID, 'attendees.removalPassword': req.body.removalPassword }, { - "$set": { - "attendees.$.status": "attending", - "attendees.$.name": req.body.attendeeName, - "attendees.$.email": req.body.attendeeEmail, - "attendees.$.number": req.body.attendeeNumber, - } - }).then((event) => { - addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); - if (sendEmails) { - if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/addeventattendee.handlebars', { eventID: req.params.eventID, siteName, siteLogo, domain, removalPassword: req.body.removalPassword, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You're RSVPed to ${event.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - } - res.redirect(`/${req.params.eventID}`); - }) - .catch((error) => { - res.send('Database error, please try again :('); - addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + error); - }); -}); - -router.post('/unattendevent/:eventID', (req, res) => { - const removalPassword = req.body.removalPassword; - // Don't allow blank removal passwords! - if (!removalPassword) { - return res.sendStatus(500); - } - - Event.update( - { id: req.params.eventID }, - { $pull: { attendees: { removalPassword } } } - ) - .then(response => { - console.log(response) - addToLog("unattendEvent", "success", "Attendee removed self from event " + req.params.eventID); - if (sendEmails) { - if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/unattendevent.handlebars', { eventID: req.params.eventID, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID - }); - res.end(); - }) - .catch((err) => { - res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee from event " + req.params.eventID + " failed with error: " + err); - }); -}); - -// this is a one-click unattend that requires a secret URL that only the person who RSVPed over -// activitypub knows -router.get('/oneclickunattendevent/:eventID/:attendeeID', (req, res) => { - // Mastodon will "click" links that sent to its users, presumably as a prefetch? - // Anyway, this ignores the automated clicks that are done without the user's knowledge - if (req.headers['user-agent'] && req.headers['user-agent'].includes('Mastodon')) { - return res.sendStatus(200); - } - Event.update( - { id: req.params.eventID }, - { $pull: { attendees: { _id: req.params.attendeeID } } } - ) - .then(response => { - addToLog("oneClickUnattend", "success", "Attendee removed via one click unattend " + req.params.eventID); - if (sendEmails) { - // currently this is never called because we don't have the email address - if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/removeeventattendee.handlebars', { eventName: req.params.eventName, siteName, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID - }); - res.end(); - }) - .catch((err) => { - res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee by admin from event " + req.params.eventID + " failed with error: " + err); - }); -}); - -router.post('/removeattendee/:eventID/:attendeeID', (req, res) => { - Event.update( - { id: req.params.eventID }, - { $pull: { attendees: { _id: req.params.attendeeID } } } - ) - .then(response => { - console.log(response) - addToLog("removeEventAttendee", "success", "Attendee removed by admin from event " + req.params.eventID); - if (sendEmails) { - // currently this is never called because we don't have the email address - if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/removeeventattendee.handlebars', { eventName: req.params.eventName, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID - }); - res.end(); - }) - .catch((err) => { - res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee by admin from event " + req.params.eventID + " failed with error: " + err); - }); -}); - -/* - * Create an email subscription on an event group. - */ -router.post('/subscribe/:eventGroupID', (req, res) => { - const subscriber = { - email: req.body.emailAddress, - }; - if (!subscriber.email) { - return res.sendStatus(500); - } - - EventGroup.findOne(({ - id: req.params.eventGroupID, - })) - .then((eventGroup) => { - if (!eventGroup) { - return res.sendStatus(404); - } - eventGroup.subscribers.push(subscriber); - eventGroup.save(); - if (sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/subscribed.handlebars', { eventGroupName: eventGroup.name, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(subscriber.email), siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: subscriber.email, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have subscribed to an event group`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - return res.redirect(`/group/${eventGroup.id}`) - }) - .catch((error) => { - addToLog("addSubscription", "error", "Attempt to subscribe " + req.body.emailAddress + " to event group " + req.params.eventGroupID + " failed with error: " + error); - return res.sendStatus(500); - }); -}); - -/* - * Delete an existing email subscription on an event group. - */ -router.get('/unsubscribe/:eventGroupID', (req, res) => { - const email = req.query.email; - console.log(email); - if (!email) { - return res.sendStatus(500); - } - - EventGroup.update( - { id: req.params.eventGroupID }, - { $pull: { subscribers: { email } } } - ) - .then(response => { - return res.redirect('/'); - }) - .catch((error) => { - addToLog("removeSubscription", "error", "Attempt to unsubscribe " + req.query.email + " from event group " + req.params.eventGroupID + " failed with error: " + error); - return res.sendStatus(500); - }); -}); - -router.post('/post/comment/:eventID', (req, res) => { - let commentID = nanoid(); - const newComment = { - id: commentID, - author: req.body.commentAuthor, - content: req.body.commentContent, - timestamp: moment() - }; - - Event.findOne({ - id: req.params.eventID, - }, function (err, event) { - if (!event) return; - event.comments.push(newComment); - event.save() - .then(() => { - addToLog("addEventComment", "success", "Comment added to event " + req.params.eventID); - // broadcast an identical message to all followers, will show in their home timeline - // and in the home timeline of the event - const guidObject = crypto.randomBytes(16).toString('hex'); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, - "name": `Comment on ${event.name}`, - "type": "Note", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `

${req.body.commentAuthor} commented: ${req.body.commentContent}.

See the full conversation here.

`, - } - ap.broadcastCreateMessage(jsonObject, event.followers, req.params.eventID) - if (sendEmails) { - Event.findOne({ id: req.params.eventID }).then((event) => { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/addeventcomment.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, commentAuthor: req.body.commentAuthor, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New comment in ${event.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { - console.log("Nothing to send!"); - } - }); - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID - }); - res.end(); - }) - .catch((err) => { res.send('Database error, please try again :(' + err); addToLog("addEventComment", "error", "Attempt to add comment to event " + req.params.eventID + " failed with error: " + err); }); - }); -}); - -router.post('/post/reply/:eventID/:commentID', (req, res) => { - let replyID = nanoid(); - let commentID = req.params.commentID; - const newReply = { - id: replyID, - author: req.body.replyAuthor, - content: req.body.replyContent, - timestamp: moment() - }; - Event.findOne({ - id: req.params.eventID, - }, function (err, event) { - if (!event) return; - var parentComment = event.comments.id(commentID); - parentComment.replies.push(newReply); - event.save() - .then(() => { - addToLog("addEventReply", "success", "Reply added to comment " + commentID + " in event " + req.params.eventID); - // broadcast an identical message to all followers, will show in their home timeline - const guidObject = crypto.randomBytes(16).toString('hex'); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, - "name": `Comment on ${event.name}`, - "type": "Note", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `

${req.body.replyAuthor} commented: ${req.body.replyContent}

See the full conversation here.

`, - } - ap.broadcastCreateMessage(jsonObject, event.followers, req.params.eventID) - if (sendEmails) { - Event.findOne({ id: req.params.eventID }).then((event) => { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/addeventcomment.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, commentAuthor: req.body.replyAuthor, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New comment in ${event.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { - console.log("Nothing to send!"); - } - }); - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID - }); - res.end(); - }) - .catch((err) => { res.send('Database error, please try again :('); addToLog("addEventReply", "error", "Attempt to add reply to comment " + commentID + " in event " + req.params.eventID + " failed with error: " + err); }); - }); -}); - -router.post('/deletecomment/:eventID/:commentID/:editToken', (req, res) => { - let submittedEditToken = req.params.editToken; - Event.findOne(({ - id: req.params.eventID, - })) - .then((event) => { - if (event.editToken === submittedEditToken) { - // Token matches - event.comments.id(req.params.commentID).remove(); - event.save() - .then(() => { - addToLog("deleteComment", "success", "Comment deleted from event " + req.params.eventID); - res.writeHead(302, { - 'Location': '/' + req.params.eventID + '?e=' + req.params.editToken - }); - res.end(); - }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: " + err); }); - } - else { - // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: token does not match"); - } - }) - .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: " + err); }); -}); - -router.post('/activitypub/inbox', (req, res) => { - if (!isFederated) return res.sendStatus(404); - // validate the incoming message - const signature = req.get('Signature'); - let signature_header = signature.split(',').map(pair => { - return pair.split('=').map(value => { - return value.replace(/^"/g, '').replace(/"$/g, '') - }); - }).reduce((acc, el) => { - acc[el[0]] = el[1]; - return acc; - }, {}); - - // get the actor - // TODO if this is a Delete for an Actor this won't work - request({ - url: signature_header.keyId, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, actor) { - let publicKey = ''; - - try { - if (JSON.parse(actor).publicKey) { - publicKey = JSON.parse(actor).publicKey.publicKeyPem; - } - } - catch (err) { - return res.status(500).send('Actor could not be parsed' + err); - } - - let comparison_string = signature_header.headers.split(' ').map(header => { - if (header === '(request-target)') { - return '(request-target): post /activitypub/inbox'; - } - else { - return `${header}: ${req.get(header)}` - } - }).join('\n'); - - const verifier = crypto.createVerify('RSA-SHA256') - verifier.update(comparison_string, 'ascii') - const publicKeyBuf = new Buffer(publicKey, 'ascii') - const signatureBuf = new Buffer(signature_header.signature, 'base64') - try { - const result = verifier.verify(publicKeyBuf, signatureBuf) - if (result) { - // actually process the ActivityPub message now that it's been verified - ap.processInbox(req, res); - } - else { - return res.status(401).send('Signature could not be verified.'); - } - } - catch (err) { - return res.status(401).send('Signature could not be verified: ' + err); - } - }); -}); - -router.use(function (req, res, next) { - res.status(404); - res.render('404', { url: req.url }); - return; -}); - -addToLog("startup", "success", "Started up successfully"); - -module.exports = router; diff --git a/src/activitypub.js b/src/activitypub.js new file mode 100644 index 0000000..442f03c --- /dev/null +++ b/src/activitypub.js @@ -0,0 +1,941 @@ +const domain = require('./config/domain.js').domain; +const contactEmail = require('./config/domain.js').email; +const siteName = require('./config/domain.js').sitename; +const isFederated = require('./config/domain.js').isFederated; +const request = require('request'); +const addToLog = require('./helpers.js').addToLog; +const crypto = require('crypto'); +// This alphabet (used to generate all event, group, etc. IDs) is missing '-' +// because ActivityPub doesn't like it in IDs +const { customAlphabet } = require('nanoid'); +const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_', 21); +var moment = require('moment-timezone'); +const mongoose = require('mongoose'); +const Event = mongoose.model('Event'); +const EventGroup = mongoose.model('EventGroup'); +var sanitizeHtml = require('sanitize-html'); + +function createActivityPubActor(eventID, domain, pubkey, description, name, location, imageFilename, startUTC, endUTC, timezone) { + let actor = { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + + 'id': `https://${domain}/${eventID}`, + 'type': 'Person', + 'preferredUsername': `${eventID}`, + 'inbox': `https://${domain}/activitypub/inbox`, + 'outbox': `https://${domain}/${eventID}/outbox`, + 'followers': `https://${domain}/${eventID}/followers`, + 'summary': `

${description}

`, + 'name': name, + 'featured': `https://${domain}/${eventID}/featured`, + + 'publicKey': { + 'id': `https://${domain}/${eventID}#main-key`, + 'owner': `https://${domain}/${eventID}`, + 'publicKeyPem': pubkey + } + }; + if (location) { + actor.summary += `

Location: ${location}.

` + } + let displayDate; + if (startUTC && timezone) { + displayDate = moment.tz(startUTC, timezone).format('D MMMM YYYY h:mm a'); + actor.summary += `

Starting ${displayDate} ${timezone}.

`; + } + if (imageFilename) { + actor.icon = { + 'type': 'Image', + 'mediaType': 'image/jpg', + 'url': `https://${domain}/events/${imageFilename}`, + }; + } + return JSON.stringify(actor); +} + +function createActivityPubEvent(name, startUTC, endUTC, timezone, description, location) { + const guid = crypto.randomBytes(16).toString('hex'); + let eventObject = { + "@context": "https://www.w3.org/ns/activitystreams", + 'id': `https://${domain}/${guid}`, + "name": name, + "type": "Event", + "startTime": moment.tz(startUTC, timezone).format(), + "endTime": moment.tz(endUTC, timezone).format(), + "content": description, + "location": location + } + return JSON.stringify(eventObject); +} + +function createFeaturedPost(eventID, name, startUTC, endUTC, timezone, description, location) { + const featured = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": `https://${domain}/${eventID}/m/featuredPost`, + "type": "Note", + "name": "Test", + 'cc': 'https://www.w3.org/ns/activitystreams#Public', + "content": `

This is an event that was posted on ${siteName}. If you follow this account, you'll see updates in your timeline about the event. If your software supports polls, you should get a poll in your DMs asking if you want to RSVP. You can reply and RSVP right from there. If your software has an event calendar built in, you should get an event in your inbox that you can RSVP to like you respond to any event.

For more information on how to interact with this, check out this link.

`, + 'attributedTo': `https://${domain}/${eventID}`, + } + return featured; +} + +function updateActivityPubEvent(oldEvent, name, startUTC, endUTC, timezone, description, location) { + // we want to persist the old ID no matter what happens to the Event itself + const id = oldEvent.id; + let eventObject = { + "@context": "https://www.w3.org/ns/activitystreams", + 'id': id, + "name": name, + "type": "Event", + "startTime": moment.tz(startUTC, timezone).format(), + "endTime": moment.tz(endUTC, timezone).format(), + "content": description, + "location": location + } + return JSON.stringify(eventObject); +} + + +function updateActivityPubActor(actor, description, name, location, imageFilename, startUTC, endUTC, timezone) { + if (!actor) return; + actor.summary = `

${description}

`; + actor.name = name; + if (location) { + actor.summary += `

Location: ${location}.

` + } + let displayDate; + if (startUTC && timezone) { + displayDate = moment.tz(startUTC, timezone).format('D MMMM YYYY h:mm a'); + actor.summary += `

Starting ${displayDate} ${timezone}.

`; + } + if (imageFilename) { + actor.icon = { + 'type': 'Image', + 'mediaType': 'image/jpg', + 'url': `https://${domain}/events/${imageFilename}`, + }; + } + return JSON.stringify(actor); +} + +function signAndSend(message, eventID, targetDomain, inbox, callback) { + if (!isFederated) return; + let inboxFragment = inbox.replace('https://' + targetDomain, ''); + // get the private key + Event.findOne({ + id: eventID + }) + .then((event) => { + if (event) { + const digest = crypto.createHash('sha256').update(JSON.stringify(message)).digest('base64'); + const privateKey = event.privateKey; + const signer = crypto.createSign('sha256'); + let d = new Date(); + let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}\ndigest: SHA-256=${digest}`; + signer.update(stringToSign); + signer.end(); + const signature = signer.sign(privateKey); + const signature_b64 = signature.toString('base64'); + const algorithm = 'rsa-sha256'; + let header = `keyId="https://${domain}/${eventID}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; + request({ + url: inbox, + headers: { + 'Host': targetDomain, + 'Date': d.toUTCString(), + 'Signature': header, + 'Digest': `SHA-256=${digest}`, + 'Content-Type': 'application/activity+json', + 'Accept': 'application/activity+json' + }, + method: 'POST', + json: true, + body: message + }, function (error, response) { + if (error) { + callback(error, null, 500); + } + else { + // Add the message to the database + const messageID = message.id; + const newMessage = { + id: message.id, + content: JSON.stringify(message) + }; + Event.findOne({ + id: eventID, + }, function (err, event) { + if (!event) return; + event.activityPubMessages.push(newMessage); + // also add the message's object if it has one + if (message.object && message.object.id) { + event.activityPubMessages.push({ + id: message.object.id, + content: JSON.stringify(message.object) + }); + } + event.save() + .then(() => { + addToLog("addActivityPubMessage", "success", "ActivityPubMessage added to event " + eventID); + callback(null, message.id, 200); + }) + .catch((err) => { + addToLog("addActivityPubMessage", "error", "Attempt to add ActivityPubMessage to event " + eventID + " failed with error: " + err); + callback(err, null, 500); + }); + }) + } + }); + } + else { + callback(`No record found for ${eventID}.`, null, 404); + } + }); +} + +// this function sends something to the timeline of every follower in the followers array +// it's also an unlisted public message, meaning non-followers can see the message if they look at +// the profile but it doesn't spam federated timelines +function broadcastCreateMessage(apObject, followers, eventID) { + if (!isFederated) return; + let guidCreate = crypto.randomBytes(16).toString('hex'); + Event.findOne({ + id: eventID, + }, function (err, event) { + if (event) { + // iterate over followers + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find(el => el.actorId === actorId); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], + 'id': `https://${domain}/${eventID}/m/${guidCreate}`, + 'type': 'Create', + 'actor': `https://${domain}/${eventID}`, + 'to': [actorId], + 'cc': 'https://www.w3.org/ns/activitystreams#Public', + 'object': apObject + }; + signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { + if (err) { + console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); + } + else { + console.log('sent to', actorId); + } + }); + } + else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } // end if event + else { + console.log(`No event found with the id ${eventID}`); + } + }); +} + + +// sends an Announce for the apObject +function broadcastAnnounceMessage(apObject, followers, eventID) { + if (!isFederated) return; + let guidUpdate = crypto.randomBytes(16).toString('hex'); + Event.findOne({ + id: eventID, + }, function (err, event) { + if (event) { + // iterate over followers + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find(el => el.actorId === actorId); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const announceMessage = { + '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], + 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, + 'cc': 'https://www.w3.org/ns/activitystreams#Public', + 'type': 'Announce', + 'actor': `https://${domain}/${eventID}`, + 'object': apObject, + 'to': actorId + }; + signAndSend(announceMessage, eventID, targetDomain, inbox, function (err, resp, status) { + if (err) { + console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); + } + else { + console.log('sent to', actorId); + } + }); + } + else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } // end if event + else { + console.log(`No event found with the id ${eventID}`); + } + }); +} + +// sends an Update for the apObject +function broadcastUpdateMessage(apObject, followers, eventID) { + if (!isFederated) return; + let guidUpdate = crypto.randomBytes(16).toString('hex'); + // iterate over followers + Event.findOne({ + id: eventID, + }, function (err, event) { + if (event) { + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find(el => el.actorId === actorId); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, + 'type': 'Update', + 'actor': `https://${domain}/${eventID}`, + 'object': apObject + }; + signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { + if (err) { + console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); + } + else { + console.log('sent to', actorId); + } + }); + } + else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } + else { + console.log(`No event found with the id ${eventID}`); + } + }); +} + +function broadcastDeleteMessage(apObject, followers, eventID, callback) { + callback = callback || function () { }; + if (!isFederated) { + callback([]); + return; + } + // we need to build an array of promises for each message we're sending, run Promise.all(), and then that will resolve when every message has been sent (or failed) + // per spec, each promise will execute *as it is built*, which is fine, we just need the guarantee that they are all done + let promises = []; + + let guidUpdate = crypto.randomBytes(16).toString('hex'); + // iterate over followers + for (const follower of followers) { + promises.push(new Promise((resolve, reject) => { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + Event.findOne({ + id: eventID, + }, function (err, event) { + if (event) { + const follower = event.followers.find(el => el.actorId === actorId); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, + 'type': 'Delete', + 'actor': `https://${domain}/${eventID}`, + 'object': apObject + }; + signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { + if (err) { + console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); + reject(`Didn't send to ${actorId}, status ${status} with error ${err}`); + } + else { + console.log('sent to', actorId); + resolve('sent to', actorId); + } + }); + } + else { + console.log(`No follower found with the id ${actorId}`, null, 404); + reject(`No follower found with the id ${actorId}`, null, 404); + } + } + else { + console.log(`No event found with the id ${eventID}`, null, 404); + reject(`No event found with the id ${eventID}`, null, 404); + } + }); // end event + })); + } // end followers + + Promise.all(promises.map(p => p.catch(e => e))).then(statuses => { + callback(statuses); + }); +} + +// this sends a message "to:" an individual fediverse user +function sendDirectMessage(apObject, actorId, eventID, callback) { + if (!isFederated) return; + callback = callback || function () { }; + const guidCreate = crypto.randomBytes(16).toString('hex'); + const guidObject = crypto.randomBytes(16).toString('hex'); + let d = new Date(); + + apObject.published = d.toISOString(); + apObject.attributedTo = `https://${domain}/${eventID}`; + apObject.to = actorId; + apObject.id = `https://${domain}/${eventID}/m/${guidObject}`; + apObject.content = unescape(apObject.content) + + let createMessage = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `https://${domain}/${eventID}/m/${guidCreate}`, + 'type': 'Create', + 'actor': `https://${domain}/${eventID}`, + 'to': [actorId], + 'object': apObject + }; + + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + Event.findOne({ + id: eventID, + }, function (err, event) { + if (event) { + const follower = event.followers.find(el => el.actorId === actorId); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + signAndSend(createMessage, eventID, targetDomain, inbox, callback); + } + else { + callback(`No follower found with the id ${actorId}`, null, 404); + } + } + else { + callback(`No event found with the id ${eventID}`, null, 404); + } + }); +} + +function sendAcceptMessage(thebody, eventID, targetDomain, callback) { + if (!isFederated) return; + callback = callback || function () { }; + const guid = crypto.randomBytes(16).toString('hex'); + const actorId = thebody.actor; + let message = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `https://${domain}/${guid}`, + 'type': 'Accept', + 'actor': `https://${domain}/${eventID}`, + 'object': thebody, + }; + // get the inbox + Event.findOne({ + id: eventID, + }, function (err, event) { + if (event) { + const follower = event.followers.find(el => el.actorId === actorId); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + signAndSend(message, eventID, targetDomain, inbox, callback); + } + } + else { + callback(`Could not find event ${eventID}`, null, 404); + } + }); +} + +function _handleFollow(req, res) { + const myURL = new URL(req.body.actor); + let targetDomain = myURL.hostname; + let eventID = req.body.object.replace(`https://${domain}/`, ''); + // Add the user to the DB of accounts that follow the account + // get the follower's username + request({ + url: req.body.actor, + headers: { + 'Accept': 'application/activity+json', + 'Content-Type': 'application/activity+json' + } + }, function (error, response, body) { + body = JSON.parse(body) + const name = body.preferredUsername || body.name || body.attributedTo; + const newFollower = { + actorId: req.body.actor, + followId: req.body.id, + name: name, + actorJson: JSON.stringify(body) + }; + Event.findOne({ + id: eventID, + }, function (err, event) { + // if this account is NOT already in our followers list, add it + if (event && !event.followers.map(el => el.actorId).includes(req.body.actor)) { + event.followers.push(newFollower); + event.save() + .then(() => { + addToLog("addEventFollower", "success", "Follower added to event " + eventID); + // Accept the follow request + sendAcceptMessage(req.body, eventID, targetDomain, function (err, resp, status) { + if (err) { + console.log(`Didn't send Accept to ${req.body.actor}, status ${status} with error ${err}`); + } + else { + console.log('sent Accept to', req.body.actor); + // ALSO send an ActivityPub Event activity since this person is "interested" in the event, as indicated by the Follow + const jsonEventObject = JSON.parse(event.activityPubEvent); + // send direct message to user + sendDirectMessage(jsonEventObject, newFollower.actorId, event.id); + + // if users can self-RSVP, send a Question to the new follower + if (event.usersCanAttend) { + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + "name": `RSVP to ${event.name}`, + "type": "Question", + "content": `@${name} Will you attend ${event.name}? (If you reply "Yes", you'll be listed as an attendee on the event page.)`, + "oneOf": [ + { "type": "Note", "name": "Yes" }, + ], + "endTime": event.start.toISOString(), + "tag": [{ "type": "Mention", "href": req.body.actor, "name": name }] + } + // send direct message to user + sendDirectMessage(jsonObject, req.body.actor, eventID, function (error, response, statuscode) { + if (error) { + console.log('Error sending direct message:', error); + return res.status(statuscode).json(error); + } + else { + return res.status(statuscode).json({ messageid: response }); + } + }); + } + } + }); + }) + .catch((err) => { + addToLog("addEventFollower", "error", "Attempt to add follower to event " + eventID + " failed with error: " + err); + return res.status(500).send('Database error, please try again :('); + }); + } + else { + // this person is already a follower so just say "ok" + return res.status(200); + } + }) + }) //end request +} + +function _handleUndoFollow(req, res) { + // get the record of all followers for this account + const eventID = req.body.object.object.replace(`https://${domain}/`, ''); + Event.findOne({ + id: eventID, + }, function (err, event) { + if (!event) return; + // check to see if the Follow object's id matches the id we have on record + // is this even someone who follows us + const indexOfFollower = event.followers.findIndex(el => el.actorId === req.body.object.actor); + if (indexOfFollower !== -1) { + // does the id we have match the id we are being given + if (event.followers[indexOfFollower].followId === req.body.object.id) { + // we have a match and can trust the Undo! remove this person from the followers list + event.followers.splice(indexOfFollower, 1); + event.save() + .then(() => { + addToLog("removeEventFollower", "success", "Follower removed from event " + eventID); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog("removeEventFollower", "error", "Attempt to remove follower from event " + eventID + " failed with error: " + err); + return res.send('Database error, please try again :('); + }); + } + } + }); +} + +function _handleAcceptEvent(req, res) { + let { name, attributedTo, inReplyTo, to, actor } = req.body; + if (Array.isArray(to)) { + to = to[0]; + } + const eventID = to.replace(`https://${domain}/`, ''); + Event.findOne({ + id: eventID, + }, function (err, event) { + if (!event) return; + // does the id we got match the id of a thing we sent out + const message = event.activityPubMessages.find(el => el.id === req.body.object); + if (message) { + // it's a match + request({ + url: actor, + headers: { + 'Accept': 'application/activity+json', + 'Content-Type': 'application/activity+json' + } + }, function (error, response, body) { + body = JSON.parse(body) + // if this account is NOT already in our attendees list, add it + if (!event.attendees.map(el => el.id).includes(actor)) { + const attendeeName = body.preferredUsername || body.name || actor; + const newAttendee = { + name: attendeeName, + status: 'attending', + id: actor, + number: 1, + }; + event.attendees.push(newAttendee); + event.save() + .then((fullEvent) => { + addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); + // get the new attendee with its hidden id from the full event + let fullAttendee = fullEvent.attendees.find(el => el.id === actor); + // send a "click here to remove yourself" link back to the user as a DM + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + "name": `RSVP to ${event.name}`, + "type": "Note", + "content": `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, + "tag": [{ "type": "Mention", "href": newAttendee.id, "name": newAttendee.name }] + } + // send direct message to user + sendDirectMessage(jsonObject, newAttendee.id, event.id); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + err); + return res.status(500).send('Database error, please try again :('); + }); + } + else { + // it's a duplicate and this person is already rsvped so just say OK + return res.status(200).send("Attendee is already registered."); + } + }); + } + }); +} + +function _handleUndoAcceptEvent(req, res) { + let { name, attributedTo, inReplyTo, to, actor } = req.body; + if (Array.isArray(to)) { + to = to[0]; + } + const eventID = to.replace(`https://${domain}/`, ''); + Event.findOne({ + id: eventID, + }, function (err, event) { + if (!event) return; + // does the id we got match the id of a thing we sent out + const message = event.activityPubMessages.find(el => el.id === req.body.object.object); + if (message) { + // it's a match + Event.update( + { id: eventID }, + { $pull: { attendees: { id: actor } } } + ) + .then(response => { + addToLog("oneClickUnattend", "success", "Attendee removed via one click unattend " + req.params.eventID); + }); + } + }); +} + +function _handleCreateNote(req, res) { + // figure out what this is in reply to -- it should be addressed specifically to us + let { name, attributedTo, inReplyTo, to } = req.body.object; + // if it's an array just grab the first element, since a poll should only broadcast back to the pollster + if (Array.isArray(to)) { + to = to[0]; + } + const eventID = to.replace(`https://${domain}/`, ''); + // make sure this person is actually a follower + Event.findOne({ + id: eventID, + }, function (err, event) { + if (!event) return; + // is this even someone who follows us + const indexOfFollower = event.followers.findIndex(el => el.actorId === req.body.object.attributedTo); + if (indexOfFollower !== -1) { + // compare the inReplyTo to its stored message, if it exists and it's going to the right follower then this is a valid reply + const message = event.activityPubMessages.find(el => { + const content = JSON.parse(el.content); + return inReplyTo === (content.object && content.object.id); + }); + if (message) { + const content = JSON.parse(message.content); + // check if the message we sent out was sent to the actor this incoming message is attributedTo + if (content.to[0] === attributedTo) { + // it's a match, this is a valid poll response, add RSVP to database + // fetch the profile information of the user + request({ + url: attributedTo, + headers: { + 'Accept': 'application/activity+json', + 'Content-Type': 'application/activity+json' + } + }, function (error, response, body) { + body = JSON.parse(body) + // if this account is NOT already in our attendees list, add it + if (!event.attendees.map(el => el.id).includes(attributedTo)) { + const attendeeName = body.preferredUsername || body.name || attributedTo; + const newAttendee = { + name: attendeeName, + status: 'attending', + id: attributedTo, + number: 1, + }; + event.attendees.push(newAttendee); + event.save() + .then((fullEvent) => { + addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); + // get the new attendee with its hidden id from the full event + let fullAttendee = fullEvent.attendees.find(el => el.id === attributedTo); + // send a "click here to remove yourself" link back to the user as a DM + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + "name": `RSVP to ${event.name}`, + "type": "Note", + "content": `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, + "tag": [{ "type": "Mention", "href": newAttendee.id, "name": newAttendee.name }] + } + // send direct message to user + sendDirectMessage(jsonObject, newAttendee.id, event.id); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + err); + return res.status(500).send('Database error, please try again :('); + }); + } + else { + // it's a duplicate and this person is already rsvped so just say OK + return res.status(200).send("Attendee is already registered."); + } + }); + } + } + } + }); +} + +function _handleDelete(req, res) { + const deleteObjectId = req.body.object.id; + // find all events with comments from the author + Event.find({ + "comments.actorId": req.body.actor + }, function (err, events) { + if (!events) { + return res.sendStatus(404); + } + + // find the event with THIS comment from the author + let eventWithComment = events.find(event => { + let comments = event.comments; + return comments.find(comment => { + if (!comment.activityJson) { + return false; + } + return JSON.parse(comment.activityJson).object.id === req.body.object.id; + }) + }); + + if (!eventWithComment) { + return res.sendStatus(404); + } + + // delete the comment + // find the index of the comment, it should have an activityJson field because from an AP server you can only delete an AP-originated comment (and of course it needs to be yours) + let indexOfComment = eventWithComment.comments.findIndex(comment => { + return comment.activityJson && JSON.parse(comment.activityJson).object.id === req.body.object.id; + }); + eventWithComment.comments.splice(indexOfComment, 1); + eventWithComment.save() + .then(() => { + addToLog("deleteComment", "success", "Comment deleted from event " + eventWithComment.id); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog("deleteComment", "error", "Attempt to delete comment " + req.body.object.id + "from event " + eventWithComment.id + " failed with error: " + err); + return res.sendStatus(500); + }); + }); +} + +function _handleCreateNoteComment(req, res) { + // figure out what this is in reply to -- it should be addressed specifically to us + let { attributedTo, inReplyTo, to, cc } = req.body.object; + // normalize cc into an array + if (typeof cc === 'string') { + cc = [cc]; + } + // normalize to into an array + if (typeof to === 'string') { + to = [to]; + } + + // if this is a public message (in the to or cc fields) + if (to.includes('https://www.w3.org/ns/activitystreams#Public') || (Array.isArray(cc) && cc.includes('https://www.w3.org/ns/activitystreams#Public'))) { + // figure out which event(s) of ours it was addressing + let ourEvents = cc.filter(el => el.includes(`https://${domain}/`)) + .map(el => el.replace(`https://${domain}/`, '')); + // comments should only be on one event. if more than one, ignore (spam, probably) + if (ourEvents.length === 1) { + let eventID = ourEvents[0]; + // add comment + let commentID = nanoid(); + // get the actor for the commenter + request({ + url: req.body.actor, + headers: { + 'Accept': 'application/activity+json', + 'Content-Type': 'application/activity+json' + } + }, function (error, response, actor) { + if (!error) { + const parsedActor = JSON.parse(actor); + const name = parsedActor.preferredUsername || parsedActor.name || req.body.actor; + const newComment = { + id: commentID, + actorId: req.body.actor, + activityId: req.body.object.id, + author: name, + content: sanitizeHtml(req.body.object.content, { allowedTags: [], allowedAttributes: {} }).replace('@' + eventID, ''), + timestamp: moment(), + activityJson: JSON.stringify(req.body), + actorJson: actor + }; + + Event.findOne({ + id: eventID, + }, function (err, event) { + if (!event) { + return res.sendStatus(404); + } + if (!event.usersCanComment) { + return res.sendStatus(200); + } + event.comments.push(newComment); + event.save() + .then(() => { + addToLog("addEventComment", "success", "Comment added to event " + eventID); + const guidObject = crypto.randomBytes(16).toString('hex'); + const jsonObject = req.body.object; + jsonObject.attributedTo = newComment.actorId; + broadcastAnnounceMessage(jsonObject, event.followers, eventID) + return res.sendStatus(200); + }) + .catch((err) => { + addToLog("addEventComment", "error", "Attempt to add comment to event " + eventID + " failed with error: " + err); + res.status(500).send('Database error, please try again :(' + err); + }); + }); + } + }); + } // end ourevent + } // end public message +} + +function processInbox(req, res) { + if (!isFederated) return res.sendStatus(404); + try { + // if a Follow activity hits the inbox + if (typeof req.body.object === 'string' && req.body.type === 'Follow') { + _handleFollow(req, res); + } + // if an Undo activity with a Follow object hits the inbox + if (req.body && req.body.type === 'Undo' && req.body.object && req.body.object.type === 'Follow') { + _handleUndoFollow(req, res); + } + // if an Accept activity with the id of the Event we sent out hits the inbox, it is an affirmative RSVP + if (req.body && req.body.type === 'Accept' && req.body.object && typeof req.body.object === 'string') { + _handleAcceptEvent(req, res); + } + // if an Undo activity containing an Accept containing the id of the Event we sent out hits the inbox, it is an undo RSVP + if (req.body && req.body.type === 'Undo' && req.body.object && req.body.object.object && typeof req.body.object.object === 'string' && req.body.object.type === 'Accept') { + _handleUndoAcceptEvent(req, res); + } + // if a Create activity with a Note object hits the inbox, and it's a reply, it might be a vote in a poll + if (req.body && req.body.type === 'Create' && req.body.object && req.body.object.type === 'Note' && req.body.object.inReplyTo && req.body.object.to) { + _handleCreateNote(req, res); + } + // if a Delete activity hits the inbox, it might a deletion of a comment + if (req.body && req.body.type === 'Delete') { + _handleDelete(req, res); + } + // if we are CC'ed on a public or unlisted Create/Note, then this is a comment to us we should boost (Announce) to our followers + if (req.body && req.body.type === 'Create' && req.body.object && req.body.object.type === 'Note' && req.body.object.to) { + _handleCreateNoteComment(req, res); + } // CC'ed + } + catch (e) { + console.log('Error in processing inbox:', e) + } +} + +function createWebfinger(eventID, domain) { + return { + 'subject': `acct:${eventID}@${domain}`, + + 'links': [ + { + 'rel': 'self', + 'type': 'application/activity+json', + 'href': `https://${domain}/${eventID}` + } + ] + }; +} + +module.exports = { + processInbox, + sendAcceptMessage, + sendDirectMessage, + broadcastAnnounceMessage, + broadcastUpdateMessage, + broadcastDeleteMessage, + broadcastCreateMessage, + signAndSend, + createActivityPubActor, + updateActivityPubActor, + createActivityPubEvent, + updateActivityPubEvent, + createFeaturedPost, + createWebfinger, +} diff --git a/src/app.js b/src/app.js new file mode 100755 index 0000000..cc50593 --- /dev/null +++ b/src/app.js @@ -0,0 +1,54 @@ +const express = require('express'); +const path = require('path'); +const session = require('express-session'); +const cors = require('cors'); +const routes = require('./routes'); +const hbs = require('express-handlebars'); +const bodyParser = require('body-parser'); + +const app = express(); + +// Configuration // + +//app.use(cors()); +//app.use(bodyParser.json()); +//app.use(session({ secret: 'slartibartfast', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); + + +// View engine // +const hbsInstance = hbs.create({ + defaultLayout: 'main', + partialsDir: ['views/partials/'], + layoutsDir: 'views/layouts/', + helpers: { + plural: function(number, text) { + var singular = number === 1; + // If no text parameter was given, just return a conditional s. + if (typeof text !== 'string') return singular ? '' : 's'; + // Split with regex into group1/group2 or group1(group3) + var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); + // If no match, just append a conditional s. + if (!match) return text + (singular ? '' : 's'); + // We have a good match, so fire away + return singular && match[1] // Singular case + || + match[2] // Plural case: 'bagel/bagels' --> bagels + || + match[1] + (match[3] || 's'); // Plural case: 'bagel(s)' or 'bagel' --> bagels + } + } +}); +app.engine('handlebars', hbsInstance.engine); +app.set('view engine', 'handlebars'); +app.set('hbsInstance', hbsInstance); + +// Static files // + +app.use(express.static('public')); + +// Router // +app.use(bodyParser.json({ type: "application/activity+json" })); // support json encoded bodies +app.use(bodyParser.urlencoded({ extended: true })); +app.use('/', routes); + +module.exports = app; diff --git a/src/config/api-example.js b/src/config/api-example.js new file mode 100644 index 0000000..9202f0a --- /dev/null +++ b/src/config/api-example.js @@ -0,0 +1,8 @@ +// Which of these fields are used depends on the 'mailService' config entry in config/domain.js +module.exports = { + 'sendgrid' : '', // If using SendGrid, the Sendgrid API key goes here + 'smtpServer': '', // If using Nodemailer, your SMTP server hostname goes here + 'smtpPort': '', // If using Nodemailer, your SMTP server port goes here + 'smtpUsername': '', // If using Nodemailer, your SMTP server username goes here + 'smtpPassword': '' // If using Nodemailer, your SMTP password goes here +}; diff --git a/src/config/database-docker.js b/src/config/database-docker.js new file mode 100644 index 0000000..7847097 --- /dev/null +++ b/src/config/database-docker.js @@ -0,0 +1,3 @@ +module.exports = { + 'url' : 'mongodb://mongo:27017/gathio' // For dockerised MongoDB connection +}; diff --git a/src/config/database-example.js b/src/config/database-example.js new file mode 100644 index 0000000..4aa4c4d --- /dev/null +++ b/src/config/database-example.js @@ -0,0 +1,3 @@ +module.exports = { + 'url' : 'mongodb://localhost:27017/gathio' // For local MongoDB connection +}; diff --git a/src/config/domain-example.js b/src/config/domain-example.js new file mode 100644 index 0000000..19c797a --- /dev/null +++ b/src/config/domain-example.js @@ -0,0 +1,13 @@ +module.exports = { + // Your domain goes here. If there is a port it should be 'domain:port', but otherwise just 'domain' + 'domain' : 'localhost:3000' , + 'port': '3000', + 'email': 'contact@example.com', + 'mailService': 'nodemailer', // Which mail service to use to send emails to attendees. Options are 'nodemailer' or 'sendgrid'. Configure settings for the mail service in config/api.js.z + 'sitename': 'gathio', + 'isFederated': true, + // If left blank, this defaults to https://yourdomain.com/images/gathio-email-logo.gif. Set a full URL here to change it to your own logo (or just change the file itself) + 'logo_url': '', + // Show a Ko-Fi box to donate money to Raphael Kabo (Gathio's creator) on the front page + 'showKofi': false, +}; diff --git a/src/config/gathio.service b/src/config/gathio.service new file mode 100644 index 0000000..447d44f --- /dev/null +++ b/src/config/gathio.service @@ -0,0 +1,13 @@ +[Unit] +Description=GathIO +After=network.target + +[Service] +Type=simple +User=gathio +WorkingDirectory=/srv/gathio +ExecStart=/usr/bin/npm start +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..bf95e27 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,57 @@ +const domain = require('./config/domain.js').domain; +const siteName = require('./config/domain.js').sitename; + +const mongoose = require('mongoose'); +const Log = mongoose.model('Log'); +var moment = require('moment-timezone'); +const icalGenerator = require('ical-generator'); + +// LOGGING + +function addToLog(process, status, message) { + let logEntry = new Log({ + status: status, + process: process, + message: message, + timestamp: moment() + }); + logEntry.save().catch(() => { console.log("Error saving log entry!") }); +} + +function exportIcal(events, calendarName) { + // Create a new icalGenerator... generator + const cal = icalGenerator({ + name: calendarName || siteName, + x: { + 'X-WR-CALNAME': calendarName || siteName, + }, + }); + if (events instanceof Array === false) { + events = [ events ]; + } + events.forEach(event => { + // Add the event to the generator + cal.createEvent({ + start: moment.tz(event.start, event.timezone), + end: moment.tz(event.end, event.timezone), + timezone: event.timezone, + timestamp: moment(), + summary: event.name, + description: event.description, + organizer: { + name: event.hostName || "Anonymous", + email: event.creatorEmail || 'anonymous@anonymous.com', + }, + location: event.location, + url: 'https://' + domain + '/' + event.id + }); + }); + // Stringify it! + const string = cal.toString(); + return string; +} + +module.exports = { + addToLog, + exportIcal, +} diff --git a/src/models/Event.js b/src/models/Event.js new file mode 100755 index 0000000..d800077 --- /dev/null +++ b/src/models/Event.js @@ -0,0 +1,254 @@ +const mongoose = require('mongoose'); + +const Attendees = new mongoose.Schema({ + name: { + type: String, + trim: true + }, + status: { + type: String, + trim: true + }, + email: { + type: String, + trim: true + }, + removalPassword: { + type: String, + trim: true, + unique: true, + sparse: true, + }, + id: { + type: String, + trim: true, + unique: true, + sparse: true, + }, + // The number of people that are attending under one 'attendee' object + number: { + type: Number, + trim: true, + default: 1 + }, + created: Date, +}) + +const Followers = new mongoose.Schema({ + // this is the id of the original follow *request*, which we use to validate Undo events + followId: { + type: String, + trim: true + }, + // this is the actual remote user profile id + actorId: { + type: String, + trim: true + }, + // this is the stringified JSON of the entire user profile + actorJson: { + type: String, + trim: true + }, + name: { + type: String, + trim: true + }, +}, { _id: false }) + +const ReplySchema = new mongoose.Schema({ + id: { + type: String, + required: true, + unique: true, + sparse: true + }, + author: { + type: String, + trim: true, + required: true + }, + content: { + type: String, + trim: true, + required: true + }, + timestamp: { + type: Date, + trim: true, + required: true + } +}) + +const ActivityPubMessages = new mongoose.Schema({ + id: { + type: String, + required: true, + unique: true, + sparse: true + }, + content: { + type: String, + trim: true, + required: true + } +}) + +const CommentSchema = new mongoose.Schema({ + id: { + type: String, + required: true, + unique: true, + sparse: true + }, + author: { + type: String, + trim: true, + required: true + }, + content: { + type: String, + trim: true, + required: true + }, + timestamp: { + type: Date, + trim: true, + required: true + }, + activityJson: { + type: String, + trim: true + }, + actorJson: { + type: String, + trim: true + }, + activityId: { + type: String, + trim: true + }, + actorId: { + type: String, + trim: true + }, + replies: [ReplySchema] +}) + +const EventSchema = new mongoose.Schema({ + id: { + type: String, + required: true, + unique: true + }, + type: { + type: String, + trim: true, + required: true + }, + name: { + type: String, + trim: true, + required: true + }, + location: { + type: String, + trim: true, + required: true + }, + start: { // Stored as a UTC timestamp + type: Date, + trim: true, + required: true + }, + end: { // Stored as a UTC timestamp + type: Date, + trim: true, + required: true + }, + timezone: { + type: String, + default: 'Etc/UTC' + }, + description: { + type: String, + trim: true, + required: true + }, + image: { + type: String, + trim: true + }, + url: { + type: String, + trim: true + }, + creatorEmail: { + type: String, + trim: true + }, + hostName: { + type: String, + trim: true + }, + viewPassword: { + type: String, + trim: true + }, + editPassword: { + type: String, + trim: true + }, + editToken: { + type: String, + trim: true, + minlength: 32, + maxlength: 32 + }, + eventGroup: { type: mongoose.Schema.Types.ObjectId, ref: 'EventGroup' }, + usersCanAttend: { + type: Boolean, + trim: true, + default: false + }, + showUsersList: { + type: Boolean, + trim: true, + default: false + }, + usersCanComment: { + type: Boolean, + trim: true, + default: false + }, + firstLoad: { + type: Boolean, + trim: true, + default: true + }, + attendees: [Attendees], + maxAttendees: { + type: Number + }, + comments: [CommentSchema], + activityPubActor: { + type: String, + trim: true + }, + activityPubEvent: { + type: String, + trim: true + }, + publicKey: { + type: String, + trim: true + }, + privateKey: { + type: String, + trim: true + }, + followers: [Followers], + activityPubMessages: [ActivityPubMessages] +}); + +module.exports = mongoose.model('Event', EventSchema); diff --git a/src/models/EventGroup.js b/src/models/EventGroup.js new file mode 100755 index 0000000..c70ef95 --- /dev/null +++ b/src/models/EventGroup.js @@ -0,0 +1,57 @@ +const mongoose = require('mongoose'); + +const Subscriber = new mongoose.Schema({ + email: { + type: String, + trim: true + }, +}) + +const EventGroupSchema = new mongoose.Schema({ + id: { + type: String, + required: true, + unique: true + }, + name: { + type: String, + trim: true, + required: true + }, + description: { + type: String, + trim: true, + required: true + }, + image: { + type: String, + trim: true + }, + url: { + type: String, + trim: true + }, + creatorEmail: { + type: String, + trim: true + }, + hostName: { + type: String, + trim: true + }, + editToken: { + type: String, + trim: true, + minlength: 32, + maxlength: 32 + }, + firstLoad: { + type: Boolean, + trim: true, + default: true + }, + events: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Event' }], + subscribers: [Subscriber], +}); + +module.exports = mongoose.model('EventGroup', EventGroupSchema); diff --git a/src/models/Log.js b/src/models/Log.js new file mode 100755 index 0000000..95a3ab3 --- /dev/null +++ b/src/models/Log.js @@ -0,0 +1,26 @@ +const mongoose = require('mongoose'); + +const LogSchema = new mongoose.Schema({ + status: { + type: String, + trim: true, + required: true + }, + process: { + type: String, + trim: true, + required: true + }, + message: { + type: String, + trim: true, + required: true + }, + timestamp: { + type: Date, + trim: true, + required: true + } +}); + +module.exports = mongoose.model('Log', LogSchema); diff --git a/src/routes.js b/src/routes.js new file mode 100755 index 0000000..c9867e3 --- /dev/null +++ b/src/routes.js @@ -0,0 +1,1976 @@ +const fs = require('fs'); + +const express = require('express'); + +const mongoose = require('mongoose'); + +// This alphabet (used to generate all event, group, etc. IDs) is missing '-' +// because ActivityPub doesn't like it in IDs +const { customAlphabet } = require('nanoid'); +const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_', 21); + +const randomstring = require("randomstring"); + +const { body, validationResult } = require('express-validator'); + +const router = express.Router(); + +const Event = mongoose.model('Event'); +const EventGroup = mongoose.model('EventGroup'); +const addToLog = require('./helpers.js').addToLog; + +var moment = require('moment-timezone'); + +const marked = require('marked'); + +const generateRSAKeypair = require('generate-rsa-keypair'); +const crypto = require('crypto'); +const request = require('request'); +const niceware = require('niceware'); + +const domain = require('./config/domain.js').domain; +const contactEmail = require('./config/domain.js').email; +const mailService = require('./config/domain.js').mailService; +const siteName = require('./config/domain.js').sitename; +const siteLogo = require('./config/domain.js').logo_url; +let isFederated = require('./config/domain.js').isFederated; +let showKofi = require('./config/domain.js').showKofi; +// if the federation config isn't set, things are federated by default +if (isFederated === undefined) { + isFederated = true; +} +const ap = require('./activitypub.js'); + +// Extra marked renderer (used to render plaintext event description for page metadata) +// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/ +// ? to ? helper +function htmlEscapeToText(text) { + return text.replace(/\&\#[0-9]*;|&/g, function (escapeCode) { + if (escapeCode.match(/amp/)) { + return '&'; + } + return String.fromCharCode(escapeCode.match(/[0-9]+/)); + }); +} + +function render_plain() { + var render = new marked.Renderer(); + // render just the text of a link, strong, em + render.link = function (href, title, text) { + return text; + }; + render.strong = function (text) { + return text; + } + render.em = function (text) { + return text; + } + // render just the text of a paragraph + render.paragraph = function (text) { + return htmlEscapeToText(text) + '\r\n'; + }; + // render nothing for headings, images, and br + render.heading = function (text, level) { + return ''; + }; + render.image = function (href, title, text) { + return ''; + }; + render.br = function () { + return ''; + }; + return render; +} + +const ical = require('ical'); +const { exportIcal } = require('./helpers.js'); + +const sgMail = require('@sendgrid/mail'); +const nodemailer = require("nodemailer"); + +const apiCredentials = require('./config/api.js'); + +let sendEmails = false; +let nodemailerTransporter; +if (mailService) { + switch (mailService) { + case 'sendgrid': + sgMail.setApiKey(apiCredentials.sendgrid); + console.log("Sendgrid is ready to send emails."); + sendEmails = true; + break; + case 'nodemailer': + nodemailerTransporter = nodemailer.createTransport({ + host: apiCredentials.smtpServer, + port: apiCredentials.smtpPort, + secure: false, // true for 465, false for other ports + auth: { + user: apiCredentials.smtpUsername, // generated ethereal user + pass: apiCredentials.smtpPassword, // generated ethereal password + }, + }); + nodemailerTransporter.verify((error, success) => { + if (error) { + console.log(error); + } else { + console.log("Nodemailer SMTP server is ready to send emails."); + sendEmails = true; + } + }); + break; + default: + console.error('You have not configured this Gathio instance to send emails! This means that event creators will not receive emails when their events are created, which means they may end up locked out of editing events. Consider setting up an email service.') + } +} + +const fileUpload = require('express-fileupload'); +var Jimp = require('jimp'); +router.use(fileUpload()); + +// SCHEDULED DELETION +const schedule = require('node-schedule'); +schedule.scheduleJob('59 23 * * *', function (fireDate) { + const too_old = moment.tz('Etc/UTC').subtract(7, 'days').toDate(); + console.log("Old event deletion running! Deleting all events concluding before ", too_old); + + Event.find({ end: { $lte: too_old } }).then((oldEvents) => { + oldEvents.forEach(event => { + const deleteEventFromDB = (id) => { + Event.remove({ "_id": id }) + .then(response => { + addToLog("deleteOldEvents", "success", "Old event " + id + " deleted"); + }).catch((err) => { + addToLog("deleteOldEvents", "error", "Attempt to delete old event " + id + " failed with error: " + err); + }); + } + + if (event.image) { + fs.unlink(global.appRoot + '/public/events/' + event.image, (err) => { + if (err) { + addToLog("deleteOldEvents", "error", "Attempt to delete event image for old event " + event.id + " failed with error: " + err); + } + // Image removed + addToLog("deleteOldEvents", "error", "Image deleted for old event " + event.id); + }) + } + // Check if event has ActivityPub fields + if (event.activityPubActor && event.activityPubEvent) { + // Broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information + const guidUpdateObject = crypto.randomBytes(16).toString('hex'); + const jsonUpdateObject = JSON.parse(event.activityPubActor); + const jsonEventObject = JSON.parse(event.activityPubEvent); + // first broadcast AP messages, THEN delete from DB + ap.broadcastDeleteMessage(jsonUpdateObject, event.followers, event.id, function (statuses) { + ap.broadcastDeleteMessage(jsonEventObject, event.followers, event.id, function (statuses) { + deleteEventFromDB(event._id); + }); + }); + } else { + // No ActivityPub data - simply delete the event + deleteEventFromDB(event._id); + } + }) + }).catch((err) => { + addToLog("deleteOldEvents", "error", "Attempt to delete old event " + event.id + " failed with error: " + err); + }); + + // TODO: While we're here, also remove all provisioned event attendees over a day + // old (they're not going to become active) +}); + +// FRONTEND ROUTES + +router.get('/', (req, res) => { + res.render('home', { + domain, + email: contactEmail, + siteName, + showKofi, + }); +}); + +router.get('/new', (req, res) => { + res.render('home'); +}); + +router.get('/new/event', (req, res) => { + res.render('newevent', { + domain: domain, + email: contactEmail, + siteName: siteName, + }); +}); +router.get('/new/event/public', (req, res) => { + let isPrivate = false; + let isPublic = true; + let isOrganisation = false; + let isUnknownType = false; + res.render('newevent', { + title: 'New event', + isPrivate: isPrivate, + isPublic: isPublic, + isOrganisation: isOrganisation, + isUnknownType: isUnknownType, + eventType: 'public', + domain: domain, + email: contactEmail, + siteName: siteName, + }); +}) + +// return the JSON for the featured/pinned post for this event +router.get('/:eventID/featured', (req, res) => { + if (!isFederated) return res.sendStatus(404); + const { eventID } = req.params; + const guidObject = crypto.randomBytes(16).toString('hex'); + const featured = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": `https://${domain}/${eventID}/featured`, + "type": "OrderedCollection", + "orderedItems": [ + ap.createFeaturedPost(eventID) + ] + } + res.json(featured); +}); + +// return the JSON for a given activitypub message +router.get('/:eventID/m/:hash', (req, res) => { + if (!isFederated) return res.sendStatus(404); + const { hash, eventID } = req.params; + const id = `https://${domain}/${eventID}/m/${hash}`; + + Event.findOne({ + id: eventID + }) + .then((event) => { + if (!event) { + res.status(404); + res.render('404', { url: req.url }); + } + else { + const message = event.activityPubMessages.find(el => el.id === id); + if (message) { + return res.json(JSON.parse(message.content)); + } + else { + res.status(404); + return res.render('404', { url: req.url }); + } + } + }) + .catch((err) => { + addToLog("getActivityPubMessage", "error", "Attempt to get Activity Pub Message for " + id + " failed with error: " + err); + res.status(404); + res.render('404', { url: req.url }); + return; + }); +}); + +// return the webfinger record required for the initial activitypub handshake +router.get('/.well-known/webfinger', (req, res) => { + if (!isFederated) return res.sendStatus(404); + let resource = req.query.resource; + if (!resource || !resource.includes('acct:')) { + return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.'); + } + else { + // "foo@domain" + let activityPubAccount = resource.replace('acct:', ''); + // "foo" + let eventID = activityPubAccount.replace(/@.*/, ''); + Event.findOne({ + id: eventID + }) + .then((event) => { + if (!event) { + res.status(404); + res.render('404', { url: req.url }); + } + else { + res.json(ap.createWebfinger(eventID, domain)); + } + }) + .catch((err) => { + addToLog("renderWebfinger", "error", "Attempt to render webfinger for " + req.params.eventID + " failed with error: " + err); + res.status(404); + res.render('404', { url: req.url }); + return; + }); + } +}); + +router.get('/:eventID', (req, res) => { + Event.findOne({ + id: req.params.eventID + }) + .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is + .populate('eventGroup') + .then((event) => { + if (event) { + const parsedLocation = event.location.replace(/\s+/g, '+'); + let displayDate; + if (moment.tz(event.end, event.timezone).isSame(event.start, 'day')) { + // Happening during one day + displayDate = moment.tz(event.start, event.timezone).format('dddd D MMMM YYYY [from] h:mm a') + moment.tz(event.end, event.timezone).format(' [to] h:mm a [](z)[]'); + } + else { + displayDate = moment.tz(event.start, event.timezone).format('dddd D MMMM YYYY [at] h:mm a') + moment.tz(event.end, event.timezone).format(' [–] dddd D MMMM YYYY [at] h:mm a [](z)[]'); + } + let eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString(); + let eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString(); + let parsedStart = moment.tz(event.start, event.timezone).format('YYYYMMDD[T]HHmmss'); + let parsedEnd = moment.tz(event.end, event.timezone).format('YYYYMMDD[T]HHmmss'); + let eventHasConcluded = false; + if (moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone))) { + eventHasConcluded = true; + } + let eventHasBegun = false; + if (moment.tz(event.start, event.timezone).isBefore(moment.tz(event.timezone))) { + eventHasBegun = true; + } + let fromNow = moment.tz(event.start, event.timezone).fromNow(); + let parsedDescription = marked.parse(event.description); + let eventEditToken = event.editToken; + + let escapedName = event.name.replace(/\s+/g, '+'); + + let eventHasCoverImage = false; + if (event.image) { + eventHasCoverImage = true; + } + else { + eventHasCoverImage = false; + } + let eventHasHost = false; + if (event.hostName) { + eventHasHost = true; + } + else { + eventHasHost = false; + } + let firstLoad = false; + if (event.firstLoad === true) { + firstLoad = true; + Event.findOneAndUpdate({ id: req.params.eventID }, { firstLoad: false }, function (err, raw) { + if (err) { + res.send(err); + } + }); + } + let editingEnabled = false; + if (Object.keys(req.query).length !== 0) { + if (!req.query.e) { + editingEnabled = false; + console.log("No edit token set"); + } + else { + if (req.query.e === eventEditToken) { + editingEnabled = true; + } + else { + editingEnabled = false; + } + } + } + let eventAttendees = event.attendees.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)) + .map(el => { + if (!el.id) { + el.id = el._id; + } + if (el.number > 1) { + el.name = `${el.name} (${el.number} people)`; + } + return el; + }) + .filter((obj, pos, arr) => { + return obj.status === 'attending' && arr.map(mapObj => mapObj.id).indexOf(obj.id) === pos; + }); + + let spotsRemaining, noMoreSpots; + let numberOfAttendees = eventAttendees.reduce((acc, attendee) => { + if (attendee.status === 'attending') { + return acc + attendee.number || 1; + } + return acc; + }, 0); + if (event.maxAttendees) { + spotsRemaining = event.maxAttendees - numberOfAttendees; + if (spotsRemaining <= 0) { + noMoreSpots = true; + } + } + let metadata = { + title: event.name, + description: marked.parse(event.description, { renderer: render_plain() }).split(" ").splice(0, 40).join(" ").trim(), + image: (eventHasCoverImage ? `https://${domain}/events/` + event.image : null), + url: `https://${domain}/` + req.params.eventID + }; + if (req.headers.accept && (req.headers.accept.includes('application/activity+json') || req.headers.accept.includes('application/json') || req.headers.accept.includes('application/json+ld'))) { + res.json(JSON.parse(event.activityPubActor)); + } + else { + res.set("X-Robots-Tag", "noindex"); + res.render('event', { + domain: domain, + isFederated: isFederated, + email: contactEmail, + title: event.name, + escapedName: escapedName, + eventData: event, + eventAttendees: eventAttendees, + numberOfAttendees, + spotsRemaining: spotsRemaining, + noMoreSpots: noMoreSpots, + eventStartISO: eventStartISO, + eventEndISO: eventEndISO, + parsedLocation: parsedLocation, + parsedStart: parsedStart, + parsedEnd: parsedEnd, + displayDate: displayDate, + fromNow: fromNow, + timezone: event.timezone, + parsedDescription: parsedDescription, + editingEnabled: editingEnabled, + eventHasCoverImage: eventHasCoverImage, + eventHasHost: eventHasHost, + firstLoad: firstLoad, + eventHasConcluded: eventHasConcluded, + eventHasBegun: eventHasBegun, + metadata: metadata, + siteName: siteName + }) + } + } + else { + res.status(404); + res.render('404', { url: req.url }); + } + + }) + .catch((err) => { + addToLog("displayEvent", "error", "Attempt to display event " + req.params.eventID + " failed with error: " + err); + console.log(err) + res.status(404); + res.render('404', { url: req.url }); + return; + }); +}) + +router.get('/:eventID/followers', (req, res) => { + if (!isFederated) return res.sendStatus(404); + const eventID = req.params.eventID; + Event.findOne({ + id: eventID + }) + .then((event) => { + if (event) { + const followers = event.followers.map(el => el.actorId); + let followersCollection = { + "type": "OrderedCollection", + "totalItems": followers.length, + "id": `https://${domain}/${eventID}/followers`, + "first": { + "type": "OrderedCollectionPage", + "totalItems": followers.length, + "partOf": `https://${domain}/${eventID}/followers`, + "orderedItems": followers, + "id": `https://${domain}/${eventID}/followers?page=1` + }, + "@context": ["https://www.w3.org/ns/activitystreams"] + }; + return res.json(followersCollection); + } + else { + return res.status(400).send('Bad request.'); + } + }) +}) + +router.get('/group/:eventGroupID', (req, res) => { + EventGroup.findOne({ + id: req.params.eventGroupID + }) + .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is + .then(async (eventGroup) => { + if (eventGroup) { + let parsedDescription = marked.parse(eventGroup.description); + let eventGroupEditToken = eventGroup.editToken; + + let escapedName = eventGroup.name.replace(/\s+/g, '+'); + + let eventGroupHasCoverImage = false; + if (eventGroup.image) { + eventGroupHasCoverImage = true; + } + else { + eventGroupHasCoverImage = false; + } + let eventGroupHasHost = false; + if (eventGroup.hostName) { + eventGroupHasHost = true; + } + else { + eventGroupHasHost = false; + } + + let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); + + events.map(event => { + if (moment.tz(event.end, event.timezone).isSame(event.start, 'day')) { + // Happening during one day + event.displayDate = moment.tz(event.start, event.timezone).format('D MMM YYYY'); + } + else { + event.displayDate = moment.tz(event.start, event.timezone).format('D MMM YYYY') + moment.tz(event.end, event.timezone).format(' - D MMM YYYY'); + } + if (moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone))) { + event.eventHasConcluded = true; + } else { + event.eventHasConcluded = false; + } + return (({ id, name, displayDate, eventHasConcluded }) => ({ id, name, displayDate, eventHasConcluded }))(event); + }); + + let upcomingEventsExist = false; + if (events.some(e => e.eventHasConcluded === false)) { + upcomingEventsExist = true; + } + + let firstLoad = false; + if (eventGroup.firstLoad === true) { + firstLoad = true; + EventGroup.findOneAndUpdate({ id: req.params.eventGroupID }, { firstLoad: false }, function (err, raw) { + if (err) { + res.send(err); + } + }); + } + let editingEnabled = false; + if (Object.keys(req.query).length !== 0) { + if (!req.query.e) { + editingEnabled = false; + console.log("No edit token set"); + } + else { + if (req.query.e === eventGroupEditToken) { + editingEnabled = true; + } + else { + editingEnabled = false; + } + } + } + let metadata = { + title: eventGroup.name, + description: marked.parse(eventGroup.description, { renderer: render_plain() }).split(" ").splice(0, 40).join(" ").trim(), + image: (eventGroupHasCoverImage ? `https://${domain}/events/` + eventGroup.image : null), + url: `https://${domain}/` + req.params.eventID + }; + res.set("X-Robots-Tag", "noindex"); + res.render('eventgroup', { + domain: domain, + title: eventGroup.name, + eventGroupData: eventGroup, + escapedName: escapedName, + events: events, + upcomingEventsExist: upcomingEventsExist, + parsedDescription: parsedDescription, + editingEnabled: editingEnabled, + eventGroupHasCoverImage: eventGroupHasCoverImage, + eventGroupHasHost: eventGroupHasHost, + firstLoad: firstLoad, + metadata: metadata + }) + } + else { + res.status(404); + res.render('404', { url: req.url }); + } + + }) + .catch((err) => { + addToLog("displayEventGroup", "error", "Attempt to display event group " + req.params.eventGroupID + " failed with error: " + err); + console.log(err) + res.status(404); + res.render('404', { url: req.url }); + return; + }); +}) + +router.get('/group/:eventGroupID/feed.ics', (req, res) => { + EventGroup.findOne({ + id: req.params.eventGroupID + }) + .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is + .then(async (eventGroup) => { + if (eventGroup) { + let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); + const string = exportIcal(events, eventGroup.name); + res.set('Content-Type', 'text/calendar'); + return res.send(string); + } + }) + .catch((err) => { + addToLog("eventGroupFeed", "error", "Attempt to display event group feed for " + req.params.eventGroupID + " failed with error: " + err); + console.log(err) + res.status(404); + res.render('404', { url: req.url }); + return; + }); +}); + +router.get('/exportevent/:eventID', (req, res) => { + Event.findOne({ + id: req.params.eventID + }) + .populate('eventGroup') + .then((event) => { + if (event) { + const string = exportIcal([event]); + res.send(string); + } + }) + .catch((err) => { + addToLog("exportEvent", "error", "Attempt to export event " + req.params.eventID + " failed with error: " + err); + console.log(err) + res.status(404); + res.render('404', { url: req.url }); + return; + }); +}); + +router.get('/exportgroup/:eventGroupID', (req, res) => { + EventGroup.findOne({ + id: req.params.eventGroupID + }) + .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is + .then(async (eventGroup) => { + if (eventGroup) { + let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); + const string = exportIcal(events); + res.send(string); + } + }) + .catch((err) => { + addToLog("exportEvent", "error", "Attempt to export event group " + req.params.eventGroupID + " failed with error: " + err); + console.log(err) + res.status(404); + res.render('404', { url: req.url }); + return; + }); +}); + +// BACKEND ROUTES + +router.post('/newevent', async (req, res) => { + let eventID = nanoid(); + let editToken = randomstring.generate(); + let eventImageFilename = ""; + let isPartOfEventGroup = false; + if (req.files && Object.keys(req.files).length !== 0) { + let eventImageBuffer = req.files.imageUpload.data; + eventImageFilename = await Jimp.read(eventImageBuffer) + .then(img => { + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write('./public/events/' + eventID + '.jpg'); // save + const filename = eventID + '.jpg'; + return filename; + }) + .catch(err => { + addToLog("Jimp", "error", "Attempt to edit image failed with error: " + err); + }); + } + let startUTC = moment.tz(req.body.eventStart, 'D MMMM YYYY, hh:mm a', req.body.timezone); + let endUTC = moment.tz(req.body.eventEnd, 'D MMMM YYYY, hh:mm a', req.body.timezone); + let eventGroup; + if (req.body.eventGroupCheckbox) { + eventGroup = await EventGroup.findOne({ + id: req.body.eventGroupID, + editToken: req.body.eventGroupEditToken + }) + if (eventGroup) { + isPartOfEventGroup = true; + } + } + + // generate RSA keypair for ActivityPub + let pair = generateRSAKeypair(); + + const event = new Event({ + id: eventID, + type: req.body.eventType, + name: req.body.eventName, + location: req.body.eventLocation, + start: startUTC, + end: endUTC, + timezone: req.body.timezone, + description: req.body.eventDescription, + image: eventImageFilename, + creatorEmail: req.body.creatorEmail, + url: req.body.eventURL, + hostName: req.body.hostName, + viewPassword: req.body.viewPassword, + editPassword: req.body.editPassword, + editToken: editToken, + eventGroup: isPartOfEventGroup ? eventGroup._id : null, + usersCanAttend: req.body.joinCheckbox ? true : false, + showUsersList: req.body.guestlistCheckbox ? true : false, + usersCanComment: req.body.interactionCheckbox ? true : false, + maxAttendees: req.body.maxAttendees, + firstLoad: true, + activityPubActor: ap.createActivityPubActor(eventID, domain, pair.public, marked.parse(req.body.eventDescription), req.body.eventName, req.body.eventLocation, eventImageFilename, startUTC, endUTC, req.body.timezone), + activityPubEvent: ap.createActivityPubEvent(req.body.eventName, startUTC, endUTC, req.body.timezone, req.body.eventDescription, req.body.eventLocation), + activityPubMessages: [{ id: `https://${domain}/${eventID}/m/featuredPost`, content: JSON.stringify(ap.createFeaturedPost(eventID, req.body.eventName, startUTC, endUTC, req.body.timezone, req.body.eventDescription, req.body.eventLocation)) }], + publicKey: pair.public, + privateKey: pair.private + }); + event.save() + .then((event) => { + addToLog("createEvent", "success", "Event " + eventID + "created"); + // Send email with edit link + if (req.body.creatorEmail && sendEmails) { + req.app.get('hbsInstance').renderView('./views/emails/createevent.handlebars', { eventID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail + }, + subject: `${siteName}: ${req.body.eventName}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + // If the event was added to a group, send an email to any group + // subscribers + if (event.eventGroup && sendEmails) { + EventGroup.findOne({ _id: event.eventGroup._id }) + .then((eventGroup) => { + const subscribers = eventGroup.subscribers.reduce((acc, current) => { + if (acc.includes(current.email)) { + return acc; + } + return [current.email, ...acc]; + }, []); + subscribers.forEach(emailAddress => { + req.app.get('hbsInstance').renderView('./views/emails/eventgroupupdated.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, eventGroupName: eventGroup.name, eventName: event.name, eventID: event.id, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(emailAddress), cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: emailAddress, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New event in ${eventGroup.name}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + }); + }); + } + res.writeHead(302, { + 'Location': '/' + eventID + '?e=' + editToken + }); + res.end(); + }) + .catch((err) => { res.status(500).send('Database error, please try again :( - ' + err); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); +}); + +router.post('/importevent', (req, res) => { + let eventID = nanoid(); + let editToken = randomstring.generate(); + if (req.files && Object.keys(req.files).length !== 0) { + let iCalObject = ical.parseICS(req.files.icsImportControl.data.toString('utf8')); + let importedEventData = iCalObject[Object.keys(iCalObject)]; + + let creatorEmail; + if (req.body.creatorEmail) { + creatorEmail = req.body.creatorEmail; + } else if (importedEventData.organizer) { + creatorEmail = importedEventData.organizer.val.replace("MAILTO:", ""); + } + + const event = new Event({ + id: eventID, + type: 'public', + name: importedEventData.summary, + location: importedEventData.location, + start: importedEventData.start, + end: importedEventData.end, + timezone: typeof importedEventData.start.tz !== 'undefined' ? importedEventData.start.tz : "Etc/UTC", + description: importedEventData.description, + image: '', + creatorEmail: creatorEmail, + url: '', + hostName: importedEventData.organizer ? importedEventData.organizer.params.CN.replace(/["]+/g, '') : "", + viewPassword: '', + editPassword: '', + editToken: editToken, + usersCanAttend: false, + showUsersList: false, + usersCanComment: false, + firstLoad: true + }); + event.save() + .then(() => { + addToLog("createEvent", "success", "Event " + eventID + " created"); + // Send email with edit link + if (creatorEmail && sendEmails) { + req.app.get('hbsInstance').renderView('./views/emails/createevent.handlebars', { eventID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail + }, + subject: `${siteName}: ${importedEventData.summary}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + res.writeHead(302, { + 'Location': '/' + eventID + '?e=' + editToken + }); + res.end(); + }) + .catch((err) => { res.send('Database error, please try again :('); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); + } + else { + console.log("Files array is empty!") + res.status(500).end(); + } +}); + +router.post('/neweventgroup', (req, res) => { + let eventGroupID = nanoid(); + let editToken = randomstring.generate(); + let eventGroupImageFilename = ""; + if (req.files && Object.keys(req.files).length !== 0) { + let eventImageBuffer = req.files.imageUpload.data; + Jimp.read(eventImageBuffer, (err, img) => { + if (err) addToLog("Jimp", "error", "Attempt to edit image failed with error: " + err); + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write('./public/events/' + eventGroupID + '.jpg'); // save + }); + eventGroupImageFilename = eventGroupID + '.jpg'; + } + const eventGroup = new EventGroup({ + id: eventGroupID, + name: req.body.eventGroupName, + description: req.body.eventGroupDescription, + image: eventGroupImageFilename, + creatorEmail: req.body.creatorEmail, + url: req.body.eventGroupURL, + hostName: req.body.hostName, + editToken: editToken, + firstLoad: true + }); + eventGroup.save() + .then(() => { + addToLog("createEventGroup", "success", "Event group " + eventGroupID + " created"); + // Send email with edit link + if (req.body.creatorEmail && sendEmails) { + req.app.get('hbsInstance').renderView('./views/emails/createeventgroup.handlebars', { eventGroupID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail + }, + subject: `${siteName}: ${req.body.eventGroupName}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + res.writeHead(302, { + 'Location': '/group/' + eventGroupID + '?e=' + editToken + }); + res.end(); + }) + .catch((err) => { res.send('Database error, please try again :( - ' + err); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); +}); + +router.post('/verifytoken/event/:eventID', (req, res) => { + Event.findOne({ + id: req.params.eventID, + editToken: req.body.editToken, + }) + .then(event => { + if (event) return res.sendStatus(200); + return res.sendStatus(404); + }) +}); + +router.post('/verifytoken/group/:eventGroupID', (req, res) => { + EventGroup.findOne({ + id: req.params.eventGroupID, + editToken: req.body.editToken, + }) + .then(group => { + if (group) return res.sendStatus(200); + return res.sendStatus(404); + }) +}); + + +router.post('/editevent/:eventID/:editToken', (req, res) => { + let submittedEditToken = req.params.editToken; + Event.findOne(({ + id: req.params.eventID, + })) + .then(async (event) => { + if (event.editToken === submittedEditToken) { + // Token matches + + // If there is a new image, upload that first + let eventID = req.params.eventID; + let eventImageFilename = event.image; + if (req.files && Object.keys(req.files).length !== 0) { + let eventImageBuffer = req.files.imageUpload.data; + Jimp.read(eventImageBuffer, (err, img) => { + if (err) throw err; + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG + .write('./public/events/' + eventID + '.jpg'); // save + }); + eventImageFilename = eventID + '.jpg'; + } + let startUTC = moment.tz(req.body.eventStart, 'D MMMM YYYY, hh:mm a', req.body.timezone); + let endUTC = moment.tz(req.body.eventEnd, 'D MMMM YYYY, hh:mm a', req.body.timezone); + + let isPartOfEventGroup = false; + let eventGroup; + if (req.body.eventGroupCheckbox) { + eventGroup = await EventGroup.findOne({ + id: req.body.eventGroupID, + editToken: req.body.eventGroupEditToken + }) + if (eventGroup) { + isPartOfEventGroup = true; + } + } + const updatedEvent = { + name: req.body.eventName, + location: req.body.eventLocation, + start: startUTC, + end: endUTC, + timezone: req.body.timezone, + description: req.body.eventDescription, + url: req.body.eventURL, + hostName: req.body.hostName, + image: eventImageFilename, + usersCanAttend: req.body.joinCheckbox ? true : false, + showUsersList: req.body.guestlistCheckbox ? true : false, + usersCanComment: req.body.interactionCheckbox ? true : false, + maxAttendees: req.body.maxAttendeesCheckbox ? req.body.maxAttendees : null, + eventGroup: isPartOfEventGroup ? eventGroup._id : null, + activityPubActor: event.activityPubActor ? ap.updateActivityPubActor(JSON.parse(event.activityPubActor), req.body.eventDescription, req.body.eventName, req.body.eventLocation, eventImageFilename, startUTC, endUTC, req.body.timezone) : null, + activityPubEvent: event.activityPubEvent ? ap.updateActivityPubEvent(JSON.parse(event.activityPubEvent), req.body.eventName, req.body.startUTC, req.body.endUTC, req.body.timezone) : null, + } + let diffText = '

This event was just updated with new information.

    '; + let displayDate; + if (event.name !== updatedEvent.name) { + diffText += `
  • the event name changed to ${updatedEvent.name}
  • `; + } + if (event.location !== updatedEvent.location) { + diffText += `
  • the location changed to ${updatedEvent.location}
  • `; + } + if (event.start.toISOString() !== updatedEvent.start.toISOString()) { + displayDate = moment.tz(updatedEvent.start, updatedEvent.timezone).format('dddd D MMMM YYYY h:mm a'); + diffText += `
  • the start time changed to ${displayDate}
  • `; + } + if (event.end.toISOString() !== updatedEvent.end.toISOString()) { + displayDate = moment.tz(updatedEvent.end, updatedEvent.timezone).format('dddd D MMMM YYYY h:mm a'); + diffText += `
  • the end time changed to ${displayDate}
  • `; + } + if (event.timezone !== updatedEvent.timezone) { + diffText += `
  • the time zone changed to ${updatedEvent.timezone}
  • `; + } + if (event.description !== updatedEvent.description) { + diffText += `
  • the event description changed
  • `; + } + diffText += `
`; + Event.findOneAndUpdate({ id: req.params.eventID }, updatedEvent, function (err, raw) { + if (err) { + addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); + res.send(err); + } + }) + .then(() => { + addToLog("editEvent", "success", "Event " + req.params.eventID + " edited"); + // send update to ActivityPub subscribers + Event.findOne({ id: req.params.eventID }, function (err, event) { + if (!event) return; + let attendees = event.attendees.filter(el => el.id); + if (!err) { + // broadcast an identical message to all followers, will show in home timeline + const guidObject = crypto.randomBytes(16).toString('hex'); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, + "name": `RSVP to ${event.name}`, + "type": "Note", + 'cc': 'https://www.w3.org/ns/activitystreams#Public', + "content": `${diffText} See here: https://${domain}/${req.params.eventID}`, + } + ap.broadcastCreateMessage(jsonObject, event.followers, eventID) + // also broadcast an Update profile message to all followers so that at least Mastodon servers will update the local profile information + const jsonUpdateObject = JSON.parse(event.activityPubActor); + ap.broadcastUpdateMessage(jsonUpdateObject, event.followers, eventID) + // also broadcast an Update/Event for any calendar apps that are consuming our Events + const jsonEventObject = JSON.parse(event.activityPubEvent); + ap.broadcastUpdateMessage(jsonEventObject, event.followers, eventID) + + // DM to attendees + for (const attendee of attendees) { + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + "name": `RSVP to ${event.name}`, + "type": "Note", + "content": `@${attendee.name} ${diffText} See here: https://${domain}/${req.params.eventID}`, + "tag": [{ "type": "Mention", "href": attendee.id, "name": attendee.name }] + } + // send direct message to user + ap.sendDirectMessage(jsonObject, attendee.id, eventID); + } + } + }) + // Send update to all attendees + if (sendEmails) { + Event.findOne({ id: req.params.eventID }).then((event) => { + const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.app.get('hbsInstance').renderView('./views/emails/editevent.handlebars', { diffText, eventID: req.params.eventID, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + address: contactEmail + }, + subject: `${siteName}: ${event.name} was just edited`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.sendMultiple(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + else { + console.log("Nothing to send!"); + } + }) + } + res.writeHead(302, { + 'Location': '/' + req.params.eventID + '?e=' + req.params.editToken + }); + res.end(); + }) + .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); }); + } + else { + // Token doesn't match + res.send('Sorry! Something went wrong'); + addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: token does not match"); + } + }) + .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); }); +}); + +router.post('/editeventgroup/:eventGroupID/:editToken', (req, res) => { + let submittedEditToken = req.params.editToken; + EventGroup.findOne(({ + id: req.params.eventGroupID, + })) + .then((eventGroup) => { + if (eventGroup.editToken === submittedEditToken) { + // Token matches + + // If there is a new image, upload that first + let eventGroupID = req.params.eventGroupID; + let eventGroupImageFilename = eventGroup.image; + if (req.files && Object.keys(req.files).length !== 0) { + let eventImageBuffer = req.files.eventGroupImageUpload.data; + Jimp.read(eventImageBuffer, (err, img) => { + if (err) throw err; + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG + .write('./public/events/' + eventGroupID + '.jpg'); // save + }); + eventGroupImageFilename = eventGroupID + '.jpg'; + } + const updatedEventGroup = { + name: req.body.eventGroupName, + description: req.body.eventGroupDescription, + url: req.body.eventGroupURL, + hostName: req.body.hostName, + image: eventGroupImageFilename + } + EventGroup.findOneAndUpdate({ id: req.params.eventGroupID }, updatedEventGroup, function (err, raw) { + if (err) { + addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); + res.send(err); + } + }) + .then(() => { + addToLog("editEventGroup", "success", "Event group " + req.params.eventGroupID + " edited"); + res.writeHead(302, { + 'Location': '/group/' + req.params.eventGroupID + '?e=' + req.params.editToken + }); + res.end(); + }) + .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); }); + } + else { + // Token doesn't match + res.send('Sorry! Something went wrong'); + addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: token does not match"); + } + }) + .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); }); +}); + +router.post('/deleteimage/:eventID/:editToken', (req, res) => { + let submittedEditToken = req.params.editToken; + Event.findOne(({ + id: req.params.eventID, + })) + .then((event) => { + if (event.editToken === submittedEditToken) { + // Token matches + if (event.image) { + eventImage = event.image; + } else { + res.status(500).send('This event doesn\'t have a linked image. What are you even doing'); + } + fs.unlink(global.appRoot + '/public/events/' + eventImage, (err) => { + if (err) { + res.status(500).send(err); + addToLog("deleteEventImage", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); + } + // Image removed + addToLog("deleteEventImage", "success", "Image for event " + req.params.eventID + " deleted"); + event.image = ""; + event.save() + .then(response => { + res.status(200).send('Success'); + }) + .catch(err => { + res.status(500).send(err); + addToLog("deleteEventImage", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); + }) + }); + } + }); +}); + +router.post('/deleteevent/:eventID/:editToken', (req, res) => { + let submittedEditToken = req.params.editToken; + let eventImage; + Event.findOne(({ + id: req.params.eventID, + })) + .then((event) => { + if (event.editToken === submittedEditToken) { + // Token matches + + let eventImage; + if (event.image) { + eventImage = event.image; + } + + // broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information + const guidUpdateObject = crypto.randomBytes(16).toString('hex'); + const jsonUpdateObject = JSON.parse(event.activityPubActor); + // first broadcast AP messages, THEN delete from DB + ap.broadcastDeleteMessage(jsonUpdateObject, event.followers, req.params.eventID, function (statuses) { + Event.deleteOne({ id: req.params.eventID }, function (err, raw) { + if (err) { + res.send(err); + addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); + } + }) + .then(() => { + // Delete image + if (eventImage) { + fs.unlink(global.appRoot + '/public/events/' + eventImage, (err) => { + if (err) { + res.send(err); + addToLog("deleteEvent", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); + } + // Image removed + addToLog("deleteEvent", "success", "Event " + req.params.eventID + " deleted"); + }) + } + res.writeHead(302, { + 'Location': '/' + }); + res.end(); + + // Send emails here otherwise they don't exist lol + if (sendEmails) { + const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.app.get('hbsInstance').renderView('./views/emails/deleteevent.handlebars', { siteName, siteLogo, domain, eventName: event.name, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + address: contactEmail + }, + subject: `${siteName}: ${event.name} was deleted`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.sendMultiple(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + else { + console.log("Nothing to send!"); + } + } + }) + .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); }); + }); + } + else { + // Token doesn't match + res.send('Sorry! Something went wrong'); + addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: token does not match"); + } + }) + .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); }); +}); + +router.post('/deleteeventgroup/:eventGroupID/:editToken', (req, res) => { + let submittedEditToken = req.params.editToken; + EventGroup.findOne(({ + id: req.params.eventGroupID, + })) + .then(async (eventGroup) => { + if (eventGroup.editToken === submittedEditToken) { + // Token matches + + let linkedEvents = await Event.find({ eventGroup: eventGroup._id }); + + let linkedEventIDs = linkedEvents.map(event => event._id); + let eventGroupImage = false; + if (eventGroup.image) { + eventGroupImage = eventGroup.image; + } + + EventGroup.deleteOne({ id: req.params.eventGroupID }, function (err, raw) { + if (err) { + res.send(err); + addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); + } + }) + .then(() => { + // Delete image + if (eventGroupImage) { + fs.unlink(global.appRoot + '/public/events/' + eventGroupImage, (err) => { + if (err) { + res.send(err); + addToLog("deleteEventGroup", "error", "Attempt to delete event image for event group " + req.params.eventGroupID + " failed with error: " + err); + } + }) + } + Event.update({ _id: { $in: linkedEventIDs } }, { $set: { eventGroup: null } }, { multi: true }) + .then(response => { + console.log(response); + addToLog("deleteEventGroup", "success", "Event group " + req.params.eventGroupID + " deleted"); + res.writeHead(302, { + 'Location': '/' + }); + res.end(); + }) + .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); + }) + .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); + } + else { + // Token doesn't match + res.send('Sorry! Something went wrong'); + addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: token does not match"); + } + }) + .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); +}); + +router.post('/attendee/provision', async (req, res) => { + const removalPassword = niceware.generatePassphrase(6).join('-'); + const newAttendee = { + status: 'provisioned', + removalPassword, + created: Date.now(), + }; + + const event = await Event.findOne({ id: req.query.eventID }).catch(e => { + addToLog("provisionEventAttendee", "error", "Attempt to provision attendee in event " + req.query.eventID + " failed with error: " + e); + return res.sendStatus(500); + }); + + if (!event) { + return res.sendStatus(404); + } + + event.attendees.push(newAttendee); + await event.save().catch(e => { + console.log(e); + addToLog("provisionEventAttendee", "error", "Attempt to provision attendee in event " + req.query.eventID + " failed with error: " + e); + return res.sendStatus(500); + }); + addToLog("provisionEventAttendee", "success", "Attendee provisioned in event " + req.query.eventID); + + // Return the removal password and the number of free spots remaining + let freeSpots; + if (event.maxAttendees !== null && event.maxAttendees !== undefined) { + freeSpots = event.maxAttendees - event.attendees.reduce((acc, a) => acc + (a.status === 'attending' ? (a.number || 1) : 0), 0); + } else { + freeSpots = undefined; + } + return res.json({ removalPassword, freeSpots }); +}); + +router.post('/attendevent/:eventID', async (req, res) => { + // Do not allow empty removal passwords + if (!req.body.removalPassword) { + return res.sendStatus(500); + } + const event = await Event.findOne({ id: req.params.eventID }).catch(e => { + addToLog("attendEvent", "error", "Attempt to attend event " + req.params.eventID + " failed with error: " + e); + return res.sendStatus(500); + }); + if (!event) { + return res.sendStatus(404); + } + const attendee = event.attendees.find(a => a.removalPassword === req.body.removalPassword); + if (!attendee) { + return res.sendStatus(404); + } + // Do we have enough free spots in this event to accomodate this attendee? + // First, check if the event has a max number of attendees + if (event.maxAttendees !== null && event.maxAttendees !== undefined) { + const freeSpots = event.maxAttendees - event.attendees.reduce((acc, a) => acc + (a.status === 'attending' ? (a.number || 1) : 0), 0); + if (req.body.attendeeNumber > freeSpots) { + return res.sendStatus(403); + } + } + + Event.findOneAndUpdate({ id: req.params.eventID, 'attendees.removalPassword': req.body.removalPassword }, { + "$set": { + "attendees.$.status": "attending", + "attendees.$.name": req.body.attendeeName, + "attendees.$.email": req.body.attendeeEmail, + "attendees.$.number": req.body.attendeeNumber, + } + }).then((event) => { + addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); + if (sendEmails) { + if (req.body.attendeeEmail) { + req.app.get('hbsInstance').renderView('./views/emails/addeventattendee.handlebars', { eventID: req.params.eventID, siteName, siteLogo, domain, removalPassword: req.body.removalPassword, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You're RSVPed to ${event.name}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + } + res.redirect(`/${req.params.eventID}`); + }) + .catch((error) => { + res.send('Database error, please try again :('); + addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + error); + }); +}); + +router.post('/unattendevent/:eventID', (req, res) => { + const removalPassword = req.body.removalPassword; + // Don't allow blank removal passwords! + if (!removalPassword) { + return res.sendStatus(500); + } + + Event.update( + { id: req.params.eventID }, + { $pull: { attendees: { removalPassword } } } + ) + .then(response => { + console.log(response) + addToLog("unattendEvent", "success", "Attendee removed self from event " + req.params.eventID); + if (sendEmails) { + if (req.body.attendeeEmail) { + req.app.get('hbsInstance').renderView('./views/emails/unattendevent.handlebars', { eventID: req.params.eventID, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + } + res.writeHead(302, { + 'Location': '/' + req.params.eventID + }); + res.end(); + }) + .catch((err) => { + res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee from event " + req.params.eventID + " failed with error: " + err); + }); +}); + +// this is a one-click unattend that requires a secret URL that only the person who RSVPed over +// activitypub knows +router.get('/oneclickunattendevent/:eventID/:attendeeID', (req, res) => { + // Mastodon will "click" links that sent to its users, presumably as a prefetch? + // Anyway, this ignores the automated clicks that are done without the user's knowledge + if (req.headers['user-agent'] && req.headers['user-agent'].includes('Mastodon')) { + return res.sendStatus(200); + } + Event.update( + { id: req.params.eventID }, + { $pull: { attendees: { _id: req.params.attendeeID } } } + ) + .then(response => { + addToLog("oneClickUnattend", "success", "Attendee removed via one click unattend " + req.params.eventID); + if (sendEmails) { + // currently this is never called because we don't have the email address + if (req.body.attendeeEmail) { + req.app.get('hbsInstance').renderView('./views/emails/removeeventattendee.handlebars', { eventName: req.params.eventName, siteName, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + } + res.writeHead(302, { + 'Location': '/' + req.params.eventID + }); + res.end(); + }) + .catch((err) => { + res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee by admin from event " + req.params.eventID + " failed with error: " + err); + }); +}); + +router.post('/removeattendee/:eventID/:attendeeID', (req, res) => { + Event.update( + { id: req.params.eventID }, + { $pull: { attendees: { _id: req.params.attendeeID } } } + ) + .then(response => { + console.log(response) + addToLog("removeEventAttendee", "success", "Attendee removed by admin from event " + req.params.eventID); + if (sendEmails) { + // currently this is never called because we don't have the email address + if (req.body.attendeeEmail) { + req.app.get('hbsInstance').renderView('./views/emails/removeeventattendee.handlebars', { eventName: req.params.eventName, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + } + res.writeHead(302, { + 'Location': '/' + req.params.eventID + }); + res.end(); + }) + .catch((err) => { + res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee by admin from event " + req.params.eventID + " failed with error: " + err); + }); +}); + +/* + * Create an email subscription on an event group. + */ +router.post('/subscribe/:eventGroupID', (req, res) => { + const subscriber = { + email: req.body.emailAddress, + }; + if (!subscriber.email) { + return res.sendStatus(500); + } + + EventGroup.findOne(({ + id: req.params.eventGroupID, + })) + .then((eventGroup) => { + if (!eventGroup) { + return res.sendStatus(404); + } + eventGroup.subscribers.push(subscriber); + eventGroup.save(); + if (sendEmails) { + req.app.get('hbsInstance').renderView('./views/emails/subscribed.handlebars', { eventGroupName: eventGroup.name, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(subscriber.email), siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: subscriber.email, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have subscribed to an event group`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + return res.redirect(`/group/${eventGroup.id}`) + }) + .catch((error) => { + addToLog("addSubscription", "error", "Attempt to subscribe " + req.body.emailAddress + " to event group " + req.params.eventGroupID + " failed with error: " + error); + return res.sendStatus(500); + }); +}); + +/* + * Delete an existing email subscription on an event group. + */ +router.get('/unsubscribe/:eventGroupID', (req, res) => { + const email = req.query.email; + console.log(email); + if (!email) { + return res.sendStatus(500); + } + + EventGroup.update( + { id: req.params.eventGroupID }, + { $pull: { subscribers: { email } } } + ) + .then(response => { + return res.redirect('/'); + }) + .catch((error) => { + addToLog("removeSubscription", "error", "Attempt to unsubscribe " + req.query.email + " from event group " + req.params.eventGroupID + " failed with error: " + error); + return res.sendStatus(500); + }); +}); + +router.post('/post/comment/:eventID', (req, res) => { + let commentID = nanoid(); + const newComment = { + id: commentID, + author: req.body.commentAuthor, + content: req.body.commentContent, + timestamp: moment() + }; + + Event.findOne({ + id: req.params.eventID, + }, function (err, event) { + if (!event) return; + event.comments.push(newComment); + event.save() + .then(() => { + addToLog("addEventComment", "success", "Comment added to event " + req.params.eventID); + // broadcast an identical message to all followers, will show in their home timeline + // and in the home timeline of the event + const guidObject = crypto.randomBytes(16).toString('hex'); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, + "name": `Comment on ${event.name}`, + "type": "Note", + 'cc': 'https://www.w3.org/ns/activitystreams#Public', + "content": `

${req.body.commentAuthor} commented: ${req.body.commentContent}.

See the full conversation here.

`, + } + ap.broadcastCreateMessage(jsonObject, event.followers, req.params.eventID) + if (sendEmails) { + Event.findOne({ id: req.params.eventID }).then((event) => { + const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.app.get('hbsInstance').renderView('./views/emails/addeventcomment.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, commentAuthor: req.body.commentAuthor, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New comment in ${event.name}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.sendMultiple(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + else { + console.log("Nothing to send!"); + } + }); + } + res.writeHead(302, { + 'Location': '/' + req.params.eventID + }); + res.end(); + }) + .catch((err) => { res.send('Database error, please try again :(' + err); addToLog("addEventComment", "error", "Attempt to add comment to event " + req.params.eventID + " failed with error: " + err); }); + }); +}); + +router.post('/post/reply/:eventID/:commentID', (req, res) => { + let replyID = nanoid(); + let commentID = req.params.commentID; + const newReply = { + id: replyID, + author: req.body.replyAuthor, + content: req.body.replyContent, + timestamp: moment() + }; + Event.findOne({ + id: req.params.eventID, + }, function (err, event) { + if (!event) return; + var parentComment = event.comments.id(commentID); + parentComment.replies.push(newReply); + event.save() + .then(() => { + addToLog("addEventReply", "success", "Reply added to comment " + commentID + " in event " + req.params.eventID); + // broadcast an identical message to all followers, will show in their home timeline + const guidObject = crypto.randomBytes(16).toString('hex'); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, + "name": `Comment on ${event.name}`, + "type": "Note", + 'cc': 'https://www.w3.org/ns/activitystreams#Public', + "content": `

${req.body.replyAuthor} commented: ${req.body.replyContent}

See the full conversation here.

`, + } + ap.broadcastCreateMessage(jsonObject, event.followers, req.params.eventID) + if (sendEmails) { + Event.findOne({ id: req.params.eventID }).then((event) => { + const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.app.get('hbsInstance').renderView('./views/emails/addeventcomment.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, commentAuthor: req.body.replyAuthor, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New comment in ${event.name}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.sendMultiple(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + else { + console.log("Nothing to send!"); + } + }); + } + res.writeHead(302, { + 'Location': '/' + req.params.eventID + }); + res.end(); + }) + .catch((err) => { res.send('Database error, please try again :('); addToLog("addEventReply", "error", "Attempt to add reply to comment " + commentID + " in event " + req.params.eventID + " failed with error: " + err); }); + }); +}); + +router.post('/deletecomment/:eventID/:commentID/:editToken', (req, res) => { + let submittedEditToken = req.params.editToken; + Event.findOne(({ + id: req.params.eventID, + })) + .then((event) => { + if (event.editToken === submittedEditToken) { + // Token matches + event.comments.id(req.params.commentID).remove(); + event.save() + .then(() => { + addToLog("deleteComment", "success", "Comment deleted from event " + req.params.eventID); + res.writeHead(302, { + 'Location': '/' + req.params.eventID + '?e=' + req.params.editToken + }); + res.end(); + }) + .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: " + err); }); + } + else { + // Token doesn't match + res.send('Sorry! Something went wrong'); + addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: token does not match"); + } + }) + .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: " + err); }); +}); + +router.post('/activitypub/inbox', (req, res) => { + if (!isFederated) return res.sendStatus(404); + // validate the incoming message + const signature = req.get('Signature'); + let signature_header = signature.split(',').map(pair => { + return pair.split('=').map(value => { + return value.replace(/^"/g, '').replace(/"$/g, '') + }); + }).reduce((acc, el) => { + acc[el[0]] = el[1]; + return acc; + }, {}); + + // get the actor + // TODO if this is a Delete for an Actor this won't work + request({ + url: signature_header.keyId, + headers: { + 'Accept': 'application/activity+json', + 'Content-Type': 'application/activity+json' + } + }, function (error, response, actor) { + let publicKey = ''; + + try { + if (JSON.parse(actor).publicKey) { + publicKey = JSON.parse(actor).publicKey.publicKeyPem; + } + } + catch (err) { + return res.status(500).send('Actor could not be parsed' + err); + } + + let comparison_string = signature_header.headers.split(' ').map(header => { + if (header === '(request-target)') { + return '(request-target): post /activitypub/inbox'; + } + else { + return `${header}: ${req.get(header)}` + } + }).join('\n'); + + const verifier = crypto.createVerify('RSA-SHA256') + verifier.update(comparison_string, 'ascii') + const publicKeyBuf = new Buffer(publicKey, 'ascii') + const signatureBuf = new Buffer(signature_header.signature, 'base64') + try { + const result = verifier.verify(publicKeyBuf, signatureBuf) + if (result) { + // actually process the ActivityPub message now that it's been verified + ap.processInbox(req, res); + } + else { + return res.status(401).send('Signature could not be verified.'); + } + } + catch (err) { + return res.status(401).send('Signature could not be verified: ' + err); + } + }); +}); + +router.use(function (req, res, next) { + res.status(404); + res.render('404', { url: req.url }); + return; +}); + +addToLog("startup", "success", "Started up successfully"); + +module.exports = router; diff --git a/src/start.js b/src/start.js new file mode 100755 index 0000000..363062e --- /dev/null +++ b/src/start.js @@ -0,0 +1,32 @@ +require('dotenv').config(); + +const path = require('path'); + +const mongoose = require('mongoose'); + +const databaseCredentials = require('./config/database.js'); +const port = require('./config/domain.js').port; + +mongoose.connect(databaseCredentials.url, { useNewUrlParser: true, useUnifiedTopology: true }); +mongoose.set('useCreateIndex', true); +mongoose.Promise = global.Promise; +mongoose.connection + .on('connected', () => { + console.log('Mongoose connection open!'); + }) + .on('error', (err) => { + console.log('Connection error: ${err.message}'); + }); + + +require('./models/Event'); +require('./models/Log'); +require('./models/EventGroup'); + +const app = require('./app.js'); + +global.appRoot = path.resolve(__dirname); + +const server = app.listen(port, () => { + console.log(`Welcome to gathio! The app is now running on http://localhost:${server.address().port}`); +}); diff --git a/start.js b/start.js deleted file mode 100755 index 325e9e0..0000000 --- a/start.js +++ /dev/null @@ -1,32 +0,0 @@ -require('dotenv').config(); - -const path = require('path'); - -const mongoose = require('mongoose'); - -const databaseCredentials = require('./config/database.js'); -const port = require('./config/domain.js').port; - -mongoose.connect(databaseCredentials.url, { useNewUrlParser: true, useUnifiedTopology: true }); -mongoose.set('useCreateIndex', true); -mongoose.Promise = global.Promise; -mongoose.connection - .on('connected', () => { - console.log('Mongoose connection open!'); - }) - .on('error', (err) => { - console.log('Connection error: ${err.message}'); - }); - - -require('./models/Event'); -require('./models/Log'); -require('./models/EventGroup'); - -const app = require('./app'); - -global.appRoot = path.resolve(__dirname); - -const server = app.listen(port, () => { - console.log(`Welcome to gathio! The app is now running on http://localhost:${server.address().port}`); -}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9d7b7ab --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "allowJs": true, + "checkJs": true, + "removeComments": true, + "resolveJsonModule": true, + "typeRoots": ["./node_modules/@types"], + "sourceMap": true, + "outDir": "dist", + "strict": true, + "baseUrl": ".", + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "moduleResolution": "nodenext", + "skipLibCheck": true, + }, + "include": ["src/**/*"], +} \ No newline at end of file -- cgit v1.2.3 From 57545c3a3eda30a1ef6ece10090a6b0fb69a29a7 Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:06 +0100 Subject: Add Prettier --- .prettierignore | 1 + package.json | 1 + pnpm-lock.yaml | 9 +++++++++ 3 files changed, 11 insertions(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..53c37a1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/package.json b/package.json index 0a3d018..d765c0e 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "devDependencies": { "eslint": "^8.40.0", "nodemon": "^2.0.22", + "prettier": "^2.8.8", "typescript": "^5.0.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 972ab1f..33b638a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ devDependencies: nodemon: specifier: ^2.0.22 version: 2.0.22 + prettier: + specifier: ^2.8.8 + version: 2.8.8 typescript: specifier: ^5.0.4 version: 5.0.4 @@ -2291,6 +2294,12 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: false -- cgit v1.2.3 From 453c08463bf46e568c8a5018416ad88498a73f29 Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:06 +0100 Subject: linting: source files --- .prettierignore | 3 +- .prettierrc.json | 1 + src/activitypub.js | 1684 +++++++++++++---------- src/app.js | 65 +- src/config/api-example.js | 10 +- src/config/database-docker.js | 2 +- src/config/database-example.js | 2 +- src/config/domain-example.js | 22 +- src/helpers.js | 30 +- src/models/Event.js | 157 +-- src/models/EventGroup.js | 28 +- src/models/Log.js | 14 +- src/routes.js | 2903 ++++++++++++++++++++++++++-------------- src/start.js | 40 +- 14 files changed, 3110 insertions(+), 1851 deletions(-) create mode 100644 .prettierrc.json diff --git a/.prettierignore b/.prettierignore index 53c37a1..210b0f6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -dist \ No newline at end of file +dist +public \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/src/activitypub.js b/src/activitypub.js index 442f03c..c8fc682 100644 --- a/src/activitypub.js +++ b/src/activitypub.js @@ -1,123 +1,168 @@ -const domain = require('./config/domain.js').domain; -const contactEmail = require('./config/domain.js').email; -const siteName = require('./config/domain.js').sitename; -const isFederated = require('./config/domain.js').isFederated; -const request = require('request'); -const addToLog = require('./helpers.js').addToLog; -const crypto = require('crypto'); +const domain = require("./config/domain.js").domain; +const contactEmail = require("./config/domain.js").email; +const siteName = require("./config/domain.js").sitename; +const isFederated = require("./config/domain.js").isFederated; +const request = require("request"); +const addToLog = require("./helpers.js").addToLog; +const crypto = require("crypto"); // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs -const { customAlphabet } = require('nanoid'); -const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_', 21); -var moment = require('moment-timezone'); -const mongoose = require('mongoose'); -const Event = mongoose.model('Event'); -const EventGroup = mongoose.model('EventGroup'); -var sanitizeHtml = require('sanitize-html'); - -function createActivityPubActor(eventID, domain, pubkey, description, name, location, imageFilename, startUTC, endUTC, timezone) { +const { customAlphabet } = require("nanoid"); +const nanoid = customAlphabet( + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", + 21 +); +var moment = require("moment-timezone"); +const mongoose = require("mongoose"); +const Event = mongoose.model("Event"); +const EventGroup = mongoose.model("EventGroup"); +var sanitizeHtml = require("sanitize-html"); + +function createActivityPubActor( + eventID, + domain, + pubkey, + description, + name, + location, + imageFilename, + startUTC, + endUTC, + timezone +) { let actor = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", ], - 'id': `https://${domain}/${eventID}`, - 'type': 'Person', - 'preferredUsername': `${eventID}`, - 'inbox': `https://${domain}/activitypub/inbox`, - 'outbox': `https://${domain}/${eventID}/outbox`, - 'followers': `https://${domain}/${eventID}/followers`, - 'summary': `

${description}

`, - 'name': name, - 'featured': `https://${domain}/${eventID}/featured`, - - 'publicKey': { - 'id': `https://${domain}/${eventID}#main-key`, - 'owner': `https://${domain}/${eventID}`, - 'publicKeyPem': pubkey - } + id: `https://${domain}/${eventID}`, + type: "Person", + preferredUsername: `${eventID}`, + inbox: `https://${domain}/activitypub/inbox`, + outbox: `https://${domain}/${eventID}/outbox`, + followers: `https://${domain}/${eventID}/followers`, + summary: `

${description}

`, + name: name, + featured: `https://${domain}/${eventID}/featured`, + + publicKey: { + id: `https://${domain}/${eventID}#main-key`, + owner: `https://${domain}/${eventID}`, + publicKeyPem: pubkey, + }, }; if (location) { - actor.summary += `

Location: ${location}.

` + actor.summary += `

Location: ${location}.

`; } let displayDate; if (startUTC && timezone) { - displayDate = moment.tz(startUTC, timezone).format('D MMMM YYYY h:mm a'); + displayDate = moment.tz(startUTC, timezone).format("D MMMM YYYY h:mm a"); actor.summary += `

Starting ${displayDate} ${timezone}.

`; } if (imageFilename) { actor.icon = { - 'type': 'Image', - 'mediaType': 'image/jpg', - 'url': `https://${domain}/events/${imageFilename}`, + type: "Image", + mediaType: "image/jpg", + url: `https://${domain}/events/${imageFilename}`, }; } return JSON.stringify(actor); } -function createActivityPubEvent(name, startUTC, endUTC, timezone, description, location) { - const guid = crypto.randomBytes(16).toString('hex'); +function createActivityPubEvent( + name, + startUTC, + endUTC, + timezone, + description, + location +) { + const guid = crypto.randomBytes(16).toString("hex"); let eventObject = { "@context": "https://www.w3.org/ns/activitystreams", - 'id': `https://${domain}/${guid}`, - "name": name, - "type": "Event", - "startTime": moment.tz(startUTC, timezone).format(), - "endTime": moment.tz(endUTC, timezone).format(), - "content": description, - "location": location - } + id: `https://${domain}/${guid}`, + name: name, + type: "Event", + startTime: moment.tz(startUTC, timezone).format(), + endTime: moment.tz(endUTC, timezone).format(), + content: description, + location: location, + }; return JSON.stringify(eventObject); } -function createFeaturedPost(eventID, name, startUTC, endUTC, timezone, description, location) { +function createFeaturedPost( + eventID, + name, + startUTC, + endUTC, + timezone, + description, + location +) { const featured = { "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${eventID}/m/featuredPost`, - "type": "Note", - "name": "Test", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `

This is an event that was posted on ${siteName}. If you follow this account, you'll see updates in your timeline about the event. If your software supports polls, you should get a poll in your DMs asking if you want to RSVP. You can reply and RSVP right from there. If your software has an event calendar built in, you should get an event in your inbox that you can RSVP to like you respond to any event.

For more information on how to interact with this, check out this link.

`, - 'attributedTo': `https://${domain}/${eventID}`, - } + id: `https://${domain}/${eventID}/m/featuredPost`, + type: "Note", + name: "Test", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `

This is an event that was posted on ${siteName}. If you follow this account, you'll see updates in your timeline about the event. If your software supports polls, you should get a poll in your DMs asking if you want to RSVP. You can reply and RSVP right from there. If your software has an event calendar built in, you should get an event in your inbox that you can RSVP to like you respond to any event.

For more information on how to interact with this, check out this link.

`, + attributedTo: `https://${domain}/${eventID}`, + }; return featured; } -function updateActivityPubEvent(oldEvent, name, startUTC, endUTC, timezone, description, location) { +function updateActivityPubEvent( + oldEvent, + name, + startUTC, + endUTC, + timezone, + description, + location +) { // we want to persist the old ID no matter what happens to the Event itself const id = oldEvent.id; let eventObject = { "@context": "https://www.w3.org/ns/activitystreams", - 'id': id, - "name": name, - "type": "Event", - "startTime": moment.tz(startUTC, timezone).format(), - "endTime": moment.tz(endUTC, timezone).format(), - "content": description, - "location": location - } + id: id, + name: name, + type: "Event", + startTime: moment.tz(startUTC, timezone).format(), + endTime: moment.tz(endUTC, timezone).format(), + content: description, + location: location, + }; return JSON.stringify(eventObject); } - -function updateActivityPubActor(actor, description, name, location, imageFilename, startUTC, endUTC, timezone) { +function updateActivityPubActor( + actor, + description, + name, + location, + imageFilename, + startUTC, + endUTC, + timezone +) { if (!actor) return; actor.summary = `

${description}

`; actor.name = name; if (location) { - actor.summary += `

Location: ${location}.

` + actor.summary += `

Location: ${location}.

`; } let displayDate; if (startUTC && timezone) { - displayDate = moment.tz(startUTC, timezone).format('D MMMM YYYY h:mm a'); + displayDate = moment.tz(startUTC, timezone).format("D MMMM YYYY h:mm a"); actor.summary += `

Starting ${displayDate} ${timezone}.

`; } if (imageFilename) { actor.icon = { - 'type': 'Image', - 'mediaType': 'image/jpg', - 'url': `https://${domain}/events/${imageFilename}`, + type: "Image", + mediaType: "image/jpg", + url: `https://${domain}/events/${imageFilename}`, }; } return JSON.stringify(actor); @@ -125,77 +170,95 @@ function updateActivityPubActor(actor, description, name, location, imageFilenam function signAndSend(message, eventID, targetDomain, inbox, callback) { if (!isFederated) return; - let inboxFragment = inbox.replace('https://' + targetDomain, ''); + let inboxFragment = inbox.replace("https://" + targetDomain, ""); // get the private key Event.findOne({ - id: eventID - }) - .then((event) => { - if (event) { - const digest = crypto.createHash('sha256').update(JSON.stringify(message)).digest('base64'); - const privateKey = event.privateKey; - const signer = crypto.createSign('sha256'); - let d = new Date(); - let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}\ndigest: SHA-256=${digest}`; - signer.update(stringToSign); - signer.end(); - const signature = signer.sign(privateKey); - const signature_b64 = signature.toString('base64'); - const algorithm = 'rsa-sha256'; - let header = `keyId="https://${domain}/${eventID}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; - request({ + id: eventID, + }).then((event) => { + if (event) { + const digest = crypto + .createHash("sha256") + .update(JSON.stringify(message)) + .digest("base64"); + const privateKey = event.privateKey; + const signer = crypto.createSign("sha256"); + let d = new Date(); + let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}\ndigest: SHA-256=${digest}`; + signer.update(stringToSign); + signer.end(); + const signature = signer.sign(privateKey); + const signature_b64 = signature.toString("base64"); + const algorithm = "rsa-sha256"; + let header = `keyId="https://${domain}/${eventID}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; + request( + { url: inbox, headers: { - 'Host': targetDomain, - 'Date': d.toUTCString(), - 'Signature': header, - 'Digest': `SHA-256=${digest}`, - 'Content-Type': 'application/activity+json', - 'Accept': 'application/activity+json' + Host: targetDomain, + Date: d.toUTCString(), + Signature: header, + Digest: `SHA-256=${digest}`, + "Content-Type": "application/activity+json", + Accept: "application/activity+json", }, - method: 'POST', + method: "POST", json: true, - body: message - }, function (error, response) { + body: message, + }, + function (error, response) { if (error) { callback(error, null, 500); - } - else { + } else { // Add the message to the database const messageID = message.id; const newMessage = { id: message.id, - content: JSON.stringify(message) + content: JSON.stringify(message), }; - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - event.activityPubMessages.push(newMessage); - // also add the message's object if it has one - if (message.object && message.object.id) { - event.activityPubMessages.push({ - id: message.object.id, - content: JSON.stringify(message.object) - }); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (!event) return; + event.activityPubMessages.push(newMessage); + // also add the message's object if it has one + if (message.object && message.object.id) { + event.activityPubMessages.push({ + id: message.object.id, + content: JSON.stringify(message.object), + }); + } + event + .save() + .then(() => { + addToLog( + "addActivityPubMessage", + "success", + "ActivityPubMessage added to event " + eventID + ); + callback(null, message.id, 200); + }) + .catch((err) => { + addToLog( + "addActivityPubMessage", + "error", + "Attempt to add ActivityPubMessage to event " + + eventID + + " failed with error: " + + err + ); + callback(err, null, 500); + }); } - event.save() - .then(() => { - addToLog("addActivityPubMessage", "success", "ActivityPubMessage added to event " + eventID); - callback(null, message.id, 200); - }) - .catch((err) => { - addToLog("addActivityPubMessage", "error", "Attempt to add ActivityPubMessage to event " + eventID + " failed with error: " + err); - callback(err, null, 500); - }); - }) + ); } - }); - } - else { - callback(`No record found for ${eventID}.`, null, 404); - } - }); + } + ); + } else { + callback(`No record found for ${eventID}.`, null, 404); + } + }); } // this function sends something to the timeline of every follower in the followers array @@ -203,145 +266,182 @@ function signAndSend(message, eventID, targetDomain, inbox, callback) { // the profile but it doesn't spam federated timelines function broadcastCreateMessage(apObject, followers, eventID) { if (!isFederated) return; - let guidCreate = crypto.randomBytes(16).toString('hex'); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - // iterate over followers - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find(el => el.actorId === actorId); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], - 'id': `https://${domain}/${eventID}/m/${guidCreate}`, - 'type': 'Create', - 'actor': `https://${domain}/${eventID}`, - 'to': [actorId], - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - 'object': apObject - }; - signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - } - }); - } - else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } // end if event - else { - console.log(`No event found with the id ${eventID}`); + let guidCreate = crypto.randomBytes(16).toString("hex"); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (event) { + // iterate over followers + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find( + (el) => el.actorId === actorId + ); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + ], + id: `https://${domain}/${eventID}/m/${guidCreate}`, + type: "Create", + actor: `https://${domain}/${eventID}`, + to: [actorId], + cc: "https://www.w3.org/ns/activitystreams#Public", + object: apObject, + }; + signAndSend( + createMessage, + eventID, + targetDomain, + inbox, + function (err, resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}` + ); + } else { + console.log("sent to", actorId); + } + } + ); + } else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } // end if event + else { + console.log(`No event found with the id ${eventID}`); + } } - }); + ); } - // sends an Announce for the apObject function broadcastAnnounceMessage(apObject, followers, eventID) { if (!isFederated) return; - let guidUpdate = crypto.randomBytes(16).toString('hex'); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - // iterate over followers - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find(el => el.actorId === actorId); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const announceMessage = { - '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'], - 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - 'type': 'Announce', - 'actor': `https://${domain}/${eventID}`, - 'object': apObject, - 'to': actorId - }; - signAndSend(announceMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - } - }); - } - else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } // end if event - else { - console.log(`No event found with the id ${eventID}`); + let guidUpdate = crypto.randomBytes(16).toString("hex"); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (event) { + // iterate over followers + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find( + (el) => el.actorId === actorId + ); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const announceMessage = { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + ], + id: `https://${domain}/${eventID}/m/${guidUpdate}`, + cc: "https://www.w3.org/ns/activitystreams#Public", + type: "Announce", + actor: `https://${domain}/${eventID}`, + object: apObject, + to: actorId, + }; + signAndSend( + announceMessage, + eventID, + targetDomain, + inbox, + function (err, resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}` + ); + } else { + console.log("sent to", actorId); + } + } + ); + } else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } // end if event + else { + console.log(`No event found with the id ${eventID}`); + } } - }); + ); } // sends an Update for the apObject function broadcastUpdateMessage(apObject, followers, eventID) { if (!isFederated) return; - let guidUpdate = crypto.randomBytes(16).toString('hex'); + let guidUpdate = crypto.randomBytes(16).toString("hex"); // iterate over followers - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find(el => el.actorId === actorId); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, - 'type': 'Update', - 'actor': `https://${domain}/${eventID}`, - 'object': apObject - }; - signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - } - }); - } - else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } - else { - console.log(`No event found with the id ${eventID}`); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (event) { + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find( + (el) => el.actorId === actorId + ); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${eventID}/m/${guidUpdate}`, + type: "Update", + actor: `https://${domain}/${eventID}`, + object: apObject, + }; + signAndSend( + createMessage, + eventID, + targetDomain, + inbox, + function (err, resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}` + ); + } else { + console.log("sent to", actorId); + } + } + ); + } else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } else { + console.log(`No event found with the id ${eventID}`); + } } - }); + ); } function broadcastDeleteMessage(apObject, followers, eventID, callback) { - callback = callback || function () { }; + callback = callback || function () {}; if (!isFederated) { callback([]); return; @@ -350,54 +450,72 @@ function broadcastDeleteMessage(apObject, followers, eventID, callback) { // per spec, each promise will execute *as it is built*, which is fine, we just need the guarantee that they are all done let promises = []; - let guidUpdate = crypto.randomBytes(16).toString('hex'); + let guidUpdate = crypto.randomBytes(16).toString("hex"); // iterate over followers for (const follower of followers) { - promises.push(new Promise((resolve, reject) => { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - const follower = event.followers.find(el => el.actorId === actorId); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${eventID}/m/${guidUpdate}`, - 'type': 'Delete', - 'actor': `https://${domain}/${eventID}`, - 'object': apObject - }; - signAndSend(createMessage, eventID, targetDomain, inbox, function (err, resp, status) { - if (err) { - console.log(`Didn't send to ${actorId}, status ${status} with error ${err}`); - reject(`Didn't send to ${actorId}, status ${status} with error ${err}`); - } - else { - console.log('sent to', actorId); - resolve('sent to', actorId); + promises.push( + new Promise((resolve, reject) => { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (event) { + const follower = event.followers.find( + (el) => el.actorId === actorId + ); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${eventID}/m/${guidUpdate}`, + type: "Delete", + actor: `https://${domain}/${eventID}`, + object: apObject, + }; + signAndSend( + createMessage, + eventID, + targetDomain, + inbox, + function (err, resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}` + ); + reject( + `Didn't send to ${actorId}, status ${status} with error ${err}` + ); + } else { + console.log("sent to", actorId); + resolve("sent to", actorId); + } + } + ); + } else { + console.log( + `No follower found with the id ${actorId}`, + null, + 404 + ); + reject(`No follower found with the id ${actorId}`, null, 404); } - }); - } - else { - console.log(`No follower found with the id ${actorId}`, null, 404); - reject(`No follower found with the id ${actorId}`, null, 404); + } else { + console.log(`No event found with the id ${eventID}`, null, 404); + reject(`No event found with the id ${eventID}`, null, 404); + } } - } - else { - console.log(`No event found with the id ${eventID}`, null, 404); - reject(`No event found with the id ${eventID}`, null, 404); - } - }); // end event - })); + ); // end event + }) + ); } // end followers - Promise.all(promises.map(p => p.catch(e => e))).then(statuses => { + Promise.all(promises.map((p) => p.catch((e) => e))).then((statuses) => { callback(statuses); }); } @@ -405,469 +523,664 @@ function broadcastDeleteMessage(apObject, followers, eventID, callback) { // this sends a message "to:" an individual fediverse user function sendDirectMessage(apObject, actorId, eventID, callback) { if (!isFederated) return; - callback = callback || function () { }; - const guidCreate = crypto.randomBytes(16).toString('hex'); - const guidObject = crypto.randomBytes(16).toString('hex'); + callback = callback || function () {}; + const guidCreate = crypto.randomBytes(16).toString("hex"); + const guidObject = crypto.randomBytes(16).toString("hex"); let d = new Date(); apObject.published = d.toISOString(); apObject.attributedTo = `https://${domain}/${eventID}`; apObject.to = actorId; apObject.id = `https://${domain}/${eventID}/m/${guidObject}`; - apObject.content = unescape(apObject.content) + apObject.content = unescape(apObject.content); let createMessage = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${eventID}/m/${guidCreate}`, - 'type': 'Create', - 'actor': `https://${domain}/${eventID}`, - 'to': [actorId], - 'object': apObject + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${eventID}/m/${guidCreate}`, + type: "Create", + actor: `https://${domain}/${eventID}`, + to: [actorId], + object: apObject, }; let myURL = new URL(actorId); let targetDomain = myURL.hostname; // get the inbox - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - const follower = event.followers.find(el => el.actorId === actorId); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - signAndSend(createMessage, eventID, targetDomain, inbox, callback); - } - else { - callback(`No follower found with the id ${actorId}`, null, 404); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (event) { + const follower = event.followers.find((el) => el.actorId === actorId); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + signAndSend(createMessage, eventID, targetDomain, inbox, callback); + } else { + callback(`No follower found with the id ${actorId}`, null, 404); + } + } else { + callback(`No event found with the id ${eventID}`, null, 404); } } - else { - callback(`No event found with the id ${eventID}`, null, 404); - } - }); + ); } function sendAcceptMessage(thebody, eventID, targetDomain, callback) { if (!isFederated) return; - callback = callback || function () { }; - const guid = crypto.randomBytes(16).toString('hex'); + callback = callback || function () {}; + const guid = crypto.randomBytes(16).toString("hex"); const actorId = thebody.actor; let message = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `https://${domain}/${guid}`, - 'type': 'Accept', - 'actor': `https://${domain}/${eventID}`, - 'object': thebody, + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${guid}`, + type: "Accept", + actor: `https://${domain}/${eventID}`, + object: thebody, }; // get the inbox - Event.findOne({ - id: eventID, - }, function (err, event) { - if (event) { - const follower = event.followers.find(el => el.actorId === actorId); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - signAndSend(message, eventID, targetDomain, inbox, callback); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (event) { + const follower = event.followers.find((el) => el.actorId === actorId); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + signAndSend(message, eventID, targetDomain, inbox, callback); + } + } else { + callback(`Could not find event ${eventID}`, null, 404); } } - else { - callback(`Could not find event ${eventID}`, null, 404); - } - }); + ); } function _handleFollow(req, res) { const myURL = new URL(req.body.actor); let targetDomain = myURL.hostname; - let eventID = req.body.object.replace(`https://${domain}/`, ''); + let eventID = req.body.object.replace(`https://${domain}/`, ""); // Add the user to the DB of accounts that follow the account // get the follower's username - request({ - url: req.body.actor, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, body) { - body = JSON.parse(body) - const name = body.preferredUsername || body.name || body.attributedTo; - const newFollower = { - actorId: req.body.actor, - followId: req.body.id, - name: name, - actorJson: JSON.stringify(body) - }; - Event.findOne({ - id: eventID, - }, function (err, event) { - // if this account is NOT already in our followers list, add it - if (event && !event.followers.map(el => el.actorId).includes(req.body.actor)) { - event.followers.push(newFollower); - event.save() - .then(() => { - addToLog("addEventFollower", "success", "Follower added to event " + eventID); - // Accept the follow request - sendAcceptMessage(req.body, eventID, targetDomain, function (err, resp, status) { - if (err) { - console.log(`Didn't send Accept to ${req.body.actor}, status ${status} with error ${err}`); - } - else { - console.log('sent Accept to', req.body.actor); - // ALSO send an ActivityPub Event activity since this person is "interested" in the event, as indicated by the Follow - const jsonEventObject = JSON.parse(event.activityPubEvent); - // send direct message to user - sendDirectMessage(jsonEventObject, newFollower.actorId, event.id); - - // if users can self-RSVP, send a Question to the new follower - if (event.usersCanAttend) { - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Question", - "content": `@${name} Will you attend ${event.name}? (If you reply "Yes", you'll be listed as an attendee on the event page.)`, - "oneOf": [ - { "type": "Note", "name": "Yes" }, - ], - "endTime": event.start.toISOString(), - "tag": [{ "type": "Mention", "href": req.body.actor, "name": name }] - } - // send direct message to user - sendDirectMessage(jsonObject, req.body.actor, eventID, function (error, response, statuscode) { - if (error) { - console.log('Error sending direct message:', error); - return res.status(statuscode).json(error); - } - else { - return res.status(statuscode).json({ messageid: response }); + request( + { + url: req.body.actor, + headers: { + Accept: "application/activity+json", + "Content-Type": "application/activity+json", + }, + }, + function (error, response, body) { + body = JSON.parse(body); + const name = body.preferredUsername || body.name || body.attributedTo; + const newFollower = { + actorId: req.body.actor, + followId: req.body.id, + name: name, + actorJson: JSON.stringify(body), + }; + Event.findOne( + { + id: eventID, + }, + function (err, event) { + // if this account is NOT already in our followers list, add it + if ( + event && + !event.followers.map((el) => el.actorId).includes(req.body.actor) + ) { + event.followers.push(newFollower); + event + .save() + .then(() => { + addToLog( + "addEventFollower", + "success", + "Follower added to event " + eventID + ); + // Accept the follow request + sendAcceptMessage( + req.body, + eventID, + targetDomain, + function (err, resp, status) { + if (err) { + console.log( + `Didn't send Accept to ${req.body.actor}, status ${status} with error ${err}` + ); + } else { + console.log("sent Accept to", req.body.actor); + // ALSO send an ActivityPub Event activity since this person is "interested" in the event, as indicated by the Follow + const jsonEventObject = JSON.parse( + event.activityPubEvent + ); + // send direct message to user + sendDirectMessage( + jsonEventObject, + newFollower.actorId, + event.id + ); + + // if users can self-RSVP, send a Question to the new follower + if (event.usersCanAttend) { + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + name: `RSVP to ${event.name}`, + type: "Question", + content: `@${name} Will you attend ${event.name}? (If you reply "Yes", you'll be listed as an attendee on the event page.)`, + oneOf: [{ type: "Note", name: "Yes" }], + endTime: event.start.toISOString(), + tag: [ + { + type: "Mention", + href: req.body.actor, + name: name, + }, + ], + }; + // send direct message to user + sendDirectMessage( + jsonObject, + req.body.actor, + eventID, + function (error, response, statuscode) { + if (error) { + console.log( + "Error sending direct message:", + error + ); + return res.status(statuscode).json(error); + } else { + return res + .status(statuscode) + .json({ messageid: response }); + } + } + ); + } } - }); - } - } - }); - }) - .catch((err) => { - addToLog("addEventFollower", "error", "Attempt to add follower to event " + eventID + " failed with error: " + err); - return res.status(500).send('Database error, please try again :('); - }); - } - else { - // this person is already a follower so just say "ok" - return res.status(200); - } - }) - }) //end request + } + ); + }) + .catch((err) => { + addToLog( + "addEventFollower", + "error", + "Attempt to add follower to event " + + eventID + + " failed with error: " + + err + ); + return res + .status(500) + .send("Database error, please try again :("); + }); + } else { + // this person is already a follower so just say "ok" + return res.status(200); + } + } + ); + } + ); //end request } function _handleUndoFollow(req, res) { // get the record of all followers for this account - const eventID = req.body.object.object.replace(`https://${domain}/`, ''); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // check to see if the Follow object's id matches the id we have on record - // is this even someone who follows us - const indexOfFollower = event.followers.findIndex(el => el.actorId === req.body.object.actor); - if (indexOfFollower !== -1) { - // does the id we have match the id we are being given - if (event.followers[indexOfFollower].followId === req.body.object.id) { - // we have a match and can trust the Undo! remove this person from the followers list - event.followers.splice(indexOfFollower, 1); - event.save() - .then(() => { - addToLog("removeEventFollower", "success", "Follower removed from event " + eventID); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("removeEventFollower", "error", "Attempt to remove follower from event " + eventID + " failed with error: " + err); - return res.send('Database error, please try again :('); - }); - } - } - }); -} - -function _handleAcceptEvent(req, res) { - let { name, attributedTo, inReplyTo, to, actor } = req.body; - if (Array.isArray(to)) { - to = to[0]; - } - const eventID = to.replace(`https://${domain}/`, ''); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // does the id we got match the id of a thing we sent out - const message = event.activityPubMessages.find(el => el.id === req.body.object); - if (message) { - // it's a match - request({ - url: actor, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, body) { - body = JSON.parse(body) - // if this account is NOT already in our attendees list, add it - if (!event.attendees.map(el => el.id).includes(actor)) { - const attendeeName = body.preferredUsername || body.name || actor; - const newAttendee = { - name: attendeeName, - status: 'attending', - id: actor, - number: 1, - }; - event.attendees.push(newAttendee); - event.save() - .then((fullEvent) => { - addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); - // get the new attendee with its hidden id from the full event - let fullAttendee = fullEvent.attendees.find(el => el.id === actor); - // send a "click here to remove yourself" link back to the user as a DM - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Note", - "content": `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, - "tag": [{ "type": "Mention", "href": newAttendee.id, "name": newAttendee.name }] - } - // send direct message to user - sendDirectMessage(jsonObject, newAttendee.id, event.id); + const eventID = req.body.object.object.replace(`https://${domain}/`, ""); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (!event) return; + // check to see if the Follow object's id matches the id we have on record + // is this even someone who follows us + const indexOfFollower = event.followers.findIndex( + (el) => el.actorId === req.body.object.actor + ); + if (indexOfFollower !== -1) { + // does the id we have match the id we are being given + if (event.followers[indexOfFollower].followId === req.body.object.id) { + // we have a match and can trust the Undo! remove this person from the followers list + event.followers.splice(indexOfFollower, 1); + event + .save() + .then(() => { + addToLog( + "removeEventFollower", + "success", + "Follower removed from event " + eventID + ); return res.sendStatus(200); }) .catch((err) => { - addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + err); - return res.status(500).send('Database error, please try again :('); + addToLog( + "removeEventFollower", + "error", + "Attempt to remove follower from event " + + eventID + + " failed with error: " + + err + ); + return res.send("Database error, please try again :("); }); } - else { - // it's a duplicate and this person is already rsvped so just say OK - return res.status(200).send("Attendee is already registered."); - } - }); + } } - }); + ); } -function _handleUndoAcceptEvent(req, res) { +function _handleAcceptEvent(req, res) { let { name, attributedTo, inReplyTo, to, actor } = req.body; if (Array.isArray(to)) { to = to[0]; } - const eventID = to.replace(`https://${domain}/`, ''); - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // does the id we got match the id of a thing we sent out - const message = event.activityPubMessages.find(el => el.id === req.body.object.object); - if (message) { - // it's a match - Event.update( - { id: eventID }, - { $pull: { attendees: { id: actor } } } - ) - .then(response => { - addToLog("oneClickUnattend", "success", "Attendee removed via one click unattend " + req.params.eventID); - }); - } - }); -} - -function _handleCreateNote(req, res) { - // figure out what this is in reply to -- it should be addressed specifically to us - let { name, attributedTo, inReplyTo, to } = req.body.object; - // if it's an array just grab the first element, since a poll should only broadcast back to the pollster - if (Array.isArray(to)) { - to = to[0]; - } - const eventID = to.replace(`https://${domain}/`, ''); - // make sure this person is actually a follower - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) return; - // is this even someone who follows us - const indexOfFollower = event.followers.findIndex(el => el.actorId === req.body.object.attributedTo); - if (indexOfFollower !== -1) { - // compare the inReplyTo to its stored message, if it exists and it's going to the right follower then this is a valid reply - const message = event.activityPubMessages.find(el => { - const content = JSON.parse(el.content); - return inReplyTo === (content.object && content.object.id); - }); + const eventID = to.replace(`https://${domain}/`, ""); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (!event) return; + // does the id we got match the id of a thing we sent out + const message = event.activityPubMessages.find( + (el) => el.id === req.body.object + ); if (message) { - const content = JSON.parse(message.content); - // check if the message we sent out was sent to the actor this incoming message is attributedTo - if (content.to[0] === attributedTo) { - // it's a match, this is a valid poll response, add RSVP to database - // fetch the profile information of the user - request({ - url: attributedTo, + // it's a match + request( + { + url: actor, headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, body) { - body = JSON.parse(body) + Accept: "application/activity+json", + "Content-Type": "application/activity+json", + }, + }, + function (error, response, body) { + body = JSON.parse(body); // if this account is NOT already in our attendees list, add it - if (!event.attendees.map(el => el.id).includes(attributedTo)) { - const attendeeName = body.preferredUsername || body.name || attributedTo; + if (!event.attendees.map((el) => el.id).includes(actor)) { + const attendeeName = body.preferredUsername || body.name || actor; const newAttendee = { name: attendeeName, - status: 'attending', - id: attributedTo, + status: "attending", + id: actor, number: 1, }; event.attendees.push(newAttendee); - event.save() + event + .save() .then((fullEvent) => { - addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); + addToLog( + "addEventAttendee", + "success", + "Attendee added to event " + req.params.eventID + ); // get the new attendee with its hidden id from the full event - let fullAttendee = fullEvent.attendees.find(el => el.id === attributedTo); + let fullAttendee = fullEvent.attendees.find( + (el) => el.id === actor + ); // send a "click here to remove yourself" link back to the user as a DM const jsonObject = { "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Note", - "content": `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, - "tag": [{ "type": "Mention", "href": newAttendee.id, "name": newAttendee.name }] - } + name: `RSVP to ${event.name}`, + type: "Note", + content: `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, + tag: [ + { + type: "Mention", + href: newAttendee.id, + name: newAttendee.name, + }, + ], + }; // send direct message to user sendDirectMessage(jsonObject, newAttendee.id, event.id); return res.sendStatus(200); }) .catch((err) => { - addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + err); - return res.status(500).send('Database error, please try again :('); + addToLog( + "addEventAttendee", + "error", + "Attempt to add attendee to event " + + req.params.eventID + + " failed with error: " + + err + ); + return res + .status(500) + .send("Database error, please try again :("); }); - } - else { + } else { // it's a duplicate and this person is already rsvped so just say OK return res.status(200).send("Attendee is already registered."); } - }); + } + ); + } + } + ); +} + +function _handleUndoAcceptEvent(req, res) { + let { name, attributedTo, inReplyTo, to, actor } = req.body; + if (Array.isArray(to)) { + to = to[0]; + } + const eventID = to.replace(`https://${domain}/`, ""); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (!event) return; + // does the id we got match the id of a thing we sent out + const message = event.activityPubMessages.find( + (el) => el.id === req.body.object.object + ); + if (message) { + // it's a match + Event.update( + { id: eventID }, + { $pull: { attendees: { id: actor } } } + ).then((response) => { + addToLog( + "oneClickUnattend", + "success", + "Attendee removed via one click unattend " + req.params.eventID + ); + }); + } + } + ); +} + +function _handleCreateNote(req, res) { + // figure out what this is in reply to -- it should be addressed specifically to us + let { name, attributedTo, inReplyTo, to } = req.body.object; + // if it's an array just grab the first element, since a poll should only broadcast back to the pollster + if (Array.isArray(to)) { + to = to[0]; + } + const eventID = to.replace(`https://${domain}/`, ""); + // make sure this person is actually a follower + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (!event) return; + // is this even someone who follows us + const indexOfFollower = event.followers.findIndex( + (el) => el.actorId === req.body.object.attributedTo + ); + if (indexOfFollower !== -1) { + // compare the inReplyTo to its stored message, if it exists and it's going to the right follower then this is a valid reply + const message = event.activityPubMessages.find((el) => { + const content = JSON.parse(el.content); + return inReplyTo === (content.object && content.object.id); + }); + if (message) { + const content = JSON.parse(message.content); + // check if the message we sent out was sent to the actor this incoming message is attributedTo + if (content.to[0] === attributedTo) { + // it's a match, this is a valid poll response, add RSVP to database + // fetch the profile information of the user + request( + { + url: attributedTo, + headers: { + Accept: "application/activity+json", + "Content-Type": "application/activity+json", + }, + }, + function (error, response, body) { + body = JSON.parse(body); + // if this account is NOT already in our attendees list, add it + if ( + !event.attendees.map((el) => el.id).includes(attributedTo) + ) { + const attendeeName = + body.preferredUsername || body.name || attributedTo; + const newAttendee = { + name: attendeeName, + status: "attending", + id: attributedTo, + number: 1, + }; + event.attendees.push(newAttendee); + event + .save() + .then((fullEvent) => { + addToLog( + "addEventAttendee", + "success", + "Attendee added to event " + req.params.eventID + ); + // get the new attendee with its hidden id from the full event + let fullAttendee = fullEvent.attendees.find( + (el) => el.id === attributedTo + ); + // send a "click here to remove yourself" link back to the user as a DM + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + name: `RSVP to ${event.name}`, + type: "Note", + content: `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, + tag: [ + { + type: "Mention", + href: newAttendee.id, + name: newAttendee.name, + }, + ], + }; + // send direct message to user + sendDirectMessage(jsonObject, newAttendee.id, event.id); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog( + "addEventAttendee", + "error", + "Attempt to add attendee to event " + + req.params.eventID + + " failed with error: " + + err + ); + return res + .status(500) + .send("Database error, please try again :("); + }); + } else { + // it's a duplicate and this person is already rsvped so just say OK + return res + .status(200) + .send("Attendee is already registered."); + } + } + ); + } } } } - }); + ); } function _handleDelete(req, res) { const deleteObjectId = req.body.object.id; // find all events with comments from the author - Event.find({ - "comments.actorId": req.body.actor - }, function (err, events) { - if (!events) { - return res.sendStatus(404); - } + Event.find( + { + "comments.actorId": req.body.actor, + }, + function (err, events) { + if (!events) { + return res.sendStatus(404); + } - // find the event with THIS comment from the author - let eventWithComment = events.find(event => { - let comments = event.comments; - return comments.find(comment => { - if (!comment.activityJson) { - return false; - } - return JSON.parse(comment.activityJson).object.id === req.body.object.id; - }) - }); + // find the event with THIS comment from the author + let eventWithComment = events.find((event) => { + let comments = event.comments; + return comments.find((comment) => { + if (!comment.activityJson) { + return false; + } + return ( + JSON.parse(comment.activityJson).object.id === req.body.object.id + ); + }); + }); - if (!eventWithComment) { - return res.sendStatus(404); - } + if (!eventWithComment) { + return res.sendStatus(404); + } - // delete the comment - // find the index of the comment, it should have an activityJson field because from an AP server you can only delete an AP-originated comment (and of course it needs to be yours) - let indexOfComment = eventWithComment.comments.findIndex(comment => { - return comment.activityJson && JSON.parse(comment.activityJson).object.id === req.body.object.id; - }); - eventWithComment.comments.splice(indexOfComment, 1); - eventWithComment.save() - .then(() => { - addToLog("deleteComment", "success", "Comment deleted from event " + eventWithComment.id); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("deleteComment", "error", "Attempt to delete comment " + req.body.object.id + "from event " + eventWithComment.id + " failed with error: " + err); - return res.sendStatus(500); + // delete the comment + // find the index of the comment, it should have an activityJson field because from an AP server you can only delete an AP-originated comment (and of course it needs to be yours) + let indexOfComment = eventWithComment.comments.findIndex((comment) => { + return ( + comment.activityJson && + JSON.parse(comment.activityJson).object.id === req.body.object.id + ); }); - }); + eventWithComment.comments.splice(indexOfComment, 1); + eventWithComment + .save() + .then(() => { + addToLog( + "deleteComment", + "success", + "Comment deleted from event " + eventWithComment.id + ); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.body.object.id + + "from event " + + eventWithComment.id + + " failed with error: " + + err + ); + return res.sendStatus(500); + }); + } + ); } function _handleCreateNoteComment(req, res) { // figure out what this is in reply to -- it should be addressed specifically to us let { attributedTo, inReplyTo, to, cc } = req.body.object; // normalize cc into an array - if (typeof cc === 'string') { + if (typeof cc === "string") { cc = [cc]; } // normalize to into an array - if (typeof to === 'string') { + if (typeof to === "string") { to = [to]; } // if this is a public message (in the to or cc fields) - if (to.includes('https://www.w3.org/ns/activitystreams#Public') || (Array.isArray(cc) && cc.includes('https://www.w3.org/ns/activitystreams#Public'))) { + if ( + to.includes("https://www.w3.org/ns/activitystreams#Public") || + (Array.isArray(cc) && + cc.includes("https://www.w3.org/ns/activitystreams#Public")) + ) { // figure out which event(s) of ours it was addressing - let ourEvents = cc.filter(el => el.includes(`https://${domain}/`)) - .map(el => el.replace(`https://${domain}/`, '')); - // comments should only be on one event. if more than one, ignore (spam, probably) + let ourEvents = cc + .filter((el) => el.includes(`https://${domain}/`)) + .map((el) => el.replace(`https://${domain}/`, "")); + // comments should only be on one event. if more than one, ignore (spam, probably) if (ourEvents.length === 1) { let eventID = ourEvents[0]; // add comment let commentID = nanoid(); // get the actor for the commenter - request({ - url: req.body.actor, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, actor) { - if (!error) { - const parsedActor = JSON.parse(actor); - const name = parsedActor.preferredUsername || parsedActor.name || req.body.actor; - const newComment = { - id: commentID, - actorId: req.body.actor, - activityId: req.body.object.id, - author: name, - content: sanitizeHtml(req.body.object.content, { allowedTags: [], allowedAttributes: {} }).replace('@' + eventID, ''), - timestamp: moment(), - activityJson: JSON.stringify(req.body), - actorJson: actor - }; - - Event.findOne({ - id: eventID, - }, function (err, event) { - if (!event) { - return res.sendStatus(404); - } - if (!event.usersCanComment) { - return res.sendStatus(200); - } - event.comments.push(newComment); - event.save() - .then(() => { - addToLog("addEventComment", "success", "Comment added to event " + eventID); - const guidObject = crypto.randomBytes(16).toString('hex'); - const jsonObject = req.body.object; - jsonObject.attributedTo = newComment.actorId; - broadcastAnnounceMessage(jsonObject, event.followers, eventID) - return res.sendStatus(200); - }) - .catch((err) => { - addToLog("addEventComment", "error", "Attempt to add comment to event " + eventID + " failed with error: " + err); - res.status(500).send('Database error, please try again :(' + err); - }); - }); + request( + { + url: req.body.actor, + headers: { + Accept: "application/activity+json", + "Content-Type": "application/activity+json", + }, + }, + function (error, response, actor) { + if (!error) { + const parsedActor = JSON.parse(actor); + const name = + parsedActor.preferredUsername || + parsedActor.name || + req.body.actor; + const newComment = { + id: commentID, + actorId: req.body.actor, + activityId: req.body.object.id, + author: name, + content: sanitizeHtml(req.body.object.content, { + allowedTags: [], + allowedAttributes: {}, + }).replace("@" + eventID, ""), + timestamp: moment(), + activityJson: JSON.stringify(req.body), + actorJson: actor, + }; + + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (!event) { + return res.sendStatus(404); + } + if (!event.usersCanComment) { + return res.sendStatus(200); + } + event.comments.push(newComment); + event + .save() + .then(() => { + addToLog( + "addEventComment", + "success", + "Comment added to event " + eventID + ); + const guidObject = crypto.randomBytes(16).toString("hex"); + const jsonObject = req.body.object; + jsonObject.attributedTo = newComment.actorId; + broadcastAnnounceMessage( + jsonObject, + event.followers, + eventID + ); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog( + "addEventComment", + "error", + "Attempt to add comment to event " + + eventID + + " failed with error: " + + err + ); + res + .status(500) + .send("Database error, please try again :(" + err); + }); + } + ); + } } - }); + ); } // end ourevent } // end public message } @@ -876,50 +1189,79 @@ function processInbox(req, res) { if (!isFederated) return res.sendStatus(404); try { // if a Follow activity hits the inbox - if (typeof req.body.object === 'string' && req.body.type === 'Follow') { + if (typeof req.body.object === "string" && req.body.type === "Follow") { _handleFollow(req, res); } // if an Undo activity with a Follow object hits the inbox - if (req.body && req.body.type === 'Undo' && req.body.object && req.body.object.type === 'Follow') { + if ( + req.body && + req.body.type === "Undo" && + req.body.object && + req.body.object.type === "Follow" + ) { _handleUndoFollow(req, res); } // if an Accept activity with the id of the Event we sent out hits the inbox, it is an affirmative RSVP - if (req.body && req.body.type === 'Accept' && req.body.object && typeof req.body.object === 'string') { + if ( + req.body && + req.body.type === "Accept" && + req.body.object && + typeof req.body.object === "string" + ) { _handleAcceptEvent(req, res); } // if an Undo activity containing an Accept containing the id of the Event we sent out hits the inbox, it is an undo RSVP - if (req.body && req.body.type === 'Undo' && req.body.object && req.body.object.object && typeof req.body.object.object === 'string' && req.body.object.type === 'Accept') { + if ( + req.body && + req.body.type === "Undo" && + req.body.object && + req.body.object.object && + typeof req.body.object.object === "string" && + req.body.object.type === "Accept" + ) { _handleUndoAcceptEvent(req, res); } // if a Create activity with a Note object hits the inbox, and it's a reply, it might be a vote in a poll - if (req.body && req.body.type === 'Create' && req.body.object && req.body.object.type === 'Note' && req.body.object.inReplyTo && req.body.object.to) { + if ( + req.body && + req.body.type === "Create" && + req.body.object && + req.body.object.type === "Note" && + req.body.object.inReplyTo && + req.body.object.to + ) { _handleCreateNote(req, res); } // if a Delete activity hits the inbox, it might a deletion of a comment - if (req.body && req.body.type === 'Delete') { + if (req.body && req.body.type === "Delete") { _handleDelete(req, res); } // if we are CC'ed on a public or unlisted Create/Note, then this is a comment to us we should boost (Announce) to our followers - if (req.body && req.body.type === 'Create' && req.body.object && req.body.object.type === 'Note' && req.body.object.to) { + if ( + req.body && + req.body.type === "Create" && + req.body.object && + req.body.object.type === "Note" && + req.body.object.to + ) { _handleCreateNoteComment(req, res); } // CC'ed - } - catch (e) { - console.log('Error in processing inbox:', e) + } catch (e) { + console.log("Error in processing inbox:", e); } } function createWebfinger(eventID, domain) { return { - 'subject': `acct:${eventID}@${domain}`, + subject: `acct:${eventID}@${domain}`, - 'links': [ + links: [ { - 'rel': 'self', - 'type': 'application/activity+json', - 'href': `https://${domain}/${eventID}` - } - ] + rel: "self", + type: "application/activity+json", + href: `https://${domain}/${eventID}`, + }, + ], }; } @@ -938,4 +1280,4 @@ module.exports = { updateActivityPubEvent, createFeaturedPost, createWebfinger, -} +}; diff --git a/src/app.js b/src/app.js index cc50593..4a2137e 100755 --- a/src/app.js +++ b/src/app.js @@ -1,10 +1,10 @@ -const express = require('express'); -const path = require('path'); -const session = require('express-session'); -const cors = require('cors'); -const routes = require('./routes'); -const hbs = require('express-handlebars'); -const bodyParser = require('body-parser'); +const express = require("express"); +const path = require("path"); +const session = require("express-session"); +const cors = require("cors"); +const routes = require("./routes"); +const hbs = require("express-handlebars"); +const bodyParser = require("body-parser"); const app = express(); @@ -14,41 +14,40 @@ const app = express(); //app.use(bodyParser.json()); //app.use(session({ secret: 'slartibartfast', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); - // View engine // const hbsInstance = hbs.create({ - defaultLayout: 'main', - partialsDir: ['views/partials/'], - layoutsDir: 'views/layouts/', - helpers: { - plural: function(number, text) { - var singular = number === 1; - // If no text parameter was given, just return a conditional s. - if (typeof text !== 'string') return singular ? '' : 's'; - // Split with regex into group1/group2 or group1(group3) - var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); - // If no match, just append a conditional s. - if (!match) return text + (singular ? '' : 's'); - // We have a good match, so fire away - return singular && match[1] // Singular case - || - match[2] // Plural case: 'bagel/bagels' --> bagels - || - match[1] + (match[3] || 's'); // Plural case: 'bagel(s)' or 'bagel' --> bagels - } - } + defaultLayout: "main", + partialsDir: ["views/partials/"], + layoutsDir: "views/layouts/", + helpers: { + plural: function (number, text) { + var singular = number === 1; + // If no text parameter was given, just return a conditional s. + if (typeof text !== "string") return singular ? "" : "s"; + // Split with regex into group1/group2 or group1(group3) + var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); + // If no match, just append a conditional s. + if (!match) return text + (singular ? "" : "s"); + // We have a good match, so fire away + return ( + (singular && match[1]) || // Singular case + match[2] || // Plural case: 'bagel/bagels' --> bagels + match[1] + (match[3] || "s") + ); // Plural case: 'bagel(s)' or 'bagel' --> bagels + }, + }, }); -app.engine('handlebars', hbsInstance.engine); -app.set('view engine', 'handlebars'); -app.set('hbsInstance', hbsInstance); +app.engine("handlebars", hbsInstance.engine); +app.set("view engine", "handlebars"); +app.set("hbsInstance", hbsInstance); // Static files // -app.use(express.static('public')); +app.use(express.static("public")); // Router // app.use(bodyParser.json({ type: "application/activity+json" })); // support json encoded bodies app.use(bodyParser.urlencoded({ extended: true })); -app.use('/', routes); +app.use("/", routes); module.exports = app; diff --git a/src/config/api-example.js b/src/config/api-example.js index 9202f0a..493b9d6 100644 --- a/src/config/api-example.js +++ b/src/config/api-example.js @@ -1,8 +1,8 @@ // Which of these fields are used depends on the 'mailService' config entry in config/domain.js module.exports = { - 'sendgrid' : '', // If using SendGrid, the Sendgrid API key goes here - 'smtpServer': '', // If using Nodemailer, your SMTP server hostname goes here - 'smtpPort': '', // If using Nodemailer, your SMTP server port goes here - 'smtpUsername': '', // If using Nodemailer, your SMTP server username goes here - 'smtpPassword': '' // If using Nodemailer, your SMTP password goes here + sendgrid: "", // If using SendGrid, the Sendgrid API key goes here + smtpServer: "", // If using Nodemailer, your SMTP server hostname goes here + smtpPort: "", // If using Nodemailer, your SMTP server port goes here + smtpUsername: "", // If using Nodemailer, your SMTP server username goes here + smtpPassword: "", // If using Nodemailer, your SMTP password goes here }; diff --git a/src/config/database-docker.js b/src/config/database-docker.js index 7847097..96c987d 100644 --- a/src/config/database-docker.js +++ b/src/config/database-docker.js @@ -1,3 +1,3 @@ module.exports = { - 'url' : 'mongodb://mongo:27017/gathio' // For dockerised MongoDB connection + url: "mongodb://mongo:27017/gathio", // For dockerised MongoDB connection }; diff --git a/src/config/database-example.js b/src/config/database-example.js index 4aa4c4d..ca7bdcc 100644 --- a/src/config/database-example.js +++ b/src/config/database-example.js @@ -1,3 +1,3 @@ module.exports = { - 'url' : 'mongodb://localhost:27017/gathio' // For local MongoDB connection + url: "mongodb://localhost:27017/gathio", // For local MongoDB connection }; diff --git a/src/config/domain-example.js b/src/config/domain-example.js index 19c797a..abac094 100644 --- a/src/config/domain-example.js +++ b/src/config/domain-example.js @@ -1,13 +1,13 @@ module.exports = { - // Your domain goes here. If there is a port it should be 'domain:port', but otherwise just 'domain' - 'domain' : 'localhost:3000' , - 'port': '3000', - 'email': 'contact@example.com', - 'mailService': 'nodemailer', // Which mail service to use to send emails to attendees. Options are 'nodemailer' or 'sendgrid'. Configure settings for the mail service in config/api.js.z - 'sitename': 'gathio', - 'isFederated': true, - // If left blank, this defaults to https://yourdomain.com/images/gathio-email-logo.gif. Set a full URL here to change it to your own logo (or just change the file itself) - 'logo_url': '', - // Show a Ko-Fi box to donate money to Raphael Kabo (Gathio's creator) on the front page - 'showKofi': false, + // Your domain goes here. If there is a port it should be 'domain:port', but otherwise just 'domain' + domain: "localhost:3000", + port: "3000", + email: "contact@example.com", + mailService: "nodemailer", // Which mail service to use to send emails to attendees. Options are 'nodemailer' or 'sendgrid'. Configure settings for the mail service in config/api.js.z + sitename: "gathio", + isFederated: true, + // If left blank, this defaults to https://yourdomain.com/images/gathio-email-logo.gif. Set a full URL here to change it to your own logo (or just change the file itself) + logo_url: "", + // Show a Ko-Fi box to donate money to Raphael Kabo (Gathio's creator) on the front page + showKofi: false, }; diff --git a/src/helpers.js b/src/helpers.js index bf95e27..9b7559f 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,10 +1,10 @@ -const domain = require('./config/domain.js').domain; -const siteName = require('./config/domain.js').sitename; +const domain = require("./config/domain.js").domain; +const siteName = require("./config/domain.js").sitename; -const mongoose = require('mongoose'); -const Log = mongoose.model('Log'); -var moment = require('moment-timezone'); -const icalGenerator = require('ical-generator'); +const mongoose = require("mongoose"); +const Log = mongoose.model("Log"); +var moment = require("moment-timezone"); +const icalGenerator = require("ical-generator"); // LOGGING @@ -13,9 +13,11 @@ function addToLog(process, status, message) { status: status, process: process, message: message, - timestamp: moment() + timestamp: moment(), + }); + logEntry.save().catch(() => { + console.log("Error saving log entry!"); }); - logEntry.save().catch(() => { console.log("Error saving log entry!") }); } function exportIcal(events, calendarName) { @@ -23,13 +25,13 @@ function exportIcal(events, calendarName) { const cal = icalGenerator({ name: calendarName || siteName, x: { - 'X-WR-CALNAME': calendarName || siteName, + "X-WR-CALNAME": calendarName || siteName, }, }); if (events instanceof Array === false) { - events = [ events ]; + events = [events]; } - events.forEach(event => { + events.forEach((event) => { // Add the event to the generator cal.createEvent({ start: moment.tz(event.start, event.timezone), @@ -40,10 +42,10 @@ function exportIcal(events, calendarName) { description: event.description, organizer: { name: event.hostName || "Anonymous", - email: event.creatorEmail || 'anonymous@anonymous.com', + email: event.creatorEmail || "anonymous@anonymous.com", }, location: event.location, - url: 'https://' + domain + '/' + event.id + url: "https://" + domain + "/" + event.id, }); }); // Stringify it! @@ -54,4 +56,4 @@ function exportIcal(events, calendarName) { module.exports = { addToLog, exportIcal, -} +}; diff --git a/src/models/Event.js b/src/models/Event.js index d800077..234084a 100755 --- a/src/models/Event.js +++ b/src/models/Event.js @@ -1,17 +1,17 @@ -const mongoose = require('mongoose'); +const mongoose = require("mongoose"); const Attendees = new mongoose.Schema({ name: { type: String, - trim: true + trim: true, }, status: { type: String, - trim: true + trim: true, }, email: { type: String, - trim: true + trim: true, }, removalPassword: { type: String, @@ -29,226 +29,231 @@ const Attendees = new mongoose.Schema({ number: { type: Number, trim: true, - default: 1 + default: 1, }, created: Date, -}) +}); -const Followers = new mongoose.Schema({ - // this is the id of the original follow *request*, which we use to validate Undo events - followId: { - type: String, - trim: true - }, - // this is the actual remote user profile id - actorId: { - type: String, - trim: true - }, - // this is the stringified JSON of the entire user profile - actorJson: { - type: String, - trim: true - }, - name: { - type: String, - trim: true - }, -}, { _id: false }) +const Followers = new mongoose.Schema( + { + // this is the id of the original follow *request*, which we use to validate Undo events + followId: { + type: String, + trim: true, + }, + // this is the actual remote user profile id + actorId: { + type: String, + trim: true, + }, + // this is the stringified JSON of the entire user profile + actorJson: { + type: String, + trim: true, + }, + name: { + type: String, + trim: true, + }, + }, + { _id: false } +); const ReplySchema = new mongoose.Schema({ id: { type: String, required: true, unique: true, - sparse: true + sparse: true, }, author: { type: String, trim: true, - required: true + required: true, }, content: { type: String, trim: true, - required: true + required: true, }, timestamp: { type: Date, trim: true, - required: true - } -}) + required: true, + }, +}); const ActivityPubMessages = new mongoose.Schema({ id: { type: String, required: true, unique: true, - sparse: true + sparse: true, }, content: { type: String, trim: true, - required: true - } -}) + required: true, + }, +}); const CommentSchema = new mongoose.Schema({ id: { type: String, required: true, unique: true, - sparse: true + sparse: true, }, author: { type: String, trim: true, - required: true + required: true, }, content: { type: String, trim: true, - required: true + required: true, }, timestamp: { type: Date, trim: true, - required: true + required: true, }, activityJson: { type: String, - trim: true + trim: true, }, actorJson: { type: String, - trim: true + trim: true, }, activityId: { type: String, - trim: true + trim: true, }, actorId: { type: String, - trim: true + trim: true, }, - replies: [ReplySchema] -}) + replies: [ReplySchema], +}); const EventSchema = new mongoose.Schema({ id: { type: String, required: true, - unique: true + unique: true, }, type: { type: String, trim: true, - required: true + required: true, }, name: { type: String, trim: true, - required: true + required: true, }, location: { type: String, trim: true, - required: true + required: true, }, - start: { // Stored as a UTC timestamp + start: { + // Stored as a UTC timestamp type: Date, trim: true, - required: true + required: true, }, - end: { // Stored as a UTC timestamp + end: { + // Stored as a UTC timestamp type: Date, trim: true, - required: true + required: true, }, timezone: { type: String, - default: 'Etc/UTC' + default: "Etc/UTC", }, description: { type: String, trim: true, - required: true + required: true, }, image: { type: String, - trim: true + trim: true, }, url: { type: String, - trim: true + trim: true, }, creatorEmail: { type: String, - trim: true + trim: true, }, hostName: { type: String, - trim: true + trim: true, }, viewPassword: { type: String, - trim: true + trim: true, }, editPassword: { type: String, - trim: true + trim: true, }, editToken: { type: String, trim: true, minlength: 32, - maxlength: 32 + maxlength: 32, }, - eventGroup: { type: mongoose.Schema.Types.ObjectId, ref: 'EventGroup' }, + eventGroup: { type: mongoose.Schema.Types.ObjectId, ref: "EventGroup" }, usersCanAttend: { type: Boolean, trim: true, - default: false + default: false, }, showUsersList: { type: Boolean, trim: true, - default: false + default: false, }, usersCanComment: { type: Boolean, trim: true, - default: false + default: false, }, firstLoad: { type: Boolean, trim: true, - default: true + default: true, }, attendees: [Attendees], maxAttendees: { - type: Number + type: Number, }, comments: [CommentSchema], activityPubActor: { type: String, - trim: true + trim: true, }, activityPubEvent: { type: String, - trim: true + trim: true, }, publicKey: { type: String, - trim: true + trim: true, }, privateKey: { type: String, - trim: true + trim: true, }, followers: [Followers], - activityPubMessages: [ActivityPubMessages] + activityPubMessages: [ActivityPubMessages], }); -module.exports = mongoose.model('Event', EventSchema); +module.exports = mongoose.model("Event", EventSchema); diff --git a/src/models/EventGroup.js b/src/models/EventGroup.js index c70ef95..1a4ac64 100755 --- a/src/models/EventGroup.js +++ b/src/models/EventGroup.js @@ -1,57 +1,57 @@ -const mongoose = require('mongoose'); +const mongoose = require("mongoose"); const Subscriber = new mongoose.Schema({ email: { type: String, - trim: true + trim: true, }, -}) +}); const EventGroupSchema = new mongoose.Schema({ id: { type: String, required: true, - unique: true + unique: true, }, name: { type: String, trim: true, - required: true + required: true, }, description: { type: String, trim: true, - required: true + required: true, }, image: { type: String, - trim: true + trim: true, }, url: { type: String, - trim: true + trim: true, }, creatorEmail: { type: String, - trim: true + trim: true, }, hostName: { type: String, - trim: true + trim: true, }, editToken: { type: String, trim: true, minlength: 32, - maxlength: 32 + maxlength: 32, }, firstLoad: { type: Boolean, trim: true, - default: true + default: true, }, - events: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Event' }], + events: [{ type: mongoose.Schema.Types.ObjectId, ref: "Event" }], subscribers: [Subscriber], }); -module.exports = mongoose.model('EventGroup', EventGroupSchema); +module.exports = mongoose.model("EventGroup", EventGroupSchema); diff --git a/src/models/Log.js b/src/models/Log.js index 95a3ab3..b048a45 100755 --- a/src/models/Log.js +++ b/src/models/Log.js @@ -1,26 +1,26 @@ -const mongoose = require('mongoose'); +const mongoose = require("mongoose"); const LogSchema = new mongoose.Schema({ status: { type: String, trim: true, - required: true + required: true, }, process: { type: String, trim: true, - required: true + required: true, }, message: { type: String, trim: true, - required: true + required: true, }, timestamp: { type: Date, trim: true, - required: true - } + required: true, + }, }); -module.exports = mongoose.model('Log', LogSchema); +module.exports = mongoose.model("Log", LogSchema); diff --git a/src/routes.js b/src/routes.js index c9867e3..052446c 100755 --- a/src/routes.js +++ b/src/routes.js @@ -1,45 +1,48 @@ -const fs = require('fs'); +const fs = require("fs"); -const express = require('express'); +const express = require("express"); -const mongoose = require('mongoose'); +const mongoose = require("mongoose"); // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs -const { customAlphabet } = require('nanoid'); -const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_', 21); +const { customAlphabet } = require("nanoid"); +const nanoid = customAlphabet( + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", + 21 +); const randomstring = require("randomstring"); -const { body, validationResult } = require('express-validator'); +const { body, validationResult } = require("express-validator"); const router = express.Router(); -const Event = mongoose.model('Event'); -const EventGroup = mongoose.model('EventGroup'); -const addToLog = require('./helpers.js').addToLog; +const Event = mongoose.model("Event"); +const EventGroup = mongoose.model("EventGroup"); +const addToLog = require("./helpers.js").addToLog; -var moment = require('moment-timezone'); +var moment = require("moment-timezone"); -const marked = require('marked'); +const marked = require("marked"); -const generateRSAKeypair = require('generate-rsa-keypair'); -const crypto = require('crypto'); -const request = require('request'); -const niceware = require('niceware'); +const generateRSAKeypair = require("generate-rsa-keypair"); +const crypto = require("crypto"); +const request = require("request"); +const niceware = require("niceware"); -const domain = require('./config/domain.js').domain; -const contactEmail = require('./config/domain.js').email; -const mailService = require('./config/domain.js').mailService; -const siteName = require('./config/domain.js').sitename; -const siteLogo = require('./config/domain.js').logo_url; -let isFederated = require('./config/domain.js').isFederated; -let showKofi = require('./config/domain.js').showKofi; +const domain = require("./config/domain.js").domain; +const contactEmail = require("./config/domain.js").email; +const mailService = require("./config/domain.js").mailService; +const siteName = require("./config/domain.js").sitename; +const siteLogo = require("./config/domain.js").logo_url; +let isFederated = require("./config/domain.js").isFederated; +let showKofi = require("./config/domain.js").showKofi; // if the federation config isn't set, things are federated by default if (isFederated === undefined) { isFederated = true; } -const ap = require('./activitypub.js'); +const ap = require("./activitypub.js"); // Extra marked renderer (used to render plaintext event description for page metadata) // Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/ @@ -47,7 +50,7 @@ const ap = require('./activitypub.js'); function htmlEscapeToText(text) { return text.replace(/\&\#[0-9]*;|&/g, function (escapeCode) { if (escapeCode.match(/amp/)) { - return '&'; + return "&"; } return String.fromCharCode(escapeCode.match(/[0-9]+/)); }); @@ -61,45 +64,45 @@ function render_plain() { }; render.strong = function (text) { return text; - } + }; render.em = function (text) { return text; - } + }; // render just the text of a paragraph render.paragraph = function (text) { - return htmlEscapeToText(text) + '\r\n'; + return htmlEscapeToText(text) + "\r\n"; }; // render nothing for headings, images, and br render.heading = function (text, level) { - return ''; + return ""; }; render.image = function (href, title, text) { - return ''; + return ""; }; render.br = function () { - return ''; + return ""; }; return render; } -const ical = require('ical'); -const { exportIcal } = require('./helpers.js'); +const ical = require("ical"); +const { exportIcal } = require("./helpers.js"); -const sgMail = require('@sendgrid/mail'); +const sgMail = require("@sendgrid/mail"); const nodemailer = require("nodemailer"); -const apiCredentials = require('./config/api.js'); +const apiCredentials = require("./config/api.js"); let sendEmails = false; let nodemailerTransporter; if (mailService) { switch (mailService) { - case 'sendgrid': + case "sendgrid": sgMail.setApiKey(apiCredentials.sendgrid); console.log("Sendgrid is ready to send emails."); sendEmails = true; break; - case 'nodemailer': + case "nodemailer": nodemailerTransporter = nodemailer.createTransport({ host: apiCredentials.smtpServer, port: apiCredentials.smtpPort, @@ -119,60 +122,104 @@ if (mailService) { }); break; default: - console.error('You have not configured this Gathio instance to send emails! This means that event creators will not receive emails when their events are created, which means they may end up locked out of editing events. Consider setting up an email service.') + console.error( + "You have not configured this Gathio instance to send emails! This means that event creators will not receive emails when their events are created, which means they may end up locked out of editing events. Consider setting up an email service." + ); } } -const fileUpload = require('express-fileupload'); -var Jimp = require('jimp'); +const fileUpload = require("express-fileupload"); +var Jimp = require("jimp"); router.use(fileUpload()); // SCHEDULED DELETION -const schedule = require('node-schedule'); -schedule.scheduleJob('59 23 * * *', function (fireDate) { - const too_old = moment.tz('Etc/UTC').subtract(7, 'days').toDate(); - console.log("Old event deletion running! Deleting all events concluding before ", too_old); - - Event.find({ end: { $lte: too_old } }).then((oldEvents) => { - oldEvents.forEach(event => { - const deleteEventFromDB = (id) => { - Event.remove({ "_id": id }) - .then(response => { - addToLog("deleteOldEvents", "success", "Old event " + id + " deleted"); - }).catch((err) => { - addToLog("deleteOldEvents", "error", "Attempt to delete old event " + id + " failed with error: " + err); - }); - } +const schedule = require("node-schedule"); +schedule.scheduleJob("59 23 * * *", function (fireDate) { + const too_old = moment.tz("Etc/UTC").subtract(7, "days").toDate(); + console.log( + "Old event deletion running! Deleting all events concluding before ", + too_old + ); + + Event.find({ end: { $lte: too_old } }) + .then((oldEvents) => { + oldEvents.forEach((event) => { + const deleteEventFromDB = (id) => { + Event.remove({ _id: id }) + .then((response) => { + addToLog( + "deleteOldEvents", + "success", + "Old event " + id + " deleted" + ); + }) + .catch((err) => { + addToLog( + "deleteOldEvents", + "error", + "Attempt to delete old event " + + id + + " failed with error: " + + err + ); + }); + }; - if (event.image) { - fs.unlink(global.appRoot + '/public/events/' + event.image, (err) => { - if (err) { - addToLog("deleteOldEvents", "error", "Attempt to delete event image for old event " + event.id + " failed with error: " + err); - } - // Image removed - addToLog("deleteOldEvents", "error", "Image deleted for old event " + event.id); - }) - } - // Check if event has ActivityPub fields - if (event.activityPubActor && event.activityPubEvent) { - // Broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information - const guidUpdateObject = crypto.randomBytes(16).toString('hex'); - const jsonUpdateObject = JSON.parse(event.activityPubActor); - const jsonEventObject = JSON.parse(event.activityPubEvent); - // first broadcast AP messages, THEN delete from DB - ap.broadcastDeleteMessage(jsonUpdateObject, event.followers, event.id, function (statuses) { - ap.broadcastDeleteMessage(jsonEventObject, event.followers, event.id, function (statuses) { - deleteEventFromDB(event._id); + if (event.image) { + fs.unlink(global.appRoot + "/public/events/" + event.image, (err) => { + if (err) { + addToLog( + "deleteOldEvents", + "error", + "Attempt to delete event image for old event " + + event.id + + " failed with error: " + + err + ); + } + // Image removed + addToLog( + "deleteOldEvents", + "error", + "Image deleted for old event " + event.id + ); }); - }); - } else { - // No ActivityPub data - simply delete the event - deleteEventFromDB(event._id); - } + } + // Check if event has ActivityPub fields + if (event.activityPubActor && event.activityPubEvent) { + // Broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information + const guidUpdateObject = crypto.randomBytes(16).toString("hex"); + const jsonUpdateObject = JSON.parse(event.activityPubActor); + const jsonEventObject = JSON.parse(event.activityPubEvent); + // first broadcast AP messages, THEN delete from DB + ap.broadcastDeleteMessage( + jsonUpdateObject, + event.followers, + event.id, + function (statuses) { + ap.broadcastDeleteMessage( + jsonEventObject, + event.followers, + event.id, + function (statuses) { + deleteEventFromDB(event._id); + } + ); + } + ); + } else { + // No ActivityPub data - simply delete the event + deleteEventFromDB(event._id); + } + }); }) - }).catch((err) => { - addToLog("deleteOldEvents", "error", "Attempt to delete old event " + event.id + " failed with error: " + err); - }); + .catch((err) => { + addToLog( + "deleteOldEvents", + "error", + "Attempt to delete old event " + event.id + " failed with error: " + err + ); + }); // TODO: While we're here, also remove all provisioned event attendees over a day // old (they're not going to become active) @@ -180,8 +227,8 @@ schedule.scheduleJob('59 23 * * *', function (fireDate) { // FRONTEND ROUTES -router.get('/', (req, res) => { - res.render('home', { +router.get("/", (req, res) => { + res.render("home", { domain, email: contactEmail, siteName, @@ -189,192 +236,236 @@ router.get('/', (req, res) => { }); }); -router.get('/new', (req, res) => { - res.render('home'); +router.get("/new", (req, res) => { + res.render("home"); }); -router.get('/new/event', (req, res) => { - res.render('newevent', { +router.get("/new/event", (req, res) => { + res.render("newevent", { domain: domain, email: contactEmail, siteName: siteName, }); }); -router.get('/new/event/public', (req, res) => { +router.get("/new/event/public", (req, res) => { let isPrivate = false; let isPublic = true; let isOrganisation = false; let isUnknownType = false; - res.render('newevent', { - title: 'New event', + res.render("newevent", { + title: "New event", isPrivate: isPrivate, isPublic: isPublic, isOrganisation: isOrganisation, isUnknownType: isUnknownType, - eventType: 'public', + eventType: "public", domain: domain, email: contactEmail, siteName: siteName, }); -}) +}); // return the JSON for the featured/pinned post for this event -router.get('/:eventID/featured', (req, res) => { +router.get("/:eventID/featured", (req, res) => { if (!isFederated) return res.sendStatus(404); const { eventID } = req.params; - const guidObject = crypto.randomBytes(16).toString('hex'); + const guidObject = crypto.randomBytes(16).toString("hex"); const featured = { "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${eventID}/featured`, - "type": "OrderedCollection", - "orderedItems": [ - ap.createFeaturedPost(eventID) - ] - } + id: `https://${domain}/${eventID}/featured`, + type: "OrderedCollection", + orderedItems: [ap.createFeaturedPost(eventID)], + }; res.json(featured); }); // return the JSON for a given activitypub message -router.get('/:eventID/m/:hash', (req, res) => { +router.get("/:eventID/m/:hash", (req, res) => { if (!isFederated) return res.sendStatus(404); const { hash, eventID } = req.params; const id = `https://${domain}/${eventID}/m/${hash}`; Event.findOne({ - id: eventID + id: eventID, }) .then((event) => { if (!event) { res.status(404); - res.render('404', { url: req.url }); - } - else { - const message = event.activityPubMessages.find(el => el.id === id); + res.render("404", { url: req.url }); + } else { + const message = event.activityPubMessages.find((el) => el.id === id); if (message) { return res.json(JSON.parse(message.content)); - } - else { + } else { res.status(404); - return res.render('404', { url: req.url }); + return res.render("404", { url: req.url }); } } }) .catch((err) => { - addToLog("getActivityPubMessage", "error", "Attempt to get Activity Pub Message for " + id + " failed with error: " + err); + addToLog( + "getActivityPubMessage", + "error", + "Attempt to get Activity Pub Message for " + + id + + " failed with error: " + + err + ); res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); }); // return the webfinger record required for the initial activitypub handshake -router.get('/.well-known/webfinger', (req, res) => { +router.get("/.well-known/webfinger", (req, res) => { if (!isFederated) return res.sendStatus(404); let resource = req.query.resource; - if (!resource || !resource.includes('acct:')) { - return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.'); - } - else { + if (!resource || !resource.includes("acct:")) { + return res + .status(400) + .send( + 'Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.' + ); + } else { // "foo@domain" - let activityPubAccount = resource.replace('acct:', ''); + let activityPubAccount = resource.replace("acct:", ""); // "foo" - let eventID = activityPubAccount.replace(/@.*/, ''); + let eventID = activityPubAccount.replace(/@.*/, ""); Event.findOne({ - id: eventID + id: eventID, }) .then((event) => { if (!event) { res.status(404); - res.render('404', { url: req.url }); - } - else { + res.render("404", { url: req.url }); + } else { res.json(ap.createWebfinger(eventID, domain)); } }) .catch((err) => { - addToLog("renderWebfinger", "error", "Attempt to render webfinger for " + req.params.eventID + " failed with error: " + err); + addToLog( + "renderWebfinger", + "error", + "Attempt to render webfinger for " + + req.params.eventID + + " failed with error: " + + err + ); res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); } }); -router.get('/:eventID', (req, res) => { +router.get("/:eventID", (req, res) => { Event.findOne({ - id: req.params.eventID + id: req.params.eventID, }) .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is - .populate('eventGroup') + .populate("eventGroup") .then((event) => { if (event) { - const parsedLocation = event.location.replace(/\s+/g, '+'); + const parsedLocation = event.location.replace(/\s+/g, "+"); let displayDate; - if (moment.tz(event.end, event.timezone).isSame(event.start, 'day')) { + if (moment.tz(event.end, event.timezone).isSame(event.start, "day")) { // Happening during one day - displayDate = moment.tz(event.start, event.timezone).format('dddd D MMMM YYYY [from] h:mm a') + moment.tz(event.end, event.timezone).format(' [to] h:mm a [](z)[]'); - } - else { - displayDate = moment.tz(event.start, event.timezone).format('dddd D MMMM YYYY [at] h:mm a') + moment.tz(event.end, event.timezone).format(' [–] dddd D MMMM YYYY [at] h:mm a [](z)[]'); + displayDate = + moment + .tz(event.start, event.timezone) + .format( + 'dddd D MMMM YYYY [from] h:mm a' + ) + + moment + .tz(event.end, event.timezone) + .format( + ' [to] h:mm a [](z)[]' + ); + } else { + displayDate = + moment + .tz(event.start, event.timezone) + .format( + 'dddd D MMMM YYYY [at] h:mm a' + ) + + moment + .tz(event.end, event.timezone) + .format( + ' [–] dddd D MMMM YYYY [at] h:mm a [](z)[]' + ); } let eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString(); let eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString(); - let parsedStart = moment.tz(event.start, event.timezone).format('YYYYMMDD[T]HHmmss'); - let parsedEnd = moment.tz(event.end, event.timezone).format('YYYYMMDD[T]HHmmss'); + let parsedStart = moment + .tz(event.start, event.timezone) + .format("YYYYMMDD[T]HHmmss"); + let parsedEnd = moment + .tz(event.end, event.timezone) + .format("YYYYMMDD[T]HHmmss"); let eventHasConcluded = false; - if (moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone))) { + if ( + moment + .tz(event.end, event.timezone) + .isBefore(moment.tz(event.timezone)) + ) { eventHasConcluded = true; } let eventHasBegun = false; - if (moment.tz(event.start, event.timezone).isBefore(moment.tz(event.timezone))) { + if ( + moment + .tz(event.start, event.timezone) + .isBefore(moment.tz(event.timezone)) + ) { eventHasBegun = true; } let fromNow = moment.tz(event.start, event.timezone).fromNow(); let parsedDescription = marked.parse(event.description); let eventEditToken = event.editToken; - let escapedName = event.name.replace(/\s+/g, '+'); + let escapedName = event.name.replace(/\s+/g, "+"); let eventHasCoverImage = false; if (event.image) { eventHasCoverImage = true; - } - else { + } else { eventHasCoverImage = false; } let eventHasHost = false; if (event.hostName) { eventHasHost = true; - } - else { + } else { eventHasHost = false; } let firstLoad = false; if (event.firstLoad === true) { firstLoad = true; - Event.findOneAndUpdate({ id: req.params.eventID }, { firstLoad: false }, function (err, raw) { - if (err) { - res.send(err); + Event.findOneAndUpdate( + { id: req.params.eventID }, + { firstLoad: false }, + function (err, raw) { + if (err) { + res.send(err); + } } - }); + ); } let editingEnabled = false; if (Object.keys(req.query).length !== 0) { if (!req.query.e) { editingEnabled = false; console.log("No edit token set"); - } - else { + } else { if (req.query.e === eventEditToken) { editingEnabled = true; - } - else { + } else { editingEnabled = false; } } } - let eventAttendees = event.attendees.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)) - .map(el => { + let eventAttendees = event.attendees + .sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)) + .map((el) => { if (!el.id) { el.id = el._id; } @@ -384,12 +475,15 @@ router.get('/:eventID', (req, res) => { return el; }) .filter((obj, pos, arr) => { - return obj.status === 'attending' && arr.map(mapObj => mapObj.id).indexOf(obj.id) === pos; + return ( + obj.status === "attending" && + arr.map((mapObj) => mapObj.id).indexOf(obj.id) === pos + ); }); let spotsRemaining, noMoreSpots; let numberOfAttendees = eventAttendees.reduce((acc, attendee) => { - if (attendee.status === 'attending') { + if (attendee.status === "attending") { return acc + attendee.number || 1; } return acc; @@ -402,16 +496,27 @@ router.get('/:eventID', (req, res) => { } let metadata = { title: event.name, - description: marked.parse(event.description, { renderer: render_plain() }).split(" ").splice(0, 40).join(" ").trim(), - image: (eventHasCoverImage ? `https://${domain}/events/` + event.image : null), - url: `https://${domain}/` + req.params.eventID + description: marked + .parse(event.description, { renderer: render_plain() }) + .split(" ") + .splice(0, 40) + .join(" ") + .trim(), + image: eventHasCoverImage + ? `https://${domain}/events/` + event.image + : null, + url: `https://${domain}/` + req.params.eventID, }; - if (req.headers.accept && (req.headers.accept.includes('application/activity+json') || req.headers.accept.includes('application/json') || req.headers.accept.includes('application/json+ld'))) { + if ( + req.headers.accept && + (req.headers.accept.includes("application/activity+json") || + req.headers.accept.includes("application/json") || + req.headers.accept.includes("application/json+ld")) + ) { res.json(JSON.parse(event.activityPubActor)); - } - else { + } else { res.set("X-Robots-Tag", "noindex"); - res.render('event', { + res.render("event", { domain: domain, isFederated: isFederated, email: contactEmail, @@ -438,58 +543,61 @@ router.get('/:eventID', (req, res) => { eventHasConcluded: eventHasConcluded, eventHasBegun: eventHasBegun, metadata: metadata, - siteName: siteName - }) + siteName: siteName, + }); } - } - else { + } else { res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); } - }) .catch((err) => { - addToLog("displayEvent", "error", "Attempt to display event " + req.params.eventID + " failed with error: " + err); - console.log(err) + addToLog( + "displayEvent", + "error", + "Attempt to display event " + + req.params.eventID + + " failed with error: " + + err + ); + console.log(err); res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); -}) +}); -router.get('/:eventID/followers', (req, res) => { +router.get("/:eventID/followers", (req, res) => { if (!isFederated) return res.sendStatus(404); const eventID = req.params.eventID; Event.findOne({ - id: eventID - }) - .then((event) => { - if (event) { - const followers = event.followers.map(el => el.actorId); - let followersCollection = { - "type": "OrderedCollection", - "totalItems": followers.length, - "id": `https://${domain}/${eventID}/followers`, - "first": { - "type": "OrderedCollectionPage", - "totalItems": followers.length, - "partOf": `https://${domain}/${eventID}/followers`, - "orderedItems": followers, - "id": `https://${domain}/${eventID}/followers?page=1` - }, - "@context": ["https://www.w3.org/ns/activitystreams"] - }; - return res.json(followersCollection); - } - else { - return res.status(400).send('Bad request.'); - } - }) -}) + id: eventID, + }).then((event) => { + if (event) { + const followers = event.followers.map((el) => el.actorId); + let followersCollection = { + type: "OrderedCollection", + totalItems: followers.length, + id: `https://${domain}/${eventID}/followers`, + first: { + type: "OrderedCollectionPage", + totalItems: followers.length, + partOf: `https://${domain}/${eventID}/followers`, + orderedItems: followers, + id: `https://${domain}/${eventID}/followers?page=1`, + }, + "@context": ["https://www.w3.org/ns/activitystreams"], + }; + return res.json(followersCollection); + } else { + return res.status(400).send("Bad request."); + } + }); +}); -router.get('/group/:eventGroupID', (req, res) => { +router.get("/group/:eventGroupID", (req, res) => { EventGroup.findOne({ - id: req.params.eventGroupID + id: req.params.eventGroupID, }) .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is .then(async (eventGroup) => { @@ -497,78 +605,99 @@ router.get('/group/:eventGroupID', (req, res) => { let parsedDescription = marked.parse(eventGroup.description); let eventGroupEditToken = eventGroup.editToken; - let escapedName = eventGroup.name.replace(/\s+/g, '+'); + let escapedName = eventGroup.name.replace(/\s+/g, "+"); let eventGroupHasCoverImage = false; if (eventGroup.image) { eventGroupHasCoverImage = true; - } - else { + } else { eventGroupHasCoverImage = false; } let eventGroupHasHost = false; if (eventGroup.hostName) { eventGroupHasHost = true; - } - else { + } else { eventGroupHasHost = false; } - let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); + let events = await Event.find({ eventGroup: eventGroup._id }) + .lean() + .sort("start"); - events.map(event => { - if (moment.tz(event.end, event.timezone).isSame(event.start, 'day')) { + events.map((event) => { + if (moment.tz(event.end, event.timezone).isSame(event.start, "day")) { // Happening during one day - event.displayDate = moment.tz(event.start, event.timezone).format('D MMM YYYY'); - } - else { - event.displayDate = moment.tz(event.start, event.timezone).format('D MMM YYYY') + moment.tz(event.end, event.timezone).format(' - D MMM YYYY'); + event.displayDate = moment + .tz(event.start, event.timezone) + .format("D MMM YYYY"); + } else { + event.displayDate = + moment.tz(event.start, event.timezone).format("D MMM YYYY") + + moment.tz(event.end, event.timezone).format(" - D MMM YYYY"); } - if (moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone))) { + if ( + moment + .tz(event.end, event.timezone) + .isBefore(moment.tz(event.timezone)) + ) { event.eventHasConcluded = true; } else { event.eventHasConcluded = false; } - return (({ id, name, displayDate, eventHasConcluded }) => ({ id, name, displayDate, eventHasConcluded }))(event); + return (({ id, name, displayDate, eventHasConcluded }) => ({ + id, + name, + displayDate, + eventHasConcluded, + }))(event); }); let upcomingEventsExist = false; - if (events.some(e => e.eventHasConcluded === false)) { + if (events.some((e) => e.eventHasConcluded === false)) { upcomingEventsExist = true; } let firstLoad = false; if (eventGroup.firstLoad === true) { firstLoad = true; - EventGroup.findOneAndUpdate({ id: req.params.eventGroupID }, { firstLoad: false }, function (err, raw) { - if (err) { - res.send(err); + EventGroup.findOneAndUpdate( + { id: req.params.eventGroupID }, + { firstLoad: false }, + function (err, raw) { + if (err) { + res.send(err); + } } - }); + ); } let editingEnabled = false; if (Object.keys(req.query).length !== 0) { if (!req.query.e) { editingEnabled = false; console.log("No edit token set"); - } - else { + } else { if (req.query.e === eventGroupEditToken) { editingEnabled = true; - } - else { + } else { editingEnabled = false; } } } let metadata = { title: eventGroup.name, - description: marked.parse(eventGroup.description, { renderer: render_plain() }).split(" ").splice(0, 40).join(" ").trim(), - image: (eventGroupHasCoverImage ? `https://${domain}/events/` + eventGroup.image : null), - url: `https://${domain}/` + req.params.eventID + description: marked + .parse(eventGroup.description, { renderer: render_plain() }) + .split(" ") + .splice(0, 40) + .join(" ") + .trim(), + image: eventGroupHasCoverImage + ? `https://${domain}/events/` + eventGroup.image + : null, + url: `https://${domain}/` + req.params.eventID, }; res.set("X-Robots-Tag", "noindex"); - res.render('eventgroup', { + res.render("eventgroup", { domain: domain, title: eventGroup.name, eventGroupData: eventGroup, @@ -580,51 +709,65 @@ router.get('/group/:eventGroupID', (req, res) => { eventGroupHasCoverImage: eventGroupHasCoverImage, eventGroupHasHost: eventGroupHasHost, firstLoad: firstLoad, - metadata: metadata - }) - } - else { + metadata: metadata, + }); + } else { res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); } - }) .catch((err) => { - addToLog("displayEventGroup", "error", "Attempt to display event group " + req.params.eventGroupID + " failed with error: " + err); - console.log(err) + addToLog( + "displayEventGroup", + "error", + "Attempt to display event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + console.log(err); res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); -}) +}); -router.get('/group/:eventGroupID/feed.ics', (req, res) => { +router.get("/group/:eventGroupID/feed.ics", (req, res) => { EventGroup.findOne({ - id: req.params.eventGroupID + id: req.params.eventGroupID, }) .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is .then(async (eventGroup) => { if (eventGroup) { - let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); + let events = await Event.find({ eventGroup: eventGroup._id }) + .lean() + .sort("start"); const string = exportIcal(events, eventGroup.name); - res.set('Content-Type', 'text/calendar'); + res.set("Content-Type", "text/calendar"); return res.send(string); } }) .catch((err) => { - addToLog("eventGroupFeed", "error", "Attempt to display event group feed for " + req.params.eventGroupID + " failed with error: " + err); - console.log(err) + addToLog( + "eventGroupFeed", + "error", + "Attempt to display event group feed for " + + req.params.eventGroupID + + " failed with error: " + + err + ); + console.log(err); res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); }); -router.get('/exportevent/:eventID', (req, res) => { +router.get("/exportevent/:eventID", (req, res) => { Event.findOne({ - id: req.params.eventID + id: req.params.eventID, }) - .populate('eventGroup') + .populate("eventGroup") .then((event) => { if (event) { const string = exportIcal([event]); @@ -632,38 +775,54 @@ router.get('/exportevent/:eventID', (req, res) => { } }) .catch((err) => { - addToLog("exportEvent", "error", "Attempt to export event " + req.params.eventID + " failed with error: " + err); - console.log(err) + addToLog( + "exportEvent", + "error", + "Attempt to export event " + + req.params.eventID + + " failed with error: " + + err + ); + console.log(err); res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); }); -router.get('/exportgroup/:eventGroupID', (req, res) => { +router.get("/exportgroup/:eventGroupID", (req, res) => { EventGroup.findOne({ - id: req.params.eventGroupID + id: req.params.eventGroupID, }) .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is .then(async (eventGroup) => { if (eventGroup) { - let events = await Event.find({ eventGroup: eventGroup._id }).lean().sort('start'); + let events = await Event.find({ eventGroup: eventGroup._id }) + .lean() + .sort("start"); const string = exportIcal(events); res.send(string); } }) .catch((err) => { - addToLog("exportEvent", "error", "Attempt to export event group " + req.params.eventGroupID + " failed with error: " + err); - console.log(err) + addToLog( + "exportEvent", + "error", + "Attempt to export event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + console.log(err); res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); }); // BACKEND ROUTES -router.post('/newevent', async (req, res) => { +router.post("/newevent", async (req, res) => { let eventID = nanoid(); let editToken = randomstring.generate(); let eventImageFilename = ""; @@ -671,26 +830,38 @@ router.post('/newevent', async (req, res) => { if (req.files && Object.keys(req.files).length !== 0) { let eventImageBuffer = req.files.imageUpload.data; eventImageFilename = await Jimp.read(eventImageBuffer) - .then(img => { + .then((img) => { img .resize(920, Jimp.AUTO) // resize .quality(80) // set JPEG quality - .write('./public/events/' + eventID + '.jpg'); // save - const filename = eventID + '.jpg'; + .write("./public/events/" + eventID + ".jpg"); // save + const filename = eventID + ".jpg"; return filename; - }) - .catch(err => { - addToLog("Jimp", "error", "Attempt to edit image failed with error: " + err); + }) + .catch((err) => { + addToLog( + "Jimp", + "error", + "Attempt to edit image failed with error: " + err + ); }); } - let startUTC = moment.tz(req.body.eventStart, 'D MMMM YYYY, hh:mm a', req.body.timezone); - let endUTC = moment.tz(req.body.eventEnd, 'D MMMM YYYY, hh:mm a', req.body.timezone); + let startUTC = moment.tz( + req.body.eventStart, + "D MMMM YYYY, hh:mm a", + req.body.timezone + ); + let endUTC = moment.tz( + req.body.eventEnd, + "D MMMM YYYY, hh:mm a", + req.body.timezone + ); let eventGroup; if (req.body.eventGroupCheckbox) { eventGroup = await EventGroup.findOne({ id: req.body.eventGroupID, - editToken: req.body.eventGroupEditToken - }) + editToken: req.body.eventGroupEditToken, + }); if (eventGroup) { isPartOfEventGroup = true; } @@ -721,97 +892,171 @@ router.post('/newevent', async (req, res) => { usersCanComment: req.body.interactionCheckbox ? true : false, maxAttendees: req.body.maxAttendees, firstLoad: true, - activityPubActor: ap.createActivityPubActor(eventID, domain, pair.public, marked.parse(req.body.eventDescription), req.body.eventName, req.body.eventLocation, eventImageFilename, startUTC, endUTC, req.body.timezone), - activityPubEvent: ap.createActivityPubEvent(req.body.eventName, startUTC, endUTC, req.body.timezone, req.body.eventDescription, req.body.eventLocation), - activityPubMessages: [{ id: `https://${domain}/${eventID}/m/featuredPost`, content: JSON.stringify(ap.createFeaturedPost(eventID, req.body.eventName, startUTC, endUTC, req.body.timezone, req.body.eventDescription, req.body.eventLocation)) }], + activityPubActor: ap.createActivityPubActor( + eventID, + domain, + pair.public, + marked.parse(req.body.eventDescription), + req.body.eventName, + req.body.eventLocation, + eventImageFilename, + startUTC, + endUTC, + req.body.timezone + ), + activityPubEvent: ap.createActivityPubEvent( + req.body.eventName, + startUTC, + endUTC, + req.body.timezone, + req.body.eventDescription, + req.body.eventLocation + ), + activityPubMessages: [ + { + id: `https://${domain}/${eventID}/m/featuredPost`, + content: JSON.stringify( + ap.createFeaturedPost( + eventID, + req.body.eventName, + startUTC, + endUTC, + req.body.timezone, + req.body.eventDescription, + req.body.eventLocation + ) + ), + }, + ], publicKey: pair.public, - privateKey: pair.private + privateKey: pair.private, }); - event.save() + event + .save() .then((event) => { addToLog("createEvent", "success", "Event " + eventID + "created"); // Send email with edit link if (req.body.creatorEmail && sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/createevent.handlebars', { eventID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail + req.app + .get("hbsInstance") + .renderView( + "./views/emails/createevent.handlebars", + { + eventID, + editToken, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: ${req.body.eventName}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${req.body.eventName}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); } // If the event was added to a group, send an email to any group // subscribers if (event.eventGroup && sendEmails) { - EventGroup.findOne({ _id: event.eventGroup._id }) - .then((eventGroup) => { - const subscribers = eventGroup.subscribers.reduce((acc, current) => { - if (acc.includes(current.email)) { - return acc; - } - return [current.email, ...acc]; - }, []); - subscribers.forEach(emailAddress => { - req.app.get('hbsInstance').renderView('./views/emails/eventgroupupdated.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, eventGroupName: eventGroup.name, eventName: event.name, eventID: event.id, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(emailAddress), cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: emailAddress, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New event in ${eventGroup.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; + EventGroup.findOne({ _id: event.eventGroup._id }).then((eventGroup) => { + const subscribers = eventGroup.subscribers.reduce((acc, current) => { + if (acc.includes(current.email)) { + return acc; + } + return [current.email, ...acc]; + }, []); + subscribers.forEach((emailAddress) => { + req.app + .get("hbsInstance") + .renderView( + "./views/emails/eventgroupupdated.handlebars", + { + siteName, + siteLogo, + domain, + eventID: req.params.eventID, + eventGroupName: eventGroup.name, + eventName: event.name, + eventID: event.id, + eventGroupID: eventGroup.id, + emailAddress: encodeURIComponent(emailAddress), + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: emailAddress, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New event in ${eventGroup.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } } - }); - }); + ); }); + }); } res.writeHead(302, { - 'Location': '/' + eventID + '?e=' + editToken + Location: "/" + eventID + "?e=" + editToken, }); res.end(); }) - .catch((err) => { res.status(500).send('Database error, please try again :( - ' + err); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); + .catch((err) => { + res.status(500).send("Database error, please try again :( - " + err); + addToLog( + "createEvent", + "error", + "Attempt to create event failed with error: " + err + ); + }); }); -router.post('/importevent', (req, res) => { +router.post("/importevent", (req, res) => { let eventID = nanoid(); let editToken = randomstring.generate(); if (req.files && Object.keys(req.files).length !== 0) { - let iCalObject = ical.parseICS(req.files.icsImportControl.data.toString('utf8')); + let iCalObject = ical.parseICS( + req.files.icsImportControl.data.toString("utf8") + ); let importedEventData = iCalObject[Object.keys(iCalObject)]; let creatorEmail; @@ -823,84 +1068,115 @@ router.post('/importevent', (req, res) => { const event = new Event({ id: eventID, - type: 'public', + type: "public", name: importedEventData.summary, location: importedEventData.location, start: importedEventData.start, end: importedEventData.end, - timezone: typeof importedEventData.start.tz !== 'undefined' ? importedEventData.start.tz : "Etc/UTC", + timezone: + typeof importedEventData.start.tz !== "undefined" + ? importedEventData.start.tz + : "Etc/UTC", description: importedEventData.description, - image: '', + image: "", creatorEmail: creatorEmail, - url: '', - hostName: importedEventData.organizer ? importedEventData.organizer.params.CN.replace(/["]+/g, '') : "", - viewPassword: '', - editPassword: '', + url: "", + hostName: importedEventData.organizer + ? importedEventData.organizer.params.CN.replace(/["]+/g, "") + : "", + viewPassword: "", + editPassword: "", editToken: editToken, usersCanAttend: false, showUsersList: false, usersCanComment: false, - firstLoad: true + firstLoad: true, }); - event.save() + event + .save() .then(() => { addToLog("createEvent", "success", "Event " + eventID + " created"); // Send email with edit link if (creatorEmail && sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/createevent.handlebars', { eventID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail + req.app + .get("hbsInstance") + .renderView( + "./views/emails/createevent.handlebars", + { + eventID, + editToken, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: ${importedEventData.summary}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${importedEventData.summary}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); } res.writeHead(302, { - 'Location': '/' + eventID + '?e=' + editToken + Location: "/" + eventID + "?e=" + editToken, }); res.end(); }) - .catch((err) => { res.send('Database error, please try again :('); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); - } - else { - console.log("Files array is empty!") + .catch((err) => { + res.send("Database error, please try again :("); + addToLog( + "createEvent", + "error", + "Attempt to create event failed with error: " + err + ); + }); + } else { + console.log("Files array is empty!"); res.status(500).end(); } }); -router.post('/neweventgroup', (req, res) => { +router.post("/neweventgroup", (req, res) => { let eventGroupID = nanoid(); let editToken = randomstring.generate(); let eventGroupImageFilename = ""; if (req.files && Object.keys(req.files).length !== 0) { let eventImageBuffer = req.files.imageUpload.data; Jimp.read(eventImageBuffer, (err, img) => { - if (err) addToLog("Jimp", "error", "Attempt to edit image failed with error: " + err); + if (err) + addToLog( + "Jimp", + "error", + "Attempt to edit image failed with error: " + err + ); img .resize(920, Jimp.AUTO) // resize .quality(80) // set JPEG quality - .write('./public/events/' + eventGroupID + '.jpg'); // save + .write("./public/events/" + eventGroupID + ".jpg"); // save }); - eventGroupImageFilename = eventGroupID + '.jpg'; + eventGroupImageFilename = eventGroupID + ".jpg"; } const eventGroup = new EventGroup({ id: eventGroupID, @@ -911,76 +1187,99 @@ router.post('/neweventgroup', (req, res) => { url: req.body.eventGroupURL, hostName: req.body.hostName, editToken: editToken, - firstLoad: true + firstLoad: true, }); - eventGroup.save() + eventGroup + .save() .then(() => { - addToLog("createEventGroup", "success", "Event group " + eventGroupID + " created"); + addToLog( + "createEventGroup", + "success", + "Event group " + eventGroupID + " created" + ); // Send email with edit link if (req.body.creatorEmail && sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/createeventgroup.handlebars', { eventGroupID, editToken, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail + req.app + .get("hbsInstance") + .renderView( + "./views/emails/createeventgroup.handlebars", + { + eventGroupID, + editToken, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: ${req.body.eventGroupName}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${req.body.eventGroupName}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); } res.writeHead(302, { - 'Location': '/group/' + eventGroupID + '?e=' + editToken + Location: "/group/" + eventGroupID + "?e=" + editToken, }); res.end(); }) - .catch((err) => { res.send('Database error, please try again :( - ' + err); addToLog("createEvent", "error", "Attempt to create event failed with error: " + err); }); + .catch((err) => { + res.send("Database error, please try again :( - " + err); + addToLog( + "createEvent", + "error", + "Attempt to create event failed with error: " + err + ); + }); }); -router.post('/verifytoken/event/:eventID', (req, res) => { +router.post("/verifytoken/event/:eventID", (req, res) => { Event.findOne({ id: req.params.eventID, editToken: req.body.editToken, - }) - .then(event => { - if (event) return res.sendStatus(200); - return res.sendStatus(404); - }) + }).then((event) => { + if (event) return res.sendStatus(200); + return res.sendStatus(404); + }); }); -router.post('/verifytoken/group/:eventGroupID', (req, res) => { +router.post("/verifytoken/group/:eventGroupID", (req, res) => { EventGroup.findOne({ id: req.params.eventGroupID, editToken: req.body.editToken, - }) - .then(group => { - if (group) return res.sendStatus(200); - return res.sendStatus(404); - }) + }).then((group) => { + if (group) return res.sendStatus(200); + return res.sendStatus(404); + }); }); - -router.post('/editevent/:eventID/:editToken', (req, res) => { +router.post("/editevent/:eventID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; - Event.findOne(({ + Event.findOne({ id: req.params.eventID, - })) + }) .then(async (event) => { if (event.editToken === submittedEditToken) { // Token matches @@ -995,20 +1294,28 @@ router.post('/editevent/:eventID/:editToken', (req, res) => { img .resize(920, Jimp.AUTO) // resize .quality(80) // set JPEG - .write('./public/events/' + eventID + '.jpg'); // save + .write("./public/events/" + eventID + ".jpg"); // save }); - eventImageFilename = eventID + '.jpg'; + eventImageFilename = eventID + ".jpg"; } - let startUTC = moment.tz(req.body.eventStart, 'D MMMM YYYY, hh:mm a', req.body.timezone); - let endUTC = moment.tz(req.body.eventEnd, 'D MMMM YYYY, hh:mm a', req.body.timezone); + let startUTC = moment.tz( + req.body.eventStart, + "D MMMM YYYY, hh:mm a", + req.body.timezone + ); + let endUTC = moment.tz( + req.body.eventEnd, + "D MMMM YYYY, hh:mm a", + req.body.timezone + ); let isPartOfEventGroup = false; let eventGroup; if (req.body.eventGroupCheckbox) { eventGroup = await EventGroup.findOne({ id: req.body.eventGroupID, - editToken: req.body.eventGroupEditToken - }) + editToken: req.body.eventGroupEditToken, + }); if (eventGroup) { isPartOfEventGroup = true; } @@ -1026,12 +1333,34 @@ router.post('/editevent/:eventID/:editToken', (req, res) => { usersCanAttend: req.body.joinCheckbox ? true : false, showUsersList: req.body.guestlistCheckbox ? true : false, usersCanComment: req.body.interactionCheckbox ? true : false, - maxAttendees: req.body.maxAttendeesCheckbox ? req.body.maxAttendees : null, + maxAttendees: req.body.maxAttendeesCheckbox + ? req.body.maxAttendees + : null, eventGroup: isPartOfEventGroup ? eventGroup._id : null, - activityPubActor: event.activityPubActor ? ap.updateActivityPubActor(JSON.parse(event.activityPubActor), req.body.eventDescription, req.body.eventName, req.body.eventLocation, eventImageFilename, startUTC, endUTC, req.body.timezone) : null, - activityPubEvent: event.activityPubEvent ? ap.updateActivityPubEvent(JSON.parse(event.activityPubEvent), req.body.eventName, req.body.startUTC, req.body.endUTC, req.body.timezone) : null, - } - let diffText = '

This event was just updated with new information.

    '; + activityPubActor: event.activityPubActor + ? ap.updateActivityPubActor( + JSON.parse(event.activityPubActor), + req.body.eventDescription, + req.body.eventName, + req.body.eventLocation, + eventImageFilename, + startUTC, + endUTC, + req.body.timezone + ) + : null, + activityPubEvent: event.activityPubEvent + ? ap.updateActivityPubEvent( + JSON.parse(event.activityPubEvent), + req.body.eventName, + req.body.startUTC, + req.body.endUTC, + req.body.timezone + ) + : null, + }; + let diffText = + "

    This event was just updated with new information.

      "; let displayDate; if (event.name !== updatedEvent.name) { diffText += `
    • the event name changed to ${updatedEvent.name}
    • `; @@ -1040,11 +1369,15 @@ router.post('/editevent/:eventID/:editToken', (req, res) => { diffText += `
    • the location changed to ${updatedEvent.location}
    • `; } if (event.start.toISOString() !== updatedEvent.start.toISOString()) { - displayDate = moment.tz(updatedEvent.start, updatedEvent.timezone).format('dddd D MMMM YYYY h:mm a'); + displayDate = moment + .tz(updatedEvent.start, updatedEvent.timezone) + .format("dddd D MMMM YYYY h:mm a"); diffText += `
    • the start time changed to ${displayDate}
    • `; } if (event.end.toISOString() !== updatedEvent.end.toISOString()) { - displayDate = moment.tz(updatedEvent.end, updatedEvent.timezone).format('dddd D MMMM YYYY h:mm a'); + displayDate = moment + .tz(updatedEvent.end, updatedEvent.timezone) + .format("dddd D MMMM YYYY h:mm a"); diffText += `
    • the end time changed to ${displayDate}
    • `; } if (event.timezone !== updatedEvent.timezone) { @@ -1054,110 +1387,181 @@ router.post('/editevent/:eventID/:editToken', (req, res) => { diffText += `
    • the event description changed
    • `; } diffText += `
    `; - Event.findOneAndUpdate({ id: req.params.eventID }, updatedEvent, function (err, raw) { - if (err) { - addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); - res.send(err); + Event.findOneAndUpdate( + { id: req.params.eventID }, + updatedEvent, + function (err, raw) { + if (err) { + addToLog( + "editEvent", + "error", + "Attempt to edit event " + + req.params.eventID + + " failed with error: " + + err + ); + res.send(err); + } } - }) + ) .then(() => { - addToLog("editEvent", "success", "Event " + req.params.eventID + " edited"); + addToLog( + "editEvent", + "success", + "Event " + req.params.eventID + " edited" + ); // send update to ActivityPub subscribers Event.findOne({ id: req.params.eventID }, function (err, event) { if (!event) return; - let attendees = event.attendees.filter(el => el.id); + let attendees = event.attendees.filter((el) => el.id); if (!err) { // broadcast an identical message to all followers, will show in home timeline - const guidObject = crypto.randomBytes(16).toString('hex'); + const guidObject = crypto.randomBytes(16).toString("hex"); const jsonObject = { "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, - "name": `RSVP to ${event.name}`, - "type": "Note", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `${diffText} See here: https://${domain}/${req.params.eventID}`, - } - ap.broadcastCreateMessage(jsonObject, event.followers, eventID) + id: `https://${domain}/${req.params.eventID}/m/${guidObject}`, + name: `RSVP to ${event.name}`, + type: "Note", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `${diffText} See here: https://${domain}/${req.params.eventID}`, + }; + ap.broadcastCreateMessage(jsonObject, event.followers, eventID); // also broadcast an Update profile message to all followers so that at least Mastodon servers will update the local profile information const jsonUpdateObject = JSON.parse(event.activityPubActor); - ap.broadcastUpdateMessage(jsonUpdateObject, event.followers, eventID) + ap.broadcastUpdateMessage( + jsonUpdateObject, + event.followers, + eventID + ); // also broadcast an Update/Event for any calendar apps that are consuming our Events const jsonEventObject = JSON.parse(event.activityPubEvent); - ap.broadcastUpdateMessage(jsonEventObject, event.followers, eventID) + ap.broadcastUpdateMessage( + jsonEventObject, + event.followers, + eventID + ); // DM to attendees for (const attendee of attendees) { const jsonObject = { "@context": "https://www.w3.org/ns/activitystreams", - "name": `RSVP to ${event.name}`, - "type": "Note", - "content": `@${attendee.name} ${diffText} See here: https://${domain}/${req.params.eventID}`, - "tag": [{ "type": "Mention", "href": attendee.id, "name": attendee.name }] - } + name: `RSVP to ${event.name}`, + type: "Note", + content: `@${attendee.name} ${diffText} See here: https://${domain}/${req.params.eventID}`, + tag: [ + { + type: "Mention", + href: attendee.id, + name: attendee.name, + }, + ], + }; // send direct message to user ap.sendDirectMessage(jsonObject, attendee.id, eventID); } } - }) + }); // Send update to all attendees if (sendEmails) { Event.findOne({ id: req.params.eventID }).then((event) => { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); + const attendeeEmails = event.attendees + .filter((o) => o.status === "attending" && o.email) + .map((o) => o.email); if (attendeeEmails.length) { console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/editevent.handlebars', { diffText, eventID: req.params.eventID, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - address: contactEmail + req.app + .get("hbsInstance") + .renderView( + "./views/emails/editevent.handlebars", + { + diffText, + eventID: req.params.eventID, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: ${event.name} was just edited`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${event.name} was just edited`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); + } else { console.log("Nothing to send!"); } - }) + }); } res.writeHead(302, { - 'Location': '/' + req.params.eventID + '?e=' + req.params.editToken + Location: "/" + req.params.eventID + "?e=" + req.params.editToken, }); res.end(); }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); }); - } - else { + .catch((err) => { + console.error(err); + res.send("Sorry! Something went wrong!"); + addToLog( + "editEvent", + "error", + "Attempt to edit event " + + req.params.eventID + + " failed with error: " + + err + ); + }); + } else { // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: token does not match"); + res.send("Sorry! Something went wrong"); + addToLog( + "editEvent", + "error", + "Attempt to edit event " + + req.params.eventID + + " failed with error: token does not match" + ); } }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEvent", "error", "Attempt to edit event " + req.params.eventID + " failed with error: " + err); }); + .catch((err) => { + console.error(err); + res.send("Sorry! Something went wrong!"); + addToLog( + "editEvent", + "error", + "Attempt to edit event " + + req.params.eventID + + " failed with error: " + + err + ); + }); }); -router.post('/editeventgroup/:eventGroupID/:editToken', (req, res) => { +router.post("/editeventgroup/:eventGroupID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; - EventGroup.findOne(({ + EventGroup.findOne({ id: req.params.eventGroupID, - })) + }) .then((eventGroup) => { if (eventGroup.editToken === submittedEditToken) { // Token matches @@ -1172,81 +1576,149 @@ router.post('/editeventgroup/:eventGroupID/:editToken', (req, res) => { img .resize(920, Jimp.AUTO) // resize .quality(80) // set JPEG - .write('./public/events/' + eventGroupID + '.jpg'); // save + .write("./public/events/" + eventGroupID + ".jpg"); // save }); - eventGroupImageFilename = eventGroupID + '.jpg'; + eventGroupImageFilename = eventGroupID + ".jpg"; } const updatedEventGroup = { name: req.body.eventGroupName, description: req.body.eventGroupDescription, url: req.body.eventGroupURL, hostName: req.body.hostName, - image: eventGroupImageFilename - } - EventGroup.findOneAndUpdate({ id: req.params.eventGroupID }, updatedEventGroup, function (err, raw) { - if (err) { - addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); - res.send(err); + image: eventGroupImageFilename, + }; + EventGroup.findOneAndUpdate( + { id: req.params.eventGroupID }, + updatedEventGroup, + function (err, raw) { + if (err) { + addToLog( + "editEventGroup", + "error", + "Attempt to edit event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + res.send(err); + } } - }) + ) .then(() => { - addToLog("editEventGroup", "success", "Event group " + req.params.eventGroupID + " edited"); + addToLog( + "editEventGroup", + "success", + "Event group " + req.params.eventGroupID + " edited" + ); res.writeHead(302, { - 'Location': '/group/' + req.params.eventGroupID + '?e=' + req.params.editToken + Location: + "/group/" + + req.params.eventGroupID + + "?e=" + + req.params.editToken, }); res.end(); }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); }); - } - else { + .catch((err) => { + console.error(err); + res.send("Sorry! Something went wrong!"); + addToLog( + "editEventGroup", + "error", + "Attempt to edit event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + }); + } else { // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: token does not match"); + res.send("Sorry! Something went wrong"); + addToLog( + "editEventGroup", + "error", + "Attempt to edit event group " + + req.params.eventGroupID + + " failed with error: token does not match" + ); } }) - .catch((err) => { console.error(err); res.send('Sorry! Something went wrong!'); addToLog("editEventGroup", "error", "Attempt to edit event group " + req.params.eventGroupID + " failed with error: " + err); }); + .catch((err) => { + console.error(err); + res.send("Sorry! Something went wrong!"); + addToLog( + "editEventGroup", + "error", + "Attempt to edit event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + }); }); -router.post('/deleteimage/:eventID/:editToken', (req, res) => { +router.post("/deleteimage/:eventID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; - Event.findOne(({ + Event.findOne({ id: req.params.eventID, - })) - .then((event) => { - if (event.editToken === submittedEditToken) { - // Token matches - if (event.image) { - eventImage = event.image; - } else { - res.status(500).send('This event doesn\'t have a linked image. What are you even doing'); + }).then((event) => { + if (event.editToken === submittedEditToken) { + // Token matches + if (event.image) { + eventImage = event.image; + } else { + res + .status(500) + .send( + "This event doesn't have a linked image. What are you even doing" + ); + } + fs.unlink(global.appRoot + "/public/events/" + eventImage, (err) => { + if (err) { + res.status(500).send(err); + addToLog( + "deleteEventImage", + "error", + "Attempt to delete event image for event " + + req.params.eventID + + " failed with error: " + + err + ); } - fs.unlink(global.appRoot + '/public/events/' + eventImage, (err) => { - if (err) { + // Image removed + addToLog( + "deleteEventImage", + "success", + "Image for event " + req.params.eventID + " deleted" + ); + event.image = ""; + event + .save() + .then((response) => { + res.status(200).send("Success"); + }) + .catch((err) => { res.status(500).send(err); - addToLog("deleteEventImage", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); - } - // Image removed - addToLog("deleteEventImage", "success", "Image for event " + req.params.eventID + " deleted"); - event.image = ""; - event.save() - .then(response => { - res.status(200).send('Success'); - }) - .catch(err => { - res.status(500).send(err); - addToLog("deleteEventImage", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); - }) - }); - } - }); + addToLog( + "deleteEventImage", + "error", + "Attempt to delete event image for event " + + req.params.eventID + + " failed with error: " + + err + ); + }); + }); + } + }); }); -router.post('/deleteevent/:eventID/:editToken', (req, res) => { +router.post("/deleteevent/:eventID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; let eventImage; - Event.findOne(({ + Event.findOne({ id: req.params.eventID, - })) + }) .then((event) => { if (event.editToken === submittedEditToken) { // Token matches @@ -1257,147 +1729,286 @@ router.post('/deleteevent/:eventID/:editToken', (req, res) => { } // broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information - const guidUpdateObject = crypto.randomBytes(16).toString('hex'); + const guidUpdateObject = crypto.randomBytes(16).toString("hex"); const jsonUpdateObject = JSON.parse(event.activityPubActor); // first broadcast AP messages, THEN delete from DB - ap.broadcastDeleteMessage(jsonUpdateObject, event.followers, req.params.eventID, function (statuses) { - Event.deleteOne({ id: req.params.eventID }, function (err, raw) { - if (err) { - res.send(err); - addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); - } - }) - .then(() => { - // Delete image - if (eventImage) { - fs.unlink(global.appRoot + '/public/events/' + eventImage, (err) => { - if (err) { - res.send(err); - addToLog("deleteEvent", "error", "Attempt to delete event image for event " + req.params.eventID + " failed with error: " + err); - } - // Image removed - addToLog("deleteEvent", "success", "Event " + req.params.eventID + " deleted"); - }) + ap.broadcastDeleteMessage( + jsonUpdateObject, + event.followers, + req.params.eventID, + function (statuses) { + Event.deleteOne({ id: req.params.eventID }, function (err, raw) { + if (err) { + res.send(err); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event " + + req.params.eventID + + " failed with error: " + + err + ); } - res.writeHead(302, { - 'Location': '/' - }); - res.end(); + }) + .then(() => { + // Delete image + if (eventImage) { + fs.unlink( + global.appRoot + "/public/events/" + eventImage, + (err) => { + if (err) { + res.send(err); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event image for event " + + req.params.eventID + + " failed with error: " + + err + ); + } + // Image removed + addToLog( + "deleteEvent", + "success", + "Event " + req.params.eventID + " deleted" + ); + } + ); + } + res.writeHead(302, { + Location: "/", + }); + res.end(); - // Send emails here otherwise they don't exist lol - if (sendEmails) { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); + // Send emails here otherwise they don't exist lol + if (sendEmails) { + const attendeeEmails = event.attendees + .filter((o) => o.status === "attending" && o.email) + .map((o) => o.email); if (attendeeEmails.length) { console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/deleteevent.handlebars', { siteName, siteLogo, domain, eventName: event.name, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - address: contactEmail + req.app + .get("hbsInstance") + .renderView( + "./views/emails/deleteevent.handlebars", + { + siteName, + siteLogo, + domain, + eventName: event.name, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: ${event.name} was deleted`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${event.name} was deleted`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); + } else { console.log("Nothing to send!"); } - } - }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); }); - }); - } - else { + } + }) + .catch((err) => { + res.send( + "Sorry! Something went wrong (error deleting): " + err + ); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event " + + req.params.eventID + + " failed with error: " + + err + ); + }); + } + ); + } else { // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: token does not match"); + res.send("Sorry! Something went wrong"); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event " + + req.params.eventID + + " failed with error: token does not match" + ); } }) - .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteEvent", "error", "Attempt to delete event " + req.params.eventID + " failed with error: " + err); }); + .catch((err) => { + res.send("Sorry! Something went wrong: " + err); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event " + + req.params.eventID + + " failed with error: " + + err + ); + }); }); -router.post('/deleteeventgroup/:eventGroupID/:editToken', (req, res) => { +router.post("/deleteeventgroup/:eventGroupID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; - EventGroup.findOne(({ + EventGroup.findOne({ id: req.params.eventGroupID, - })) + }) .then(async (eventGroup) => { if (eventGroup.editToken === submittedEditToken) { // Token matches let linkedEvents = await Event.find({ eventGroup: eventGroup._id }); - let linkedEventIDs = linkedEvents.map(event => event._id); + let linkedEventIDs = linkedEvents.map((event) => event._id); let eventGroupImage = false; if (eventGroup.image) { eventGroupImage = eventGroup.image; } - EventGroup.deleteOne({ id: req.params.eventGroupID }, function (err, raw) { - if (err) { - res.send(err); - addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); + EventGroup.deleteOne( + { id: req.params.eventGroupID }, + function (err, raw) { + if (err) { + res.send(err); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + } } - }) + ) .then(() => { // Delete image if (eventGroupImage) { - fs.unlink(global.appRoot + '/public/events/' + eventGroupImage, (err) => { - if (err) { - res.send(err); - addToLog("deleteEventGroup", "error", "Attempt to delete event image for event group " + req.params.eventGroupID + " failed with error: " + err); + fs.unlink( + global.appRoot + "/public/events/" + eventGroupImage, + (err) => { + if (err) { + res.send(err); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event image for event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + } } - }) + ); } - Event.update({ _id: { $in: linkedEventIDs } }, { $set: { eventGroup: null } }, { multi: true }) - .then(response => { + Event.update( + { _id: { $in: linkedEventIDs } }, + { $set: { eventGroup: null } }, + { multi: true } + ) + .then((response) => { console.log(response); - addToLog("deleteEventGroup", "success", "Event group " + req.params.eventGroupID + " deleted"); + addToLog( + "deleteEventGroup", + "success", + "Event group " + req.params.eventGroupID + " deleted" + ); res.writeHead(302, { - 'Location': '/' + Location: "/", }); res.end(); }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); + .catch((err) => { + res.send( + "Sorry! Something went wrong (error deleting): " + err + ); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + }); }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); - } - else { + .catch((err) => { + res.send("Sorry! Something went wrong (error deleting): " + err); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + }); + } else { // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: token does not match"); + res.send("Sorry! Something went wrong"); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: token does not match" + ); } }) - .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteEventGroup", "error", "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err); }); + .catch((err) => { + res.send("Sorry! Something went wrong: " + err); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: " + + err + ); + }); }); -router.post('/attendee/provision', async (req, res) => { - const removalPassword = niceware.generatePassphrase(6).join('-'); +router.post("/attendee/provision", async (req, res) => { + const removalPassword = niceware.generatePassphrase(6).join("-"); const newAttendee = { - status: 'provisioned', + status: "provisioned", removalPassword, created: Date.now(), }; - const event = await Event.findOne({ id: req.query.eventID }).catch(e => { - addToLog("provisionEventAttendee", "error", "Attempt to provision attendee in event " + req.query.eventID + " failed with error: " + e); + const event = await Event.findOne({ id: req.query.eventID }).catch((e) => { + addToLog( + "provisionEventAttendee", + "error", + "Attempt to provision attendee in event " + + req.query.eventID + + " failed with error: " + + e + ); return res.sendStatus(500); }); @@ -1406,95 +2017,157 @@ router.post('/attendee/provision', async (req, res) => { } event.attendees.push(newAttendee); - await event.save().catch(e => { + await event.save().catch((e) => { console.log(e); - addToLog("provisionEventAttendee", "error", "Attempt to provision attendee in event " + req.query.eventID + " failed with error: " + e); + addToLog( + "provisionEventAttendee", + "error", + "Attempt to provision attendee in event " + + req.query.eventID + + " failed with error: " + + e + ); return res.sendStatus(500); }); - addToLog("provisionEventAttendee", "success", "Attendee provisioned in event " + req.query.eventID); + addToLog( + "provisionEventAttendee", + "success", + "Attendee provisioned in event " + req.query.eventID + ); // Return the removal password and the number of free spots remaining let freeSpots; if (event.maxAttendees !== null && event.maxAttendees !== undefined) { - freeSpots = event.maxAttendees - event.attendees.reduce((acc, a) => acc + (a.status === 'attending' ? (a.number || 1) : 0), 0); + freeSpots = + event.maxAttendees - + event.attendees.reduce( + (acc, a) => acc + (a.status === "attending" ? a.number || 1 : 0), + 0 + ); } else { freeSpots = undefined; } return res.json({ removalPassword, freeSpots }); }); -router.post('/attendevent/:eventID', async (req, res) => { +router.post("/attendevent/:eventID", async (req, res) => { // Do not allow empty removal passwords if (!req.body.removalPassword) { return res.sendStatus(500); } - const event = await Event.findOne({ id: req.params.eventID }).catch(e => { - addToLog("attendEvent", "error", "Attempt to attend event " + req.params.eventID + " failed with error: " + e); + const event = await Event.findOne({ id: req.params.eventID }).catch((e) => { + addToLog( + "attendEvent", + "error", + "Attempt to attend event " + + req.params.eventID + + " failed with error: " + + e + ); return res.sendStatus(500); }); if (!event) { return res.sendStatus(404); } - const attendee = event.attendees.find(a => a.removalPassword === req.body.removalPassword); + const attendee = event.attendees.find( + (a) => a.removalPassword === req.body.removalPassword + ); if (!attendee) { return res.sendStatus(404); } // Do we have enough free spots in this event to accomodate this attendee? // First, check if the event has a max number of attendees if (event.maxAttendees !== null && event.maxAttendees !== undefined) { - const freeSpots = event.maxAttendees - event.attendees.reduce((acc, a) => acc + (a.status === 'attending' ? (a.number || 1) : 0), 0); + const freeSpots = + event.maxAttendees - + event.attendees.reduce( + (acc, a) => acc + (a.status === "attending" ? a.number || 1 : 0), + 0 + ); if (req.body.attendeeNumber > freeSpots) { return res.sendStatus(403); } } - Event.findOneAndUpdate({ id: req.params.eventID, 'attendees.removalPassword': req.body.removalPassword }, { - "$set": { - "attendees.$.status": "attending", - "attendees.$.name": req.body.attendeeName, - "attendees.$.email": req.body.attendeeEmail, - "attendees.$.number": req.body.attendeeNumber, + Event.findOneAndUpdate( + { + id: req.params.eventID, + "attendees.removalPassword": req.body.removalPassword, + }, + { + $set: { + "attendees.$.status": "attending", + "attendees.$.name": req.body.attendeeName, + "attendees.$.email": req.body.attendeeEmail, + "attendees.$.number": req.body.attendeeNumber, + }, } - }).then((event) => { - addToLog("addEventAttendee", "success", "Attendee added to event " + req.params.eventID); - if (sendEmails) { - if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/addeventattendee.handlebars', { eventID: req.params.eventID, siteName, siteLogo, domain, removalPassword: req.body.removalPassword, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You're RSVPed to ${event.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + ) + .then((event) => { + addToLog( + "addEventAttendee", + "success", + "Attendee added to event " + req.params.eventID + ); + if (sendEmails) { + if (req.body.attendeeEmail) { + req.app + .get("hbsInstance") + .renderView( + "./views/emails/addeventattendee.handlebars", + { + eventID: req.params.eventID, + siteName, + siteLogo, + domain, + removalPassword: req.body.removalPassword, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You're RSVPed to ${event.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); + } } - } - res.redirect(`/${req.params.eventID}`); - }) + res.redirect(`/${req.params.eventID}`); + }) .catch((error) => { - res.send('Database error, please try again :('); - addToLog("addEventAttendee", "error", "Attempt to add attendee to event " + req.params.eventID + " failed with error: " + error); + res.send("Database error, please try again :("); + addToLog( + "addEventAttendee", + "error", + "Attempt to add attendee to event " + + req.params.eventID + + " failed with error: " + + error + ); }); }); -router.post('/unattendevent/:eventID', (req, res) => { +router.post("/unattendevent/:eventID", (req, res) => { const removalPassword = req.body.removalPassword; // Don't allow blank removal passwords! if (!removalPassword) { @@ -1505,154 +2178,231 @@ router.post('/unattendevent/:eventID', (req, res) => { { id: req.params.eventID }, { $pull: { attendees: { removalPassword } } } ) - .then(response => { - console.log(response) - addToLog("unattendEvent", "success", "Attendee removed self from event " + req.params.eventID); + .then((response) => { + console.log(response); + addToLog( + "unattendEvent", + "success", + "Attendee removed self from event " + req.params.eventID + ); if (sendEmails) { if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/unattendevent.handlebars', { eventID: req.params.eventID, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, + req.app + .get("hbsInstance") + .renderView( + "./views/emails/unattendevent.handlebars", + { + eventID: req.params.eventID, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); } } res.writeHead(302, { - 'Location': '/' + req.params.eventID + Location: "/" + req.params.eventID, }); res.end(); }) .catch((err) => { - res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee from event " + req.params.eventID + " failed with error: " + err); + res.send("Database error, please try again :("); + addToLog( + "removeEventAttendee", + "error", + "Attempt to remove attendee from event " + + req.params.eventID + + " failed with error: " + + err + ); }); }); // this is a one-click unattend that requires a secret URL that only the person who RSVPed over // activitypub knows -router.get('/oneclickunattendevent/:eventID/:attendeeID', (req, res) => { +router.get("/oneclickunattendevent/:eventID/:attendeeID", (req, res) => { // Mastodon will "click" links that sent to its users, presumably as a prefetch? // Anyway, this ignores the automated clicks that are done without the user's knowledge - if (req.headers['user-agent'] && req.headers['user-agent'].includes('Mastodon')) { + if ( + req.headers["user-agent"] && + req.headers["user-agent"].includes("Mastodon") + ) { return res.sendStatus(200); } Event.update( { id: req.params.eventID }, { $pull: { attendees: { _id: req.params.attendeeID } } } ) - .then(response => { - addToLog("oneClickUnattend", "success", "Attendee removed via one click unattend " + req.params.eventID); + .then((response) => { + addToLog( + "oneClickUnattend", + "success", + "Attendee removed via one click unattend " + req.params.eventID + ); if (sendEmails) { // currently this is never called because we don't have the email address if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/removeeventattendee.handlebars', { eventName: req.params.eventName, siteName, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, + req.app + .get("hbsInstance") + .renderView( + "./views/emails/removeeventattendee.handlebars", + { + eventName: req.params.eventName, + siteName, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); } } res.writeHead(302, { - 'Location': '/' + req.params.eventID + Location: "/" + req.params.eventID, }); res.end(); }) .catch((err) => { - res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee by admin from event " + req.params.eventID + " failed with error: " + err); + res.send("Database error, please try again :("); + addToLog( + "removeEventAttendee", + "error", + "Attempt to remove attendee by admin from event " + + req.params.eventID + + " failed with error: " + + err + ); }); }); -router.post('/removeattendee/:eventID/:attendeeID', (req, res) => { +router.post("/removeattendee/:eventID/:attendeeID", (req, res) => { Event.update( { id: req.params.eventID }, { $pull: { attendees: { _id: req.params.attendeeID } } } ) - .then(response => { - console.log(response) - addToLog("removeEventAttendee", "success", "Attendee removed by admin from event " + req.params.eventID); + .then((response) => { + console.log(response); + addToLog( + "removeEventAttendee", + "success", + "Attendee removed by admin from event " + req.params.eventID + ); if (sendEmails) { // currently this is never called because we don't have the email address if (req.body.attendeeEmail) { - req.app.get('hbsInstance').renderView('./views/emails/removeeventattendee.handlebars', { eventName: req.params.eventName, siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, + req.app + .get("hbsInstance") + .renderView( + "./views/emails/removeeventattendee.handlebars", + { + eventName: req.params.eventName, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); } } res.writeHead(302, { - 'Location': '/' + req.params.eventID + Location: "/" + req.params.eventID, }); res.end(); }) .catch((err) => { - res.send('Database error, please try again :('); addToLog("removeEventAttendee", "error", "Attempt to remove attendee by admin from event " + req.params.eventID + " failed with error: " + err); + res.send("Database error, please try again :("); + addToLog( + "removeEventAttendee", + "error", + "Attempt to remove attendee by admin from event " + + req.params.eventID + + " failed with error: " + + err + ); }); }); /* * Create an email subscription on an event group. */ -router.post('/subscribe/:eventGroupID', (req, res) => { +router.post("/subscribe/:eventGroupID", (req, res) => { const subscriber = { email: req.body.emailAddress, }; @@ -1660,9 +2410,9 @@ router.post('/subscribe/:eventGroupID', (req, res) => { return res.sendStatus(500); } - EventGroup.findOne(({ + EventGroup.findOne({ id: req.params.eventGroupID, - })) + }) .then((eventGroup) => { if (!eventGroup) { return res.sendStatus(404); @@ -1670,36 +2420,60 @@ router.post('/subscribe/:eventGroupID', (req, res) => { eventGroup.subscribers.push(subscriber); eventGroup.save(); if (sendEmails) { - req.app.get('hbsInstance').renderView('./views/emails/subscribed.handlebars', { eventGroupName: eventGroup.name, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(subscriber.email), siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: subscriber.email, - from: { - name: siteName, - email: contactEmail, + req.app + .get("hbsInstance") + .renderView( + "./views/emails/subscribed.handlebars", + { + eventGroupName: eventGroup.name, + eventGroupID: eventGroup.id, + emailAddress: encodeURIComponent(subscriber.email), + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", }, - subject: `${siteName}: You have subscribed to an event group`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.send(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); + function (err, html) { + const msg = { + to: subscriber.email, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have subscribed to an event group`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); } - return res.redirect(`/group/${eventGroup.id}`) + return res.redirect(`/group/${eventGroup.id}`); }) .catch((error) => { - addToLog("addSubscription", "error", "Attempt to subscribe " + req.body.emailAddress + " to event group " + req.params.eventGroupID + " failed with error: " + error); + addToLog( + "addSubscription", + "error", + "Attempt to subscribe " + + req.body.emailAddress + + " to event group " + + req.params.eventGroupID + + " failed with error: " + + error + ); return res.sendStatus(500); }); }); @@ -1707,7 +2481,7 @@ router.post('/subscribe/:eventGroupID', (req, res) => { /* * Delete an existing email subscription on an event group. */ -router.get('/unsubscribe/:eventGroupID', (req, res) => { +router.get("/unsubscribe/:eventGroupID", (req, res) => { const email = req.query.email; console.log(email); if (!email) { @@ -1718,256 +2492,385 @@ router.get('/unsubscribe/:eventGroupID', (req, res) => { { id: req.params.eventGroupID }, { $pull: { subscribers: { email } } } ) - .then(response => { - return res.redirect('/'); + .then((response) => { + return res.redirect("/"); }) .catch((error) => { - addToLog("removeSubscription", "error", "Attempt to unsubscribe " + req.query.email + " from event group " + req.params.eventGroupID + " failed with error: " + error); + addToLog( + "removeSubscription", + "error", + "Attempt to unsubscribe " + + req.query.email + + " from event group " + + req.params.eventGroupID + + " failed with error: " + + error + ); return res.sendStatus(500); }); }); -router.post('/post/comment/:eventID', (req, res) => { +router.post("/post/comment/:eventID", (req, res) => { let commentID = nanoid(); const newComment = { id: commentID, author: req.body.commentAuthor, content: req.body.commentContent, - timestamp: moment() + timestamp: moment(), }; - Event.findOne({ - id: req.params.eventID, - }, function (err, event) { - if (!event) return; - event.comments.push(newComment); - event.save() - .then(() => { - addToLog("addEventComment", "success", "Comment added to event " + req.params.eventID); - // broadcast an identical message to all followers, will show in their home timeline - // and in the home timeline of the event - const guidObject = crypto.randomBytes(16).toString('hex'); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, - "name": `Comment on ${event.name}`, - "type": "Note", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `

    ${req.body.commentAuthor} commented: ${req.body.commentContent}.

    See the full conversation here.

    `, - } - ap.broadcastCreateMessage(jsonObject, event.followers, req.params.eventID) - if (sendEmails) { - Event.findOne({ id: req.params.eventID }).then((event) => { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/addeventcomment.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, commentAuthor: req.body.commentAuthor, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New comment in ${event.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { - console.log("Nothing to send!"); - } + Event.findOne( + { + id: req.params.eventID, + }, + function (err, event) { + if (!event) return; + event.comments.push(newComment); + event + .save() + .then(() => { + addToLog( + "addEventComment", + "success", + "Comment added to event " + req.params.eventID + ); + // broadcast an identical message to all followers, will show in their home timeline + // and in the home timeline of the event + const guidObject = crypto.randomBytes(16).toString("hex"); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${req.params.eventID}/m/${guidObject}`, + name: `Comment on ${event.name}`, + type: "Note", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `

    ${req.body.commentAuthor} commented: ${req.body.commentContent}.

    See the full conversation here.

    `, + }; + ap.broadcastCreateMessage( + jsonObject, + event.followers, + req.params.eventID + ); + if (sendEmails) { + Event.findOne({ id: req.params.eventID }).then((event) => { + const attendeeEmails = event.attendees + .filter((o) => o.status === "attending" && o.email) + .map((o) => o.email); + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.app + .get("hbsInstance") + .renderView( + "./views/emails/addeventcomment.handlebars", + { + siteName, + siteLogo, + domain, + eventID: req.params.eventID, + commentAuthor: req.body.commentAuthor, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New comment in ${event.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); + } else { + console.log("Nothing to send!"); + } + }); + } + res.writeHead(302, { + Location: "/" + req.params.eventID, }); - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID + res.end(); + }) + .catch((err) => { + res.send("Database error, please try again :(" + err); + addToLog( + "addEventComment", + "error", + "Attempt to add comment to event " + + req.params.eventID + + " failed with error: " + + err + ); }); - res.end(); - }) - .catch((err) => { res.send('Database error, please try again :(' + err); addToLog("addEventComment", "error", "Attempt to add comment to event " + req.params.eventID + " failed with error: " + err); }); - }); + } + ); }); -router.post('/post/reply/:eventID/:commentID', (req, res) => { +router.post("/post/reply/:eventID/:commentID", (req, res) => { let replyID = nanoid(); let commentID = req.params.commentID; const newReply = { id: replyID, author: req.body.replyAuthor, content: req.body.replyContent, - timestamp: moment() + timestamp: moment(), }; - Event.findOne({ - id: req.params.eventID, - }, function (err, event) { - if (!event) return; - var parentComment = event.comments.id(commentID); - parentComment.replies.push(newReply); - event.save() - .then(() => { - addToLog("addEventReply", "success", "Reply added to comment " + commentID + " in event " + req.params.eventID); - // broadcast an identical message to all followers, will show in their home timeline - const guidObject = crypto.randomBytes(16).toString('hex'); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - "id": `https://${domain}/${req.params.eventID}/m/${guidObject}`, - "name": `Comment on ${event.name}`, - "type": "Note", - 'cc': 'https://www.w3.org/ns/activitystreams#Public', - "content": `

    ${req.body.replyAuthor} commented: ${req.body.replyContent}

    See the full conversation here.

    `, - } - ap.broadcastCreateMessage(jsonObject, event.followers, req.params.eventID) - if (sendEmails) { - Event.findOne({ id: req.params.eventID }).then((event) => { - const attendeeEmails = event.attendees.filter(o => o.status === 'attending' && o.email).map(o => o.email); - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.app.get('hbsInstance').renderView('./views/emails/addeventcomment.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, commentAuthor: req.body.replyAuthor, cache: true, layout: 'email.handlebars' }, function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New comment in ${event.name}`, - html, - }; - switch (mailService) { - case 'sendgrid': - sgMail.sendMultiple(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case 'nodemailer': - nodemailerTransporter.sendMail(msg).catch(e => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - }); - } - else { - console.log("Nothing to send!"); - } + Event.findOne( + { + id: req.params.eventID, + }, + function (err, event) { + if (!event) return; + var parentComment = event.comments.id(commentID); + parentComment.replies.push(newReply); + event + .save() + .then(() => { + addToLog( + "addEventReply", + "success", + "Reply added to comment " + + commentID + + " in event " + + req.params.eventID + ); + // broadcast an identical message to all followers, will show in their home timeline + const guidObject = crypto.randomBytes(16).toString("hex"); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${req.params.eventID}/m/${guidObject}`, + name: `Comment on ${event.name}`, + type: "Note", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `

    ${req.body.replyAuthor} commented: ${req.body.replyContent}

    See the full conversation here.

    `, + }; + ap.broadcastCreateMessage( + jsonObject, + event.followers, + req.params.eventID + ); + if (sendEmails) { + Event.findOne({ id: req.params.eventID }).then((event) => { + const attendeeEmails = event.attendees + .filter((o) => o.status === "attending" && o.email) + .map((o) => o.email); + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.app + .get("hbsInstance") + .renderView( + "./views/emails/addeventcomment.handlebars", + { + siteName, + siteLogo, + domain, + eventID: req.params.eventID, + commentAuthor: req.body.replyAuthor, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New comment in ${event.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); + } else { + console.log("Nothing to send!"); + } + }); + } + res.writeHead(302, { + Location: "/" + req.params.eventID, }); - } - res.writeHead(302, { - 'Location': '/' + req.params.eventID + res.end(); + }) + .catch((err) => { + res.send("Database error, please try again :("); + addToLog( + "addEventReply", + "error", + "Attempt to add reply to comment " + + commentID + + " in event " + + req.params.eventID + + " failed with error: " + + err + ); }); - res.end(); - }) - .catch((err) => { res.send('Database error, please try again :('); addToLog("addEventReply", "error", "Attempt to add reply to comment " + commentID + " in event " + req.params.eventID + " failed with error: " + err); }); - }); + } + ); }); -router.post('/deletecomment/:eventID/:commentID/:editToken', (req, res) => { +router.post("/deletecomment/:eventID/:commentID/:editToken", (req, res) => { let submittedEditToken = req.params.editToken; - Event.findOne(({ + Event.findOne({ id: req.params.eventID, - })) + }) .then((event) => { if (event.editToken === submittedEditToken) { // Token matches event.comments.id(req.params.commentID).remove(); - event.save() + event + .save() .then(() => { - addToLog("deleteComment", "success", "Comment deleted from event " + req.params.eventID); + addToLog( + "deleteComment", + "success", + "Comment deleted from event " + req.params.eventID + ); res.writeHead(302, { - 'Location': '/' + req.params.eventID + '?e=' + req.params.editToken + Location: "/" + req.params.eventID + "?e=" + req.params.editToken, }); res.end(); }) - .catch((err) => { res.send('Sorry! Something went wrong (error deleting): ' + err); addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: " + err); }); - } - else { + .catch((err) => { + res.send("Sorry! Something went wrong (error deleting): " + err); + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.params.commentID + + "from event " + + req.params.eventID + + " failed with error: " + + err + ); + }); + } else { // Token doesn't match - res.send('Sorry! Something went wrong'); - addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: token does not match"); + res.send("Sorry! Something went wrong"); + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.params.commentID + + "from event " + + req.params.eventID + + " failed with error: token does not match" + ); } }) - .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: " + err); }); + .catch((err) => { + res.send("Sorry! Something went wrong: " + err); + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.params.commentID + + "from event " + + req.params.eventID + + " failed with error: " + + err + ); + }); }); -router.post('/activitypub/inbox', (req, res) => { +router.post("/activitypub/inbox", (req, res) => { if (!isFederated) return res.sendStatus(404); // validate the incoming message - const signature = req.get('Signature'); - let signature_header = signature.split(',').map(pair => { - return pair.split('=').map(value => { - return value.replace(/^"/g, '').replace(/"$/g, '') - }); - }).reduce((acc, el) => { - acc[el[0]] = el[1]; - return acc; - }, {}); + const signature = req.get("Signature"); + let signature_header = signature + .split(",") + .map((pair) => { + return pair.split("=").map((value) => { + return value.replace(/^"/g, "").replace(/"$/g, ""); + }); + }) + .reduce((acc, el) => { + acc[el[0]] = el[1]; + return acc; + }, {}); // get the actor // TODO if this is a Delete for an Actor this won't work - request({ - url: signature_header.keyId, - headers: { - 'Accept': 'application/activity+json', - 'Content-Type': 'application/activity+json' - } - }, function (error, response, actor) { - let publicKey = ''; - - try { - if (JSON.parse(actor).publicKey) { - publicKey = JSON.parse(actor).publicKey.publicKeyPem; + request( + { + url: signature_header.keyId, + headers: { + Accept: "application/activity+json", + "Content-Type": "application/activity+json", + }, + }, + function (error, response, actor) { + let publicKey = ""; + + try { + if (JSON.parse(actor).publicKey) { + publicKey = JSON.parse(actor).publicKey.publicKeyPem; + } + } catch (err) { + return res.status(500).send("Actor could not be parsed" + err); } - } - catch (err) { - return res.status(500).send('Actor could not be parsed' + err); - } - let comparison_string = signature_header.headers.split(' ').map(header => { - if (header === '(request-target)') { - return '(request-target): post /activitypub/inbox'; - } - else { - return `${header}: ${req.get(header)}` - } - }).join('\n'); - - const verifier = crypto.createVerify('RSA-SHA256') - verifier.update(comparison_string, 'ascii') - const publicKeyBuf = new Buffer(publicKey, 'ascii') - const signatureBuf = new Buffer(signature_header.signature, 'base64') - try { - const result = verifier.verify(publicKeyBuf, signatureBuf) - if (result) { - // actually process the ActivityPub message now that it's been verified - ap.processInbox(req, res); - } - else { - return res.status(401).send('Signature could not be verified.'); + let comparison_string = signature_header.headers + .split(" ") + .map((header) => { + if (header === "(request-target)") { + return "(request-target): post /activitypub/inbox"; + } else { + return `${header}: ${req.get(header)}`; + } + }) + .join("\n"); + + const verifier = crypto.createVerify("RSA-SHA256"); + verifier.update(comparison_string, "ascii"); + const publicKeyBuf = new Buffer(publicKey, "ascii"); + const signatureBuf = new Buffer(signature_header.signature, "base64"); + try { + const result = verifier.verify(publicKeyBuf, signatureBuf); + if (result) { + // actually process the ActivityPub message now that it's been verified + ap.processInbox(req, res); + } else { + return res.status(401).send("Signature could not be verified."); + } + } catch (err) { + return res.status(401).send("Signature could not be verified: " + err); } } - catch (err) { - return res.status(401).send('Signature could not be verified: ' + err); - } - }); + ); }); router.use(function (req, res, next) { res.status(404); - res.render('404', { url: req.url }); + res.render("404", { url: req.url }); return; }); diff --git a/src/start.js b/src/start.js index 363062e..a6ecfbf 100755 --- a/src/start.js +++ b/src/start.js @@ -1,32 +1,38 @@ -require('dotenv').config(); +require("dotenv").config(); -const path = require('path'); +const path = require("path"); -const mongoose = require('mongoose'); +const mongoose = require("mongoose"); -const databaseCredentials = require('./config/database.js'); -const port = require('./config/domain.js').port; +const databaseCredentials = require("./config/database.js"); +const port = require("./config/domain.js").port; -mongoose.connect(databaseCredentials.url, { useNewUrlParser: true, useUnifiedTopology: true }); -mongoose.set('useCreateIndex', true); +mongoose.connect(databaseCredentials.url, { + useNewUrlParser: true, + useUnifiedTopology: true, +}); +mongoose.set("useCreateIndex", true); mongoose.Promise = global.Promise; mongoose.connection - .on('connected', () => { - console.log('Mongoose connection open!'); + .on("connected", () => { + console.log("Mongoose connection open!"); }) - .on('error', (err) => { - console.log('Connection error: ${err.message}'); + .on("error", (err) => { + console.log("Connection error: ${err.message}"); }); - -require('./models/Event'); -require('./models/Log'); -require('./models/EventGroup'); +require("./models/Event"); +require("./models/Log"); +require("./models/EventGroup"); -const app = require('./app.js'); +const app = require("./app.js"); global.appRoot = path.resolve(__dirname); const server = app.listen(port, () => { - console.log(`Welcome to gathio! The app is now running on http://localhost:${server.address().port}`); + console.log( + `Welcome to gathio! The app is now running on http://localhost:${ + server.address().port + }` + ); }); -- cgit v1.2.3 From 240195f0ca3cbc0af2f084a8beb0d2f11fe48ec5 Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:06 +0100 Subject: Ignore views directory --- .prettierignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 210b0f6..2ca284f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ dist -public \ No newline at end of file +public +views \ No newline at end of file -- cgit v1.2.3 From 116acdbd18fe26ddf4748e49f5fbdceaee720eaa Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:06 +0100 Subject: More linting --- .eslintrc.json | 33 +- .prettierignore | 3 +- .travis.yml | 4 +- FEDERATION.md | 30 +- docker-compose.yml | 24 +- src/routes.js | 992 ++++++++++++++++++++++++++--------------------------- tsconfig.json | 6 +- 7 files changed, 533 insertions(+), 559 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index f1e3d15..d3fb99f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,18 +1,17 @@ { - "env": { - "browser": true, - "es6": true, - "node": true - }, - "extends": "eslint:recommended", - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "rules": { - } -} \ No newline at end of file + "env": { + "browser": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": {} +} diff --git a/.prettierignore b/.prettierignore index 2ca284f..96fa736 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ dist public -views \ No newline at end of file +views +pnpm-lock.yaml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index cdf8b48..d6fe659 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: shell os: linux services: - - docker + - docker script: - - ./test.sh \ No newline at end of file + - ./test.sh diff --git a/FEDERATION.md b/FEDERATION.md index fde2d2d..4c3a10b 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -6,18 +6,18 @@ This document is meant to be a reference for all the ActivityPub federation-rela To keep things simple, sometimes you will see things formatted like `Create/Note` or `Delete/Event` or `Undo/Follow`. The thing before the slash is the Activity, and the thing after the slash is the Object inside the Activity, in an `object` property. So these are to be read as follows: -* `Create/Note`: a `Create` activity containing a `Note` in the `object` field -* `Delete/Event`: a `Delete` activity containing an `Event` in the `object` field -* `Undo/Follow`: an `Undo` activity containing a `Follow` in the `object` field +- `Create/Note`: a `Create` activity containing a `Note` in the `object` field +- `Delete/Event`: a `Delete` activity containing an `Event` in the `object` field +- `Undo/Follow`: an `Undo` activity containing a `Follow` in the `object` field When the word "broadcast" is used in this document, it means to send an Activity to individual inbox of each of the followers of a given Actor. This document has four main sections: -* __Federation philosophy__ lays out the general model of how this is intended to federate -* __General Actor information__ contains the basics of what to expect from our `Actor` objects -* __Inbox behavior__ lists every incoming ActivityPub activity that the server recognizes, and tells you what it does in response to that activity, including any other ActivityPub activities it sends back out. -* __Activities triggered from the web app__ tells you what circumstances on the web application cause the server to emit ActivityPub activities. (For example, when an event is updated via the web application, it lets all the ActivityPub followers know that the event has been updated.) +- **Federation philosophy** lays out the general model of how this is intended to federate +- **General Actor information** contains the basics of what to expect from our `Actor` objects +- **Inbox behavior** lists every incoming ActivityPub activity that the server recognizes, and tells you what it does in response to that activity, including any other ActivityPub activities it sends back out. +- **Activities triggered from the web app** tells you what circumstances on the web application cause the server to emit ActivityPub activities. (For example, when an event is updated via the web application, it lets all the ActivityPub followers know that the event has been updated.) Please note: there is an unfortunate collision between the English language and the ActivityPub spec that can make this document confusing. When this document uses the word 'event' with a lowercase-e and not in monospace, it refers to the thing that is being tracked in gathio: events that are being organized. When this document uses the word `Event` with a capital E and in monospace, it refers to the [`Event` object defined in the ActivityStreams Vocabulary spec](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event). @@ -37,7 +37,7 @@ Every event has an Actor. The Actor looks like this: ```json { - "@context":[ + "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1" ], @@ -50,12 +50,12 @@ Every event has an Actor. The Actor looks like this: "summary": "

    DESCRIPTION

    \n

    Location: LOCATION.

    Starting DATETIME (human readable).

    ", "name": "EVENTNAME", "featured": "https://DOMAIN/EVENTID/featured", - "publicKey":{ + "publicKey": { "id": "https://DOMAIN/EVENTID#main-key", "owner": "https://DOMAIN/EVENTID", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nOURPUBLICKEY\n-----END PUBLIC KEY-----\n" }, - "icon":{ + "icon": { "type": "Image", "mediaType": "image/jpg", "url": "https://DOMAIN/events/EVENTID.jpg" @@ -85,7 +85,7 @@ When the server receives a `Follow` Activity, it grabs the `actor` property on t Assuming we can find the Actor object, then we emit an `Accept` Activity back to the server, containing the full `Follow` that we just parsed. This lets the other server know that we have fully processed the follow request. -After this, we *also* send a `Create` Activity to the actor's inbox, containing an `Event` object with the information for this event. This is, at the moment, future compatibility for servers that consume `Event` objects. This is sent as a "direct message", directly to the inbox with no `cc` field and not addressing the public timeline. +After this, we _also_ send a `Create` Activity to the actor's inbox, containing an `Event` object with the information for this event. This is, at the moment, future compatibility for servers that consume `Event` objects. This is sent as a "direct message", directly to the inbox with no `cc` field and not addressing the public timeline. And finally we send the user a `Create` Activity containing a `Question` object. The `Question` is an invitation to RSVP to the event. Mastodon renders this as a poll to the user, which lets them send back to us a "Yes" RSVP directly from their client UI should they so choose. This is also sent as a "direct message". Some clients like Friendica, simply ignore `Question` objects, which is fine since the user can use built-in RSVP function of Friendica to RSVP anyway (see below). @@ -102,13 +102,13 @@ The plan is to have this support two ways to RSVP: 1. The user answers the `Question` sent out to the prospective attendee in the form of a `Create/Note` in the style of Mastodon polls. This is mostly a hack for implementations like Mastodon that don't have vocabulary built in to RSVP to `Event`s. 2. The user sends a `Accept/Event` or `Undo/Accept/Event` back to our server. This is for implementations like Friendica that support `Event` and do things like automatically render incoming events in their UI with an RSVP interface. We currently don't accept `Reject/Event` or `TentativeAccept/Event` because gathio has no concept of a "Maybe" or "No" RSVP. It probably should have that in the future, at which case we could meaningfully parse this stuff. -__The `Question` method__ +**The `Question` method** If the inbox gets a `Create/Note`, there is a chance that this is a response to a `Question` that we sent a user. So the first thing we do is check its `inReplyTo` property. If it matches the id of a `Question` we sent this user, and this user is still following us, then we fetch the user's profile info. This is to make sure we have their newest `preferredUsername` in their `Actor` object (falling back to `name` and then `actor`), which we will honor as the name we display on the RSVP. We then add this person to our database as an attendee of the event. Next we confirm that the user has RSVPed. We do this by sending them a `Create/Note` via direct message. The note tells them they RSVPed, and gives them a URL they can click on to instantly un-RSVP if they need to. -__The `Accept/Event` method__ +**The `Accept/Event` method** If the inbox gets an `Accept/Event`, then it assumes this is an affirmative RSVP from the actor who sent it. We check to see if the `id` of the `Event` matches the `id` of an `Event` that we sent ot this actor. If it does, then it must be a valid, affirmative RSVP. We then get the `preferredUsername` or `name` from the actor object, and add that actor to the database as an attendee. TODO: support either object URI or embedded object here. @@ -126,7 +126,7 @@ Since a user can comment on the event via ActivityPub, they should be able to de ### Incoming private messages -*TODO*: If someone tries to DM the event, we need to reply with a message like "Sorry, this service only supports posting public messages to the event page. Try contacting the event organizer directly if you need to have a private conversation." +_TODO_: If someone tries to DM the event, we need to reply with a message like "Sorry, this service only supports posting public messages to the event page. Try contacting the event organizer directly if you need to have a private conversation." ## Activities triggered from the web app @@ -144,7 +144,7 @@ And finally we send an `Update/Event` out with the new event details in the `Eve ### Delete event -When an event is deleted by its administrator, or the event has been deleted due to it being one week after the event has ended, we send a `Delete/Actor` out to followers. This lets followers know that the event has been deleted, and their server should remove its profile from their database. (On Mastodon this results in an automatic "unfollow", which is good because we want people's follow counts to go back to normal after an event is over and has been deleted.) +When an event is deleted by its administrator, or the event has been deleted due to it being one week after the event has ended, we send a `Delete/Actor` out to followers. This lets followers know that the event has been deleted, and their server should remove its profile from their database. (On Mastodon this results in an automatic "unfollow", which is good because we want people's follow counts to go back to normal after an event is over and has been deleted.) We also send a `Delete/Event` out to followers. For an application like Friendica, this removes the event from the calendar of a follower. diff --git a/docker-compose.yml b/docker-compose.yml index 44026c8..334bc3d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,16 @@ -version: '3' +version: "3" volumes: - mongodb_data_db: + mongodb_data_db: services: - gathio: - build: . - links: - - mongo - ports: - - 3000:3000 - mongo: - image: mongo:latest - volumes: - - mongodb_data_db:/data/db \ No newline at end of file + gathio: + build: . + links: + - mongo + ports: + - 3000:3000 + mongo: + image: mongo:latest + volumes: + - mongodb_data_db:/data/db diff --git a/src/routes.js b/src/routes.js index 052446c..0d59b65 100755 --- a/src/routes.js +++ b/src/routes.js @@ -937,46 +937,44 @@ router.post("/newevent", async (req, res) => { addToLog("createEvent", "success", "Event " + eventID + "created"); // Send email with edit link if (req.body.creatorEmail && sendEmails) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/createevent.handlebars", - { - eventID, - editToken, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail, - }, - subject: `${siteName}: ${req.body.eventName}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/createevent.handlebars", + { + eventID, + editToken, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${req.body.eventName}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } // If the event was added to a group, send an email to any group // subscribers @@ -989,49 +987,47 @@ router.post("/newevent", async (req, res) => { return [current.email, ...acc]; }, []); subscribers.forEach((emailAddress) => { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/eventgroupupdated.handlebars", - { - siteName, - siteLogo, - domain, - eventID: req.params.eventID, - eventGroupName: eventGroup.name, - eventName: event.name, - eventID: event.id, - eventGroupID: eventGroup.id, - emailAddress: encodeURIComponent(emailAddress), - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: emailAddress, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New event in ${eventGroup.name}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/eventgroupupdated.handlebars", + { + siteName, + siteLogo, + domain, + eventID: req.params.eventID, + eventGroupName: eventGroup.name, + eventName: event.name, + eventID: event.id, + eventGroupID: eventGroup.id, + emailAddress: encodeURIComponent(emailAddress), + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: emailAddress, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New event in ${eventGroup.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); }); }); } @@ -1098,46 +1094,44 @@ router.post("/importevent", (req, res) => { addToLog("createEvent", "success", "Event " + eventID + " created"); // Send email with edit link if (creatorEmail && sendEmails) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/createevent.handlebars", - { - eventID, - editToken, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail, - }, - subject: `${siteName}: ${importedEventData.summary}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/createevent.handlebars", + { + eventID, + editToken, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${importedEventData.summary}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } res.writeHead(302, { Location: "/" + eventID + "?e=" + editToken, @@ -1199,46 +1193,44 @@ router.post("/neweventgroup", (req, res) => { ); // Send email with edit link if (req.body.creatorEmail && sendEmails) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/createeventgroup.handlebars", - { - eventGroupID, - editToken, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.creatorEmail, - from: { - name: siteName, - email: contactEmail, - address: contactEmail, - }, - subject: `${siteName}: ${req.body.eventGroupName}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/createeventgroup.handlebars", + { + eventGroupID, + editToken, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.creatorEmail, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${req.body.eventGroupName}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } res.writeHead(302, { Location: "/group/" + eventGroupID + "?e=" + editToken, @@ -1469,46 +1461,44 @@ router.post("/editevent/:eventID/:editToken", (req, res) => { .map((o) => o.email); if (attendeeEmails.length) { console.log("Sending emails to: " + attendeeEmails); - req.app - .get("hbsInstance") - .renderView( - "./views/emails/editevent.handlebars", - { - diffText, - eventID: req.params.eventID, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - address: contactEmail, - }, - subject: `${siteName}: ${event.name} was just edited`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.sendMultiple(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/editevent.handlebars", + { + diffText, + eventID: req.params.eventID, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${event.name} was just edited`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } else { console.log("Nothing to send!"); } @@ -1788,54 +1778,52 @@ router.post("/deleteevent/:eventID/:editToken", (req, res) => { .map((o) => o.email); if (attendeeEmails.length) { console.log("Sending emails to: " + attendeeEmails); - req.app - .get("hbsInstance") - .renderView( - "./views/emails/deleteevent.handlebars", - { - siteName, - siteLogo, - domain, - eventName: event.name, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - address: contactEmail, - }, - subject: `${siteName}: ${event.name} was deleted`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.sendMultiple(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } - } - ); - } else { - console.log("Nothing to send!"); - } - } - }) - .catch((err) => { - res.send( - "Sorry! Something went wrong (error deleting): " + err - ); + req.app.get("hbsInstance").renderView( + "./views/emails/deleteevent.handlebars", + { + siteName, + siteLogo, + domain, + eventName: event.name, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + address: contactEmail, + }, + subject: `${siteName}: ${event.name} was deleted`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + } + ); + } else { + console.log("Nothing to send!"); + } + } + }) + .catch((err) => { + res.send( + "Sorry! Something went wrong (error deleting): " + err + ); addToLog( "deleteEvent", "error", @@ -2111,45 +2099,43 @@ router.post("/attendevent/:eventID", async (req, res) => { ); if (sendEmails) { if (req.body.attendeeEmail) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/addeventattendee.handlebars", - { - eventID: req.params.eventID, - siteName, - siteLogo, - domain, - removalPassword: req.body.removalPassword, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You're RSVPed to ${event.name}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/addeventattendee.handlebars", + { + eventID: req.params.eventID, + siteName, + siteLogo, + domain, + removalPassword: req.body.removalPassword, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You're RSVPed to ${event.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } } res.redirect(`/${req.params.eventID}`); @@ -2187,44 +2173,42 @@ router.post("/unattendevent/:eventID", (req, res) => { ); if (sendEmails) { if (req.body.attendeeEmail) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/unattendevent.handlebars", - { - eventID: req.params.eventID, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/unattendevent.handlebars", + { + eventID: req.params.eventID, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } } res.writeHead(302, { @@ -2269,43 +2253,41 @@ router.get("/oneclickunattendevent/:eventID/:attendeeID", (req, res) => { if (sendEmails) { // currently this is never called because we don't have the email address if (req.body.attendeeEmail) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/removeeventattendee.handlebars", - { - eventName: req.params.eventName, - siteName, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/removeeventattendee.handlebars", + { + eventName: req.params.eventName, + siteName, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } } res.writeHead(302, { @@ -2341,44 +2323,42 @@ router.post("/removeattendee/:eventID/:attendeeID", (req, res) => { if (sendEmails) { // currently this is never called because we don't have the email address if (req.body.attendeeEmail) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/removeeventattendee.handlebars", - { - eventName: req.params.eventName, - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: req.body.attendeeEmail, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have been removed from an event`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/removeeventattendee.handlebars", + { + eventName: req.params.eventName, + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: req.body.attendeeEmail, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have been removed from an event`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } } res.writeHead(302, { @@ -2420,46 +2400,44 @@ router.post("/subscribe/:eventGroupID", (req, res) => { eventGroup.subscribers.push(subscriber); eventGroup.save(); if (sendEmails) { - req.app - .get("hbsInstance") - .renderView( - "./views/emails/subscribed.handlebars", - { - eventGroupName: eventGroup.name, - eventGroupID: eventGroup.id, - emailAddress: encodeURIComponent(subscriber.email), - siteName, - siteLogo, - domain, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: subscriber.email, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: You have subscribed to an event group`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.send(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/subscribed.handlebars", + { + eventGroupName: eventGroup.name, + eventGroupID: eventGroup.id, + emailAddress: encodeURIComponent(subscriber.email), + siteName, + siteLogo, + domain, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: subscriber.email, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have subscribed to an event group`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.send(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } return res.redirect(`/group/${eventGroup.id}`); }) @@ -2557,45 +2535,43 @@ router.post("/post/comment/:eventID", (req, res) => { .map((o) => o.email); if (attendeeEmails.length) { console.log("Sending emails to: " + attendeeEmails); - req.app - .get("hbsInstance") - .renderView( - "./views/emails/addeventcomment.handlebars", - { - siteName, - siteLogo, - domain, - eventID: req.params.eventID, - commentAuthor: req.body.commentAuthor, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New comment in ${event.name}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.sendMultiple(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/addeventcomment.handlebars", + { + siteName, + siteLogo, + domain, + eventID: req.params.eventID, + commentAuthor: req.body.commentAuthor, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New comment in ${event.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } else { console.log("Nothing to send!"); } @@ -2671,45 +2647,43 @@ router.post("/post/reply/:eventID/:commentID", (req, res) => { .map((o) => o.email); if (attendeeEmails.length) { console.log("Sending emails to: " + attendeeEmails); - req.app - .get("hbsInstance") - .renderView( - "./views/emails/addeventcomment.handlebars", - { - siteName, - siteLogo, - domain, - eventID: req.params.eventID, - commentAuthor: req.body.replyAuthor, - cache: true, - layout: "email.handlebars", - }, - function (err, html) { - const msg = { - to: attendeeEmails, - from: { - name: siteName, - email: contactEmail, - }, - subject: `${siteName}: New comment in ${event.name}`, - html, - }; - switch (mailService) { - case "sendgrid": - sgMail.sendMultiple(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - case "nodemailer": - nodemailerTransporter.sendMail(msg).catch((e) => { - console.error(e.toString()); - res.status(500).end(); - }); - break; - } + req.app.get("hbsInstance").renderView( + "./views/emails/addeventcomment.handlebars", + { + siteName, + siteLogo, + domain, + eventID: req.params.eventID, + commentAuthor: req.body.replyAuthor, + cache: true, + layout: "email.handlebars", + }, + function (err, html) { + const msg = { + to: attendeeEmails, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New comment in ${event.name}`, + html, + }; + switch (mailService) { + case "sendgrid": + sgMail.sendMultiple(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case "nodemailer": + nodemailerTransporter.sendMail(msg).catch((e) => { + console.error(e.toString()); + res.status(500).end(); + }); + break; } - ); + } + ); } else { console.log("Nothing to send!"); } diff --git a/tsconfig.json b/tsconfig.json index 9d7b7ab..04519ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "nodenext", - "skipLibCheck": true, + "skipLibCheck": true }, - "include": ["src/**/*"], -} \ No newline at end of file + "include": ["src/**/*"] +} -- cgit v1.2.3 From bcf967eb359f243487b5bd2eca418387a65c4e22 Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:06 +0100 Subject: Upgrade Node version to 18 --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 6f7f377..3f430af 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v16 +v18 -- cgit v1.2.3 From 50688f573054f60aa7594672615f11713173c147 Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Fri, 12 May 2023 16:54:06 +0100 Subject: Update docker to support typescript + pnpm --- .dockerignore | 1 + Dockerfile | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5bec270..a11e337 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,10 @@ -FROM node:16-alpine +FROM node:18-alpine WORKDIR /app RUN apk add --no-cache python3 build-base -ADD package.json package-lock.json /app/ -RUN npm install +ADD package.json pnpm-lock.yaml /app/ +RUN npm install -g pnpm +RUN pnpm install COPY . /app/ -RUN cp config/api-example.js config/api.js && cp config/domain-example.js config/domain.js && cp config/database-docker.js config/database.js -CMD npm start +RUN cp src/config/api-example.js src/config/api.js && cp src/config/domain-example.js src/config/domain.js && cp src/config/database-docker.js src/config/database.js +CMD pnpm run build +CMD pnpm run start -- cgit v1.2.3