diff --git a/.env b/.env index 1cffbc4b95eb097e0946fccca58289f793363bea..42ea09e9c15f2c9de7c8d54cebfe1c3da4053cf6 100644 --- a/.env +++ b/.env @@ -1,4 +1,5 @@ NODE_ENV=development +NODE_OPTIONS="--max_old_space_size=4096" TYPEORM_CONNECTION=postgres TYPEORM_HOST=localhost @@ -91,4 +92,7 @@ SWAGGER_SERVER=https://localhost:8080 SWAGGER_CHANNEL_ID=a8b1b6db-2543-4549-b143-442735739fed # Set to True in order to deploy an internal backend (without authentication) -EXPOSE_UNAUTHENTICATED_ROUTES=False \ No newline at end of file +EXPOSE_UNAUTHENTICATED_ROUTES=False + +APPSIGNAL_PUSH_API_KEY=fill-me +APPSIGNAL_ENV=master diff --git a/appsignal.js b/appsignal.js new file mode 100644 index 0000000000000000000000000000000000000000..ad7583858473965f39c7bbe536c6bb9ca9a34830 --- /dev/null +++ b/appsignal.js @@ -0,0 +1,12 @@ +// appsignal.js or src/appsignal.js +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { Appsignal } = require('@appsignal/nodejs'); + +const appsignal = new Appsignal({ + active: true, + name: 'notifications', + pushApiKey: process.env.APPSIGNAL_PUSH_API_KEY, // Note: renamed from `apiKey` in version 2.2.5 + environment: process.env.APPSIGNAL_ENV, +}); + +module.exports = { appsignal }; diff --git a/package-lock.json b/package-lock.json index 31fd35669abb720fc0757fcc2b9139cc82185650..1aa10adcbcbf32a99f9e561f9c74facbe513c1cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,136 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@appsignal/core": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@appsignal/core/-/core-1.1.18.tgz", + "integrity": "sha512-PfFxEn6CNiX6PR6P582N5cV+5/j3tCmn1v6G+0nHrj8GY4Dir+vqJtGbgUxda0kbtCa6B4OHMH7Pe/SGL92rKw==", + "requires": { + "@appsignal/types": "=3.0.0", + "isomorphic-unfetch": "^3.1.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@appsignal/express": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/@appsignal/express/-/express-1.0.33.tgz", + "integrity": "sha512-tPrWIMtflR2+YsqoMd7TwGGSWyVLggvxkwimrKhX358KoYPymjldLaeSB2CDcOeskcVcLyj0VMeM57FXqs4CHQ==", + "requires": { + "@appsignal/nodejs": "=2.4.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@appsignal/nodejs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@appsignal/nodejs/-/nodejs-2.4.2.tgz", + "integrity": "sha512-Tjn6Tz2T/BwZQMrbBdatI1FvgEuBhtsQcAEv7zVAZ1v/ylhK3/spGsEEsOIrSHMxbBTqPAC8GBkqyXy2CZTdFA==", + "requires": { + "@appsignal/core": "^1.1.4", + "@appsignal/types": "^3.0.0", + "@opentelemetry/api": "^1.0.4", + "@opentelemetry/sdk-trace-base": "^1.2.0", + "node-addon-api": "^3.1.0", + "node-gyp": "^9.0.0", + "require-in-the-middle": "^5.1.0", + "semver": "^7.3.4", + "shimmer": "^1.2.1", + "tslib": "^2.0.3", + "winston": "^3.6.0" + }, + "dependencies": { + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "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" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "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==" + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "requires": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@appsignal/types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@appsignal/types/-/types-3.0.0.tgz", + "integrity": "sha512-L0OAKaro7209Du39UF2ZWU8CzUJxeeKaW6Zyu2FA2SGdW3HK0PoJxY7A8R52KdZiXzMlCAlrOjvoDvT/ct0RIg==" + }, "@babel/code-frame": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", @@ -454,6 +584,21 @@ "minimist": "^1.2.0" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "@eslint/eslintrc": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", @@ -563,6 +708,11 @@ "tslib": "^1.11.1" } }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" + }, "@google-cloud/common": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", @@ -1196,6 +1346,120 @@ "fastq": "^1.6.0" } }, + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "dependencies": { + "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" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "@opentelemetry/api": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.2.0.tgz", + "integrity": "sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g==" + }, + "@opentelemetry/core": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.7.0.tgz", + "integrity": "sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ==", + "requires": { + "@opentelemetry/semantic-conventions": "1.7.0" + } + }, + "@opentelemetry/resources": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.7.0.tgz", + "integrity": "sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg==", + "requires": { + "@opentelemetry/core": "1.7.0", + "@opentelemetry/semantic-conventions": "1.7.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz", + "integrity": "sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg==", + "requires": { + "@opentelemetry/core": "1.7.0", + "@opentelemetry/resources": "1.7.0", + "@opentelemetry/semantic-conventions": "1.7.0" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.7.0.tgz", + "integrity": "sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1556,11 +1820,6 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, - "@types/zen-observable": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", - "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" - }, "@typescript-eslint/eslint-plugin": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz", @@ -1826,6 +2085,11 @@ "merge-options": "^1.0.0" } }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1849,11 +2113,35 @@ } } }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "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==" + } + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -1990,9 +2278,9 @@ } }, "app-root-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", - "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==" }, "append-field": { "version": "0.1.0", @@ -2000,6 +2288,45 @@ "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo=", "optional": true }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "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==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2056,6 +2383,11 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -2133,6 +2465,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axios": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz", + "integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, "babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -2231,6 +2571,11 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" + }, "backoff": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", @@ -2299,11 +2644,21 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -2338,6 +2693,11 @@ "buffer-more-ints": "0.0.2" } }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -2492,6 +2852,111 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.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" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "lru-cache": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", + "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==" + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + } + } + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -2634,6 +3099,11 @@ "readdirp": "~3.5.0" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -2705,8 +3175,7 @@ "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, "cli-boxes": { "version": "2.2.1", @@ -2878,6 +3347,30 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + }, + "dependencies": { + "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==" + } + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2891,6 +3384,20 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "colorette": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", @@ -2902,6 +3409,15 @@ "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2922,11 +3438,20 @@ "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", "dev": true }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" }, "compressible": { "version": "2.0.18", @@ -2995,6 +3520,11 @@ "xdg-basedir": "^4.0.0" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -3172,6 +3702,11 @@ "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", "optional": true }, + "date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3323,8 +3858,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "optional": true + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { "version": "1.1.2", @@ -3408,7 +3942,8 @@ "dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true }, "dtrace-provider": { "version": "0.8.8", @@ -3507,17 +4042,115 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz", + "integrity": "sha512-Kc8fo5bbg8F4a2f3HPHTEpGyq/IRIQpyeHu3H1ThR14XDD7VrLcsGBo16HUpahgp8YkHJDaU5gNxJZbuGcuueg==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "~7.4.2" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "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==" + } + } + }, + "engine.io-client": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.3.tgz", + "integrity": "sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==", + "requires": { + "component-emitter": "~1.3.0", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.6.2", + "yeast": "0.1.2" + }, + "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" + } + } + } + }, + "engine.io-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", + "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", "requires": { - "once": "^1.4.0" + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.4", + "blob": "0.0.5", + "has-binary2": "~1.0.2" } }, "enquirer": { @@ -3535,6 +4168,16 @@ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "optional": true }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4119,6 +4762,15 @@ "es5-ext": "~0.10.14" } }, + "event-loop-stats": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/event-loop-stats/-/event-loop-stats-1.2.0.tgz", + "integrity": "sha512-h/leAlXqoEf+D9w1dnFG0srR5vfIq59rLm9PHzcl3/GwFppd+UR46UMuLdp/mvJvuA+MjSd/dNShmuM2/dPFFw==", + "optional": true, + "requires": { + "nan": "^2.14.0" + } + }, "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -4278,6 +4930,35 @@ } } }, + "express-status-monitor": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/express-status-monitor/-/express-status-monitor-1.3.4.tgz", + "integrity": "sha512-EyqHvgX57ujN4fqfUT+x6Bv2xwRyzQdv3AJvWQxcG+jK4TcF9vhrKVqGcE0T6bhT4rstpvOKRuxHBwC/Q6AXQg==", + "requires": { + "axios": "0.26.0", + "debug": "4.1.1", + "event-loop-stats": "1.2.0", + "handlebars": "^4.7.7", + "on-headers": "1.0.2", + "pidusage": "2.0.18", + "socket.io": "^2.4.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "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==" + } + } + }, "ext": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", @@ -4488,6 +5169,11 @@ "bser": "2.1.1" } }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4605,6 +5291,16 @@ "integrity": "sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A==", "dev": true }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -4651,6 +5347,14 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4672,6 +5376,61 @@ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "gaxios": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", @@ -4929,6 +5688,18 @@ } } }, + "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", @@ -4951,6 +5722,26 @@ "function-bind": "^1.1.1" } }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4962,6 +5753,11 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "optional": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -5178,6 +5974,14 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "requires": { + "ms": "^2.0.0" + } + }, "husky": { "version": "4.3.8", "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", @@ -5339,8 +6143,17 @@ "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflation": { "version": "2.0.0", @@ -5367,6 +6180,11 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", @@ -5562,6 +6380,11 @@ "is-path-inside": "^3.0.1" } }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" + }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -5737,8 +6560,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -5746,6 +6568,15 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "requires": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -7422,6 +8253,11 @@ } } }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", @@ -7866,6 +8702,25 @@ } } }, + "logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "requires": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -7912,6 +8767,69 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "dependencies": { + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", + "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + } + } + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -8042,6 +8960,80 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -8072,6 +9064,11 @@ "minimist": "^1.2.5" } }, + "module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -8210,6 +9207,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "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==" + }, "next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -8221,6 +9223,11 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, "node-cache": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", @@ -8229,17 +9236,106 @@ "clone": "2.x" } }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "optional": true - }, - "node-forge": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", - "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" - }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "node-forge": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", + "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" + }, + "node-gyp": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.1.0.tgz", + "integrity": "sha512-HkmN0ZpQJU7FLbJauJTHkHlSVAXlNGDAzH/VYFZGDOnFyn/Na3GlNJfkudmufOdS6/jNFhy88ObzL7ERz9es1g==", + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -8408,6 +9504,17 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -8528,6 +9635,14 @@ "wrappy": "1" } }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -8608,7 +9723,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "requires": { "aggregate-error": "^3.0.0" } @@ -8680,6 +9794,16 @@ } } }, + "parseqs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" + }, + "parseuri": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -8789,6 +9913,14 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, + "pidusage": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.18.tgz", + "integrity": "sha512-Y/VfKfh3poHjMEINxU+gJTeVOBjiThQeFAmzR7z56HSNiMx+etl+yBhk42nRPciPYt/VZl8DQLVXNC6P5vH11A==", + "requires": { + "safe-buffer": "^5.1.2" + } + }, "pirates": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", @@ -8907,6 +10039,20 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, "prompts": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", @@ -9278,6 +10424,54 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "require-in-the-middle": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", + "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", + "requires": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "requires": { + "has": "^1.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -9339,6 +10533,11 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==" + }, "retry-request": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", @@ -9492,6 +10691,11 @@ "ret": "~0.1.10" } }, + "safe-stable-stringify": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.0.tgz", + "integrity": "sha512-eehKHKpab6E741ud7ZIMcXhKcP6TSIezPkNZhy5U8xC6+VvrRdUA2tMgxGxaGl4cz7c2Ew5+mg5+wNB16KQqrA==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9718,8 +10922,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -9785,6 +10988,11 @@ "dev": true, "optional": true }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -9801,6 +11009,21 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -9832,6 +11055,11 @@ } } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, "snakeize": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", @@ -9951,11 +11179,155 @@ } } }, + "socket.io": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz", + "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==", + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.6.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.5.0", + "socket.io-parser": "~3.4.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "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==" + } + } + }, + "socket.io-adapter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" + }, + "socket.io-client": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz", + "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==", + "requires": { + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "engine.io-client": "~3.5.0", + "has-binary2": "~1.0.2", + "indexof": "0.0.1", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "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" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + }, + "socket.io-parser": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", + "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", + "requires": { + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "isarray": "2.0.1" + } + } + } + }, + "socket.io-parser": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "socks": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", + "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "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==" + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.3", @@ -10082,6 +11454,14 @@ "tweetnacl": "~0.14.0" } }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "requires": { + "minipass": "^3.1.1" + } + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -10354,6 +11734,11 @@ } } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, "swagger-ui-dist": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.49.0.tgz", @@ -10422,6 +11807,31 @@ } } }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "teeny-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", @@ -10482,6 +11892,11 @@ } } }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10499,7 +11914,7 @@ "thenify-all": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "requires": { "thenify": ">= 3.1.0 < 4" } @@ -10569,6 +11984,11 @@ "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -10651,6 +12071,11 @@ "punycode": "^2.1.1" } }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "ts-node": { "version": "8.10.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", @@ -10746,26 +12171,27 @@ } }, "typeorm": { - "version": "0.2.41", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.41.tgz", - "integrity": "sha512-/d8CLJJxKPgsnrZWiMyPI0rz2MFZnBQrnQ5XP3Vu3mswv2WPexb58QM6BEtmRmlTMYN5KFWUz8SKluze+wS9xw==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.9.tgz", + "integrity": "sha512-xNcE44D4hn74n7pjuMog9hRgep+BiO3IBpjEaQZ8fb56zsDz7xHT1GAeWwmGuuU+4nDEELp2mIqgSCR+zxR7Jw==", "requires": { "@sqltools/formatter": "^1.2.2", "app-root-path": "^3.0.0", "buffer": "^6.0.3", "chalk": "^4.1.0", "cli-highlight": "^2.1.11", - "debug": "^4.3.1", - "dotenv": "^8.2.0", - "glob": "^7.1.6", - "js-yaml": "^4.0.0", + "date-fns": "^2.28.0", + "debug": "^4.3.3", + "dotenv": "^16.0.0", + "glob": "^7.2.0", + "js-yaml": "^4.1.0", "mkdirp": "^1.0.4", "reflect-metadata": "^0.1.13", "sha.js": "^2.4.11", - "tslib": "^2.1.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2", "xml2js": "^0.4.23", - "yargs": "^17.0.1", - "zen-observable-ts": "^1.0.0" + "yargs": "^17.3.1" }, "dependencies": { "argparse": { @@ -10783,22 +12209,27 @@ } }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } }, + "dotenv": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", + "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==" + }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -10816,6 +12247,14 @@ "argparse": "^2.0.1" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -10835,9 +12274,14 @@ } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" } } }, @@ -10847,6 +12291,12 @@ "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", "dev": true }, + "uglify-js": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.0.tgz", + "integrity": "sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg==", + "optional": true + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -10863,6 +12313,11 @@ "debug": "^2.2.0" } }, + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -10875,6 +12330,22 @@ "set-value": "^2.0.1" } }, + "unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "requires": { + "imurmurhash": "^0.1.4" + } + }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -11241,6 +12712,14 @@ "is-typed-array": "^1.1.3" } }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -11269,12 +12748,52 @@ } } }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "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==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.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==" + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -11319,8 +12838,7 @@ "ws": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", - "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==", - "dev": true + "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==" }, "xdg-basedir": { "version": "4.0.0", @@ -11353,6 +12871,11 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xmlhttprequest-ssl": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz", + "integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -11375,9 +12898,9 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz", - "integrity": "sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -11422,9 +12945,9 @@ } }, "yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==" + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" } } }, @@ -11433,6 +12956,11 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" + }, "ylru": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", @@ -11450,20 +12978,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true - }, - "zen-observable": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" - }, - "zen-observable-ts": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", - "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", - "requires": { - "@types/zen-observable": "0.8.3", - "zen-observable": "0.8.15" - } } } } diff --git a/package.json b/package.json index dbd68ea68072197d00f3a449b8b822b8c1b62266..fd5fa43c346e40b277d6bd69c6b6ac73aad488ef 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "author": "", "license": "ISC", "dependencies": { + "@appsignal/express": "^1.0.33", + "@appsignal/nodejs": "^2.4.2", "@firebase/app-types": "0.x", "@sentry/integrations": "^6.17.9", "@sentry/node": "^6.17.9", @@ -38,6 +40,7 @@ "cors": "^2.8.5", "etcd3": "^1.1.0", "express": "^4.17.1", + "express-status-monitor": "^1.3.4", "firebase-admin": "^8.9.2", "jsonwebtoken": "^8.5.1", "ldap-escape": "^2.0.3", @@ -54,7 +57,7 @@ "safari-push-notifications": "^0.5.0", "stompit": "^1.0.0", "swagger-ui-express": "^4.1.6", - "typeorm": "^0.2.41", + "typeorm": "^0.3.9", "web-push": "^3.4.4" }, "devDependencies": { diff --git a/src/app.ts b/src/app.ts index 31c5d9254643a793c91c24bbcbff9c70ade624f2..ae1e890c0d75072556d1c4459ded8c87ae7558f9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -15,6 +15,9 @@ import { ChannelsController } from './controllers/channels/controller'; import * as fs from 'fs'; import * as https from 'https'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { appsignal } = require('../appsignal'); + Configuration.load(); // Note https://github.com/typestack/class-transformer/issues/563 @@ -50,10 +53,16 @@ const origins = //create express server const app = express(); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { expressMiddleware } = require('@appsignal/express'); + // Sentry initialize sentry.sentryInit(app); sentry.sentryHandleRequests(app); +// eslint-disable-next-line @typescript-eslint/no-var-requires +// app.use(require('express-status-monitor')()); + let server; // Parse class-validator classes into JSON Schema: @@ -112,6 +121,9 @@ const swaggerOptions = { app.get('/swagger/swagger.json', (req, res) => res.json(spec)); app.use('/swagger', swaggerUiExpress.serveFiles(null, swaggerOptions), swaggerUiExpress.setup(null, swaggerOptions)); +// ADD THIS AFTER ANY OTHER EXPRESS MIDDLEWARE, BUT BEFORE ANY ROUTES! +app.use(expressMiddleware(appsignal)); + // Render spec on root: app.get('/', (_req, res) => { res.json(spec); diff --git a/src/controllers/channels/dto.ts b/src/controllers/channels/dto.ts index 494ba1c495abe4f8c5ed091174fc154527bcd65d..ec47dc1db29284ac92b330f7b2304d80efc03162 100644 --- a/src/controllers/channels/dto.ts +++ b/src/controllers/channels/dto.ts @@ -23,6 +23,7 @@ import { User } from '../../models/user'; import { AuthorizationBag } from '../../models/authorization-bag'; import { v4 } from 'uuid'; +// TODO: fix relationship counters export class CategoryResponse { @IsString() id: string; @@ -378,12 +379,12 @@ export class ChannelStatsResponse { constructor(channel: Channel) { this.id = channel.id; - this.members = channel.members.length; + // this.members = channel.members.length; this.unsubscribed = channel.unsubscribed.length; this.owner = channel.owner; this.creationDate = channel.creationDate; this.lastActivityDate = channel.lastActivityDate; - this.groups = channel.groups.length; + // this.groups = channel.groups.length; } } @@ -509,8 +510,8 @@ export class GetChannelResponse extends GetChannelPublicResponse { this.send = send; this.relationships = new RelationshipResponse( channel.notificationCount, - channel.members?.length || 0, - channel.groups?.length || 0, + 0, // channel.members?.length || 0, + 0 // channel.groups?.length || 0, ); this.sendPrivate = channel.sendPrivate; } @@ -1335,10 +1336,8 @@ export class ChannelResponse { this.owner = channel.owner; this.name = channel.name; this.description = channel.description; - this.members = channel.members; this.adminGroup = channel.adminGroup; this.unsubscribed = channel.unsubscribed; - this.groups = channel.groups; //this.notifications = channel.notifications; this.visibility = channel.visibility; this.archive = channel.archive; @@ -1353,20 +1352,9 @@ export class ChannelResponse { this.category = channel.category; this.tags = channel.tags; this.sendPrivate = channel.sendPrivate; - this.relationships = new RelationshipResponse( - channel.notificationCount, - channel.members?.length || 0, - channel.groups?.length || 0, - ); this.channelFlags = channel.channelFlags; this.channelType = channel.channelType; } - - async channelFilters(authBag: AuthorizationBag, chan: Channel): Promise<void> { - //this.subscribed = await chan.isSubscribedByName(authBag.userName); - this.manage = await chan.isAdmin(authBag); - this.send = await chan.canSendByForm(authBag); - } } @JSONSchema({ diff --git a/src/models/api-key-object.ts b/src/models/api-key-object.ts index c2b3ea0a070a5bf0d299f74e82f1b96139d5bba4..fb19b9286bbb5680b6147bcae96fa094d09a584a 100644 --- a/src/models/api-key-object.ts +++ b/src/models/api-key-object.ts @@ -1,9 +1,12 @@ -import { Column } from 'typeorm'; +import { Column, PrimaryGeneratedColumn } from 'typeorm'; import { HexBase64BinaryEncoding } from 'crypto'; const crypto = require('crypto'); export abstract class ApiKeyObject { + @PrimaryGeneratedColumn('uuid') + id: string; + //this is a hash of the API key and the salt @Column({ nullable: true }) APIKey: string; diff --git a/src/models/category.ts b/src/models/category.ts index 3b2e83d0231b691293cc4753702545c3a5d85a1c..0c5b309e774cf8198b4af75a3b8756216fc92c1b 100644 --- a/src/models/category.ts +++ b/src/models/category.ts @@ -1,12 +1,4 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - Index, - Tree, - TreeChildren, - TreeParent, -} from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, Index, Tree, TreeChildren, TreeParent, EntityManager } from 'typeorm'; @Entity('Categories') @Tree('closure-table') @@ -33,4 +25,12 @@ export class Category { this.info = category.info; } } + + static async getDescendantIds(tm: EntityManager, categoryId: string): Promise<string[]> { + const searchParamCategory = new Category({}); + searchParamCategory.id = categoryId; + const descendants = await tm.getTreeRepository(Category).findDescendants(searchParamCategory); + const descendantIds = descendants.map(cat => cat.id); + return descendantIds; + } } diff --git a/src/models/cern-authorization-service.ts b/src/models/cern-authorization-service.ts index 5ea176581f12111167d7a95b8ef842b880534e18..87ed0c6cfdbc5ba7448398d1d6df720f71ccd6b0 100644 --- a/src/models/cern-authorization-service.ts +++ b/src/models/cern-authorization-service.ts @@ -2,6 +2,11 @@ import requestPromise = require('request-promise-native'); import * as memoize from 'memoizee'; import { User } from '../models/user'; +export interface Group { + groupId: string; // uuid + groupIdentifier: string; //slug +} + export class CernAuthorizationService { static async getAuthTokenNoCache() { return requestPromise.post(process.env.AUTHORIZATION_SERVICE_API_TOKEN_URL, { @@ -152,7 +157,7 @@ export class CernAuthorizationService { } // getCurrentUserGroups for current User - static async _getCurrentUserGroupsNoCache(userName: string): Promise<any[]> { + static async _getCurrentUserGroupsNoCache(userName: string): Promise<Group[]> { if (!userName) return []; console.debug(`getCurrentUserGroups miss cache: ${userName}`); // Using /precomputed endpoint for faster recursive resolution (uses a 15 minutes cached computed result) @@ -166,7 +171,7 @@ export class CernAuthorizationService { } // Recursive paginated result handling. Takes absolutePath as parameter to align with next field value from response - static async _getCurrentUserGroupsNoCachePaginated(nextUri: string): Promise<any[]> { + static async _getCurrentUserGroupsNoCachePaginated(nextUri: string): Promise<Group[]> { let res = []; try { const grappadata = JSON.parse( @@ -198,7 +203,7 @@ export class CernAuthorizationService { preFetch: true, }); - static async getCurrentUserGroups(userName: string) { + static async getCurrentUserGroups(userName: string): Promise<Group[]> { return CernAuthorizationService._memoizedGetCurrentUserGroups(userName); } } diff --git a/src/models/channel.ts b/src/models/channel.ts index 68373e94dd405937879315cb7df65face2dcec89..c8c3b86b457dc74ea7f1bd19133197985ab5dc83 100644 --- a/src/models/channel.ts +++ b/src/models/channel.ts @@ -1,15 +1,20 @@ import { BeforeInsert, BeforeUpdate, + Brackets, Column, DeleteDateColumn, Entity, + EntityManager, + FindRelationsNotFoundError, + In, Index, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn, + Raw, } from 'typeorm'; import { IsEmail, IsOptional, Validate } from 'class-validator'; import { User } from './user'; @@ -31,6 +36,9 @@ import { ChannelHelpers } from './channel-helpers'; import { Type } from 'class-transformer'; import { ApiKeyObject } from './api-key-object'; import { APIKeyTypeEnum } from '../middleware/api-key'; +import { SelectQueryBuilder } from 'typeorm/query-builder/SelectQueryBuilder'; +import { UserChannelCollection, UserChannelCollectionType } from './user-channel-collection'; +import { Group as AuthGroup } from './cern-authorization-service'; @Entity('Channels') @Index(['slug', 'deleteDate'], { unique: true }) @@ -60,7 +68,7 @@ export class Channel extends ApiKeyObject { // @JoinTable() @JoinTable({ name: 'channels_members__users' }) @Type(() => User) - members: User[]; + members: Promise<User[]>; @IsOptional() @ManyToOne(type => Group, { cascade: true }) @@ -76,7 +84,7 @@ export class Channel extends ApiKeyObject { @ManyToMany(type => Group) @JoinTable({ name: 'channels_groups__groups' }) @Type(() => Group) - groups: Group[]; + groups: Promise<Group[]>; @OneToMany(type => Notification, notification => notification.target, { cascade: true, @@ -305,7 +313,7 @@ export class Channel extends ApiKeyObject { // Checks if user has authorization to send Notification to the channel // via Form/API - async canSendByFormWithCache(authorizationBag: AuthorizationBag, userGroups) { + async canSendByForm(transactionManager: EntityManager, authorizationBag: AuthorizationBag, userGroups: any[]) { // No permission to send by Form set if (!this.submissionByForm) return false; @@ -313,118 +321,304 @@ export class Channel extends ApiKeyObject { if (!authorizationBag) return false; // If user is an APIKey and sendByForm APIKey is allowed - if ( - this.submissionByForm.includes(SubmissionByForm.apikey) && - this.APIKey && - authorizationBag.isApiKey && - authorizationBag.APIKey.hasRights(APIKeyTypeEnum.Channel, this.id) - ) { - return true; - } + const hasApiKeyAccess = this.hasApiKeyAccess(authorizationBag); + console.debug('hasApiKeyAccess', hasApiKeyAccess); + if (hasApiKeyAccess) return true; + // If user is channel admin - if ( - this.submissionByForm.includes(SubmissionByForm.administrators) && - (await this.isAdminWithCache(authorizationBag, userGroups)) - ) { - return true; + if (this.submissionByForm.includes(SubmissionByForm.administrators)) { + const hasAdminAccess = await this.hasAdminAccess(transactionManager, authorizationBag, userGroups); + console.debug('hasAdminAccess', hasAdminAccess); + if (hasAdminAccess) return true; } + // If user is channel member - if ( - this.submissionByForm.includes(SubmissionByForm.members) && - (await this.isMemberWithCache(authorizationBag.user, userGroups)) - ) { - return true; + if (this.submissionByForm.includes(SubmissionByForm.members)) { + const hasMemberAccess = await this.hasMemberAccess(transactionManager, authorizationBag, userGroups); + console.debug('hasMemberAccess', hasMemberAccess); + if (hasMemberAccess) return true; } // Or else you are not authorized return false; } - // Checks if user has authorization to send Notification to the channel - // via Form/API - async canSendByForm(authorizationBag: AuthorizationBag, targetedNotification = false): Promise<boolean> { - // No permission to send by Form set - if (!this.submissionByForm) return false; + // Get QB to match with tags + static qbMatchTags(tagIds: string[]): Brackets { + return new Brackets(tagsQB => { + tagsQB.where('tags.id In (:...tags)', { tags: tagIds }); + }); + } - // No authorizationBag means no send - if (!authorizationBag) return false; + // Get QB to match with search category ids + static qbMatchCategory(categoryIds: string[]): Brackets { + return new Brackets(categoryQB => { + categoryQB.where('channel.category.id In (:...categoryIds)', { categoryIds: categoryIds }); + }); + } - // If user is an APIKey and sendByForm APIKey is allowed - if ( - this.submissionByForm.includes(SubmissionByForm.apikey) && - this.APIKey && - authorizationBag.isApiKey && - authorizationBag.APIKey.hasRights(APIKeyTypeEnum.Channel, this.id) - ) { - return true; - } - // If user is channel admin - if (this.submissionByForm.includes(SubmissionByForm.administrators) && (await this.isAdmin(authorizationBag))) { - return true; - } + // Get QB Brackets query search text match + static qbSearchText(searchText: string): Brackets { + console.log('search texing'); + return new Brackets(searchTextQB => { + searchTextQB.where( + 'channel.description Like :searchText or channel.name Like :searchText or owner.username Like :searchText', + { searchText: `%${searchText}%` }, + ); + }); + } - // Targeted notifications are not allowed for members - if (targetedNotification) { - return false; + static qbChannelsMemberViaUser(qb: SelectQueryBuilder<unknown>, userId: string, restricted = false): string { + const subQuery = qb + .subQuery() + .select('channel.id') + .from(Channel, 'channel') + .innerJoin('channel.members', 'members') + .where(':userId IN (members.id)', { + userId: userId, + }) + .distinct(); + if (restricted) { + qb.andWhere('channel.visibility = :visibility', { + visibility: Visibility.restricted, + }); } + return 'channel.id IN ' + subQuery.getQuery(); + } - // If user is channel member - if (this.submissionByForm.includes(SubmissionByForm.members) && (await this.isMember(authorizationBag.user))) { - return true; + static qbChannelsMemberViaGroup( + qb: SelectQueryBuilder<unknown>, + userGroups: AuthGroup[], + restricted = false, + ): string { + const subQuery = qb + .subQuery() + .select('channel.id') + .from(Channel, 'channel') + .innerJoin('channel.groups', 'groups') + .where('groups.id IN (:...userGroups)', { + userGroups: userGroups.map(g => g.groupId), + }) + .distinct(); + + if (restricted) { + subQuery.andWhere('channel.visibility = :visibility', { + visibility: Visibility.restricted, + }); } - // Or else you are not authorized - return false; + return 'channel.id IN ' + subQuery.getQuery(); } - async canSendCritical(authorizationBag: AuthorizationBag) { - if (!authorizationBag) return false; - if (!this.channelFlags?.includes(ChannelFlags.critical)) return false; + // Filter channels by QB according to the subscribed/owned/favorite toggle + static filterChannels( + qb: SelectQueryBuilder<unknown>, + userId: string, + userGroups: AuthGroup[], + supporter: boolean, + isOwner: boolean, + isSubscribed: boolean, + isFavorite: boolean, + ): Brackets { + return new Brackets(filterQB => { + if (isFavorite) { + filterQB.andWhere(sqb => { + const subQuery = sqb + .subQuery() + .select('collection.channelId') + .from(UserChannelCollection, 'collection') + .where('collection.type = :type', { + type: UserChannelCollectionType.FAVORITE, + }) + .andWhere('collection.userId = :userId', { + userId: userId, + }) + .getQuery(); + return 'channel.id IN ' + subQuery; + }); + + return; + } - if ( - this.submissionByForm.includes(SubmissionByForm.apikey) && - this.APIKey && - authorizationBag.isApiKey && - authorizationBag.APIKey.hasRights(APIKeyTypeEnum.Channel, this.id) - ) { - return true; - } - if (this.submissionByForm.includes(SubmissionByForm.administrators) && (await this.isAdmin(authorizationBag))) { - return true; - } + if (isOwner) { + filterQB.andWhere('owner.id = :userId or adminGroup.id IN (:...userGroups)', { + userId: userId, + userGroups: userGroups.map(g => g.groupId), + }); + + filterQB.andWhere( + new Brackets(qb => { + qb.where('owner.id = :userId', { + userId: userId, + }); + if (userGroups && userGroups.length > 0) + qb.orWhere('adminGroup.id IN (:...userGroups)', { + userGroups: userGroups.map(g => g.groupId), + }); + }), + ); + + return; + } - return false; - } + if (isSubscribed) { + filterQB.andWhere(sqb => { + const subQuery = sqb + .subQuery() + .select('channel.id') + .from(Channel, 'channel') + .leftJoin('channel.unsubscribed', 'unsubscribed') + .where(':userId IN (unsubscribed.id)', { + userId: userId, + }) + .getQuery(); + return 'channel.id NOT IN ' + subQuery; + }); + + filterQB.andWhere( + new Brackets(qb => { + qb.where(sqb => this.qbChannelsMemberViaUser(sqb, userId)); + if (userGroups && userGroups.length > 0) { + qb.orWhere(sqb => this.qbChannelsMemberViaGroup(sqb, userGroups)); + } + }), + ); + + return; + } - addGroup(group: Group) { - if (this.groups) { - if (!this.groups.find((g: Group) => g.groupIdentifier === group.groupIdentifier)) { - this.groups.push(group); + if (supporter) { + return; } - } else { - this.groups = [group]; - } + + // restricted, owned channels + filterQB.orWhere( + new Brackets(qb => { + qb.where('owner.id = :userId', { + userId: userId, + }); + if (userGroups && userGroups.length > 0) + qb.orWhere('adminGroup.id IN (:...userGroups)', { + userGroups: userGroups.map(g => g.groupId), + }); + }), + ); + + // restricted, subscribed channels + filterQB.orWhere(sqb => this.qbChannelsMemberViaUser(sqb, userId, true)); + + filterQB.orWhere(sqb => this.qbChannelsMemberViaGroup(sqb, userGroups, true)); + + filterQB.orWhere('owner.id = :userId', { + userId: userId, + }); + + filterQB.orWhere('channel.visibility In (:...visibleTypes)', { + visibleTypes: [Visibility.public, Visibility.internal], + userId: userId, + }); + }); } - removeGroup(group: Group) { - this.groups = this.groups.filter(g => g.id !== group.id); + // Get QB Brackets channels by access: where user is Admin + static qbUserIsAdmin(qb: SelectQueryBuilder<unknown>, userId: string, userGroups: any[]): Brackets { + return new Brackets(accessQB => { + accessQB.where('owner.id = :userId', { userId: userId }); + if (userGroups && userGroups.length > 0) { + accessQB.orWhere('adminGroup.id IN (:...userGroups)', { + userGroups: userGroups.map(g => g.groupId), + }); + } + }); } - addMember(user: User) { - if (this.members) { - if (!this.members.find(u => u.compareTo(user))) { - this.members.push(user); + // Get QB Brackets channels by access: where user is Member + static qbUserIsMember(userId: string, userGroups: any[]): Brackets { + return new Brackets(accessQB => { + accessQB.where(':userId IN (members.id)', { + userId: userId, + }); + if (userGroups && userGroups.length > 0) { + accessQB.orWhere('groups.id IN (:...userGroups)', { + userGroups: userGroups.map(g => g.groupId), + }); } - } else { - this.members = [user]; - } - if (this.unsubscribed && this.unsubscribed.length > 0) { - this.unsubscribed = this.unsubscribed.filter(u => !u.compareTo(user)); - } + }); + } + + // Filter channels by access: if user has authorization to send Notification to the channel via Form/API + static qbFilterByCanSendByForm( + qb: SelectQueryBuilder<unknown>, + authorizationBag: AuthorizationBag, + userGroups: any[], + isTargetedNotification = false, + channelId: string, + ): void { + // Filter by access + qb.andWhere( + // Administrators + new Brackets(submissiontypeQB => { + submissiontypeQB.where( + new Brackets(adminQB => { + adminQB.where(':administrators = ANY(channel.submissionByForm)', { + administrators: SubmissionByForm.administrators, + }); + adminQB.andWhere( + new Brackets(adminGroupQB => { + adminGroupQB.where('owner.id = :userId', { userId: authorizationBag.userId }); + if (userGroups && userGroups.length > 0) { + adminGroupQB.orWhere('adminGroup.id IN (:...userGroups)', { + userGroups: userGroups.map(g => g.groupId), + }); + } + }), + ); + }), + ); + + // Members + // Targeted notifications are not allowed for members + if (!isTargetedNotification) { + submissiontypeQB.orWhere( + new Brackets(memberQB => { + memberQB.where(':members = ANY(channel.submissionByForm)', { + members: SubmissionByForm.members, + }); + memberQB.andWhere( + new Brackets(userQB => { + userQB.where(':userId IN (members.id)', { + userId: authorizationBag.userId, + }); + if (userGroups && userGroups.length > 0) { + userQB.orWhere('groups.id IN (:...userGroups)', { + userGroups: userGroups.map(g => g.groupId), + }); + } + }), + ); + }), + ); + } + + // If user is an APIKey and sendByForm APIKey is allowed + if ( + authorizationBag.isApiKey && + authorizationBag.APIKey.type == APIKeyTypeEnum.Channel && + authorizationBag.APIKey.id == channelId + ) { + submissiontypeQB.orWhere(':apikey = ANY(channel.submissionByForm)', { + apikey: SubmissionByForm.apikey, + }); + } + }), + ); } - removeMember(user: User) { - this.members = this.members.filter(u => !u.compareTo(user)); + async canSendCritical(authorizationBag: AuthorizationBag) { + if (!authorizationBag) return false; + if (!this.channelFlags?.includes(ChannelFlags.critical)) return false; + + return false; } unsubscribeMember(user: User) { @@ -442,7 +636,8 @@ export class Channel extends ApiKeyObject { } private async getMembers(subscribedOnly: boolean): Promise<User[]> { - let members = this.members.map( + const channelMembers = await this.members; + let members = channelMembers.map( u => new User({ id: u.id, @@ -455,7 +650,8 @@ export class Channel extends ApiKeyObject { members = members.filter(u => !this.isUnsubscribed(u), this); } - for (const group of this.groups) { + const channelGroups = await this.groups; + for (const group of channelGroups) { let groupMembers = await group.getMembers(); if (subscribedOnly && this.unsubscribed) { groupMembers = groupMembers.filter(u => !this.isUnsubscribed(u), this); @@ -508,14 +704,202 @@ export class Channel extends ApiKeyObject { return false; } - if (ChannelHelpers.memoizedIsInMembers(this.members, user)) { + if (ChannelHelpers.memoizedIsInMembers(await this.members, user)) { return true; } - if (ChannelHelpers.memoizedIsInGroups(this.groups, userGroups)) { + if (ChannelHelpers.memoizedIsInGroups(await this.groups, userGroups)) { return true; } return false; } + + // --------- NEW TYPEORM ACCESS QUERIES -------------- // + // + // Most implementations use transactionManager.find instead of queryBuilder due to performance + // limitations in queryBuilder and relations. + // Guidelines: + // - avoid selecting relation arrays without implementing pagination + // - avoid using queryBuilder with multiple relation selects + // - avoid returning array relations in transactionManager find methods + // --------------------------------------------------- + hasApiKeyAccess(authorizationBag: AuthorizationBag): boolean { + return ( + this.submissionByForm.includes(SubmissionByForm.apikey) && + !!this.APIKey && + authorizationBag.isApiKey && + authorizationBag.APIKey.hasRights(APIKeyTypeEnum.Channel, this.id) + ); + } + + async hasMemberAccess( + transactionManager: EntityManager, + authorizationBag: AuthorizationBag, + userGroups: any[], + ): Promise<boolean> { + // check is member + const hasMemberAccessViaUser = await this.isMemberViaUser(transactionManager, authorizationBag.userId); + console.debug('hasMemberAccessViaUser', hasMemberAccessViaUser); + if (hasMemberAccessViaUser) return true; + + const hasMemberAccessViaGroup = await this.isMemberViaGroup(transactionManager, userGroups); + console.debug('hasMemberAccessViaGroup', hasMemberAccessViaGroup); + return hasMemberAccessViaGroup; + } + + async isMemberViaUser(transactionManager: EntityManager, userId: string): Promise<boolean> { + const isMemberViaUser = await transactionManager.findOne(Channel, { + relations: { + members: true, + }, + where: { + id: this.id, + members: { + id: Raw(alias => `:userId IN (${alias})`, { userId: userId }), + }, + }, + select: { + id: true, + members: {}, + }, + }); + + return !!isMemberViaUser; + } + + async isMemberViaGroup(transactionManager: EntityManager, userGroups: any[]): Promise<boolean> { + const isMemberViaGroup = await transactionManager.findOne(Channel, { + relations: { + groups: true, + }, + where: { + id: this.id, + groups: { + id: In(userGroups.map(g => g.groupId)), + }, + }, + select: { + id: true, + groups: {}, + }, + }); + + return !!isMemberViaGroup; + } + + async isGroupSubscribed(transactionManager: EntityManager, group: Group): Promise<boolean> { + const isGroupSubscribed = await transactionManager.findOne(Channel, { + relations: { + groups: true, + }, + where: { + id: this.id, + groups: { + id: Raw(alias => `:groupId IN (${alias})`, { groupId: group.id }), + }, + }, + select: { + id: true, + groups: {}, + }, + }); + + return !!isGroupSubscribed; + } + + async isUserSubscribed(transactionManager: EntityManager, user: User): Promise<boolean> { + const isMemberSubscribed = await transactionManager.findOne(Channel, { + relations: { + members: true, + }, + where: { + id: this.id, + members: { + id: Raw(alias => `:userId IN (${alias})`, { userId: user.id }), + }, + }, + select: { + id: true, + members: {}, + }, + }); + + return !!isMemberSubscribed; + } + + async isOwner(transactionManager: EntityManager, authorizationBag: AuthorizationBag): Promise<boolean> { + const isOwner = await transactionManager.findOne(Channel, { + relations: { + owner: true, + }, + where: { + id: this.id, + owner: { + id: authorizationBag.userId, + }, + }, + }); + + return !!isOwner; + } + + async isAdminViaGroups( + transactionManager: EntityManager, + authorizationBag: AuthorizationBag, + userGroups: any[], + ): Promise<boolean> { + const isAdmin = await transactionManager.findOne(Channel, { + relations: { + adminGroup: true, + }, + where: { + id: this.id, + adminGroup: { + id: In(userGroups.map(g => g.groupId)), + }, + }, + }); + + return !!isAdmin; + } + + async hasAdminAccess(txm: EntityManager, authBag: AuthorizationBag, userGroups: any[]): Promise<boolean> { + // queries with optional relations and where by relation property, the join changes from "left" to "inner" incorrectly + // https://github.com/typeorm/typeorm/issues/9395 + // const hasAdminAccess = await txm.findOne(Channel, { + // where: [ + // { + // id: this.id, + // owner: { + // id: authBag.userId, + // } + // }, + // { + // id: this.id, + // adminGroup: { + // id: In(userGroups.map(g => g.groupId)), + // }, + // }, + // ], + // }); + + return (await this.isOwner(txm, authBag)) || (await this.isAdminViaGroups(txm, authBag, userGroups)); + } + + async addGroup(transactionManager: EntityManager, group: Group): Promise<void> { + await transactionManager.createQueryBuilder().relation(Channel, 'groups').of(this).add(group); + } + + async removeGroup(transactionManager: EntityManager, group: Group): Promise<void> { + await transactionManager.createQueryBuilder().relation(Channel, 'groups').of(this).remove(group); + } + + async addUser(transactionManager: EntityManager, user: User): Promise<void> { + await transactionManager.createQueryBuilder().relation(Channel, 'members').of(this).add(user); + } + + async removeUser(transactionManager: EntityManager, user: User): Promise<void> { + await transactionManager.createQueryBuilder().relation(Channel, 'members').of(this).remove(user); + } } diff --git a/src/models/device.ts b/src/models/device.ts index 09a14412186337c94454a0e97b106b00ab98d2ad..7dd430b5fef2759a2cc1091a52499009ff5b6d69 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -1,72 +1,71 @@ -import { Entity, ManyToOne, PrimaryGeneratedColumn, Column, BeforeInsert, BeforeUpdate } from "typeorm"; -import { User } from "./user"; +import { Entity, ManyToOne, PrimaryGeneratedColumn, Column, BeforeInsert, BeforeUpdate } from 'typeorm'; +import { User } from './user'; export enum DeviceType { - BROWSER = 'BROWSER', - APP = 'APP', - MAIL = 'MAIL', + BROWSER = 'BROWSER', + APP = 'APP', + MAIL = 'MAIL', } export enum DeviceSubType { - SAFARI = 'SAFARI', - OTHER = 'OTHER', - IOS = 'IOS', - ANDROID = 'ANDROID', - WINDOWS = 'WINDOWS', - LINUX = 'LINUX', - MAC = 'MAC', - MATTERMOST = 'MATTERMOST', - PRIMARY = 'PRIMARY' + SAFARI = 'SAFARI', + OTHER = 'OTHER', + IOS = 'IOS', + ANDROID = 'ANDROID', + WINDOWS = 'WINDOWS', + LINUX = 'LINUX', + MAC = 'MAC', + MATTERMOST = 'MATTERMOST', + PRIMARY = 'PRIMARY', } -@Entity("Devices") +@Entity('Devices') export class Device { - @PrimaryGeneratedColumn("uuid") - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ nullable: false }) - name: string; + @Column({ nullable: false }) + name: string; - @ManyToOne((type) => User, (user) => user.devices) - user: User; + @ManyToOne(type => User, user => user.devices) + user: User; - @Column({ nullable: true }) - info: string; + @Column({ nullable: true }) + info: string; - @Column('varchar') - type: DeviceType; + @Column('varchar') + type: DeviceType; - @Column('varchar', { - nullable: true, - }) - subType: DeviceSubType; + @Column('varchar', { + nullable: true, + }) + subType: DeviceSubType; - // Contains a changeable random anonymous id, mainly for Apple devices - @Column({ type:"uuid", nullable: true }) - uuid: string; + // Contains a changeable random anonymous id, mainly for Apple devices + @Column({ type: 'uuid', nullable: true }) + uuid: string; - @Column({ nullable: false }) - token: string; + @Column({ nullable: false }) + token: string; - // Trim token in case it's between quotes (mostly Safari business) - @BeforeUpdate() - @BeforeInsert() - async trimToken(): Promise<void> { - if (!this.token) - return; - if (this.token.charAt(0) === '"' && this.token.charAt(this.token.length -1) === '"') - this.token = this.token.substr(1, this.token.length -2); - } + // Trim token in case it's between quotes (mostly Safari business) + @BeforeUpdate() + @BeforeInsert() + async trimToken(): Promise<void> { + if (!this.token) return; + if (this.token.charAt(0) === '"' && this.token.charAt(this.token.length - 1) === '"') + this.token = this.token.substr(1, this.token.length - 2); + } - constructor(device) { - if (device) { - this.name = device.name; - this.user = device.user; - this.info = device.info; - this.type = device.type; - this.subType = device.subType; - this.uuid = device.uuid; - this.token = device.token; - } + constructor(device) { + if (device) { + this.name = device.name; + this.user = device.user; + this.info = device.info; + this.type = device.type; + this.subType = device.subType; + this.uuid = device.uuid; + this.token = device.token; } + } } diff --git a/src/models/mute.ts b/src/models/mute.ts index dac91a6c1abae9a45b058abe6b83ebd04c4fb5c7..6ad0ab9699f084449152ac69a778bcc1ee8be54d 100644 --- a/src/models/mute.ts +++ b/src/models/mute.ts @@ -1,28 +1,23 @@ -import { - Entity, - ManyToOne, - PrimaryGeneratedColumn, - Column, -} from "typeorm"; -import { User } from "./user"; -import { Channel } from "./channel"; -import { BadRequestError } from "routing-controllers"; -import { EntityManager } from "typeorm"; +import { Entity, ManyToOne, PrimaryGeneratedColumn, Column } from 'typeorm'; +import { User } from './user'; +import { Channel } from './channel'; +import { BadRequestError } from 'routing-controllers'; +import { EntityManager } from 'typeorm'; import { AESCypher } from '../middleware/aes-cipher'; -@Entity("Mutes") +@Entity('Mutes') export class Mute { - @PrimaryGeneratedColumn("uuid") + @PrimaryGeneratedColumn('uuid') id: string; - @ManyToOne((type) => User, (user) => user.mutes) + @ManyToOne(type => User, user => user.mutes) user: User; - @ManyToOne((type) => Channel, { nullable: true }) + @ManyToOne(type => Channel, { nullable: true }) target: Channel; @Column({ - enum: ["PERMANENT", "RANGE"], + enum: ['PERMANENT', 'RANGE'], }) type: string; @@ -45,49 +40,50 @@ export class Mute { } } - async validateMute(transactionManager: EntityManager) - { + async validateMute(transactionManager: EntityManager) { // If PERMANENT, clean start and end date if any if (this.type === 'PERMANENT') { this.start = null; this.end = null; } - if ((this.type === 'RANGE') && !this.end) - throw new BadRequestError("Invalid Mute End Date: if mute range selected, end date must be set"); + if (this.type === 'RANGE' && !this.end) + throw new BadRequestError('Invalid Mute End Date: if mute range selected, end date must be set'); - if ((this.start === this.end) && this.start && this.end) - throw new BadRequestError("Invalid Mute Date Range: start date and end date must be different"); + if (this.start === this.end && this.start && this.end) + throw new BadRequestError('Invalid Mute Date Range: start date and end date must be different'); - if (this.end && (this.end < new Date())) { + if (this.end && this.end < new Date()) { throw new BadRequestError("Invalid Mute End Date: end time can't be in the past"); } - if (this.start && this.end && (this.start >= this.end)) { - throw new BadRequestError("Invalid Mute dates: start must be before end"); + if (this.start && this.end && this.start >= this.end) { + throw new BadRequestError('Invalid Mute dates: start must be before end'); } // Check is a PERMANENT Global mute already exists for this user if (this.type === 'PERMANENT' && this.target == null) { - if (await transactionManager.findOne(Mute, { - relations: ["target"], - where: { - user: this.user, - type: "PERMANENT", - target: null, - }, - })) - throw new BadRequestError("A Global Permanent Mute already exists for this user."); + if ( + await transactionManager.findOne(Mute, { + relations: ['target'], + where: { + user: { + id: this.user, + }, + type: 'PERMANENT', + target: null, + }, + }) + ) + throw new BadRequestError('A Global Permanent Mute already exists for this user.'); } - + return true; } - } export function decodeEmail(encryptedEmail: string) { - if (!encryptedEmail) - throw new BadRequestError("Invalid encrypted data"); + if (!encryptedEmail) throw new BadRequestError('Invalid encrypted data'); return AESCypher.decrypt(encryptedEmail); } diff --git a/src/models/user-channel-collection.ts b/src/models/user-channel-collection.ts index 6fa0208d694b553b81189602de16ae35074ffd39..40c00279e1993ebdc1fef8c94b597c5e425fc4ba 100644 --- a/src/models/user-channel-collection.ts +++ b/src/models/user-channel-collection.ts @@ -1,11 +1,4 @@ -import { - Entity, - Index, - JoinColumn, - ManyToOne, - PrimaryColumn, - RelationId, -} from 'typeorm'; +import { Entity, Index, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from 'typeorm'; import { Channel } from './channel'; import { User } from './user'; @@ -19,28 +12,24 @@ export class UserChannelCollection { @PrimaryColumn('varchar') type: UserChannelCollectionType; - @ManyToOne(type => User, { primary: true }) + @PrimaryColumn() + @RelationId((userChannelCollection: UserChannelCollection) => userChannelCollection.user) + userId: string; + + @PrimaryColumn() + @RelationId((userChannelCollection: UserChannelCollection) => userChannelCollection.channel) + channelId: string; + + @ManyToOne(type => User) @JoinColumn() @Index() user: User; - @RelationId( - (userChannelCollection: UserChannelCollection) => - userChannelCollection.user, - ) - userId: string; - @Index() - @ManyToOne(type => Channel, { primary: true }) + @ManyToOne(type => Channel) @JoinColumn() channel: Channel; - @RelationId( - (userChannelCollection: UserChannelCollection) => - userChannelCollection.channel, - ) - channelId: string; - constructor(collection: UserChannelCollection) { if (collection) { this.channel = collection.channel; diff --git a/src/services/impl/api-key/verify-api-key.ts b/src/services/impl/api-key/verify-api-key.ts index 80c456a8aa091de1ab939afaa3ea5c97135ce97c..6a1e5a5297eb06db149d9575ff9f09c4fd573444 100644 --- a/src/services/impl/api-key/verify-api-key.ts +++ b/src/services/impl/api-key/verify-api-key.ts @@ -7,11 +7,7 @@ export class VerifyApiKey implements Command { constructor(private id: string, private key: string, private entity: EntityTarget<ApiKeyObject>) {} async execute(transactionManager: EntityManager): Promise<boolean> { - const obj = await transactionManager.findOne(this.entity, { - where: { - id: this.id, - }, - }); + const obj = await transactionManager.findOneBy(this.entity, { id: this.id }); if (!obj) return false; diff --git a/src/services/impl/auth/create-update-user/create-mattermost-device.ts b/src/services/impl/auth/create-update-user/create-mattermost-device.ts index 9c0885a4dc7d85907f5b7bb4cd365d14c646befd..240e13ff08cb85e80223cc8ab1c75f14be29fa3c 100644 --- a/src/services/impl/auth/create-update-user/create-mattermost-device.ts +++ b/src/services/impl/auth/create-update-user/create-mattermost-device.ts @@ -1,40 +1,43 @@ -import { Command } from "../../command"; -import { EntityManager } from "typeorm"; -import { User } from "../../../../models/user"; -import { Device } from "../../../../models/device"; +import { Command } from '../../command'; +import { EntityManager } from 'typeorm'; +import { User } from '../../../../models/user'; +import { Device } from '../../../../models/device'; export class CreateMattermostDevice implements Command { + constructor(private user: User) {} - constructor(private user: User) { } - - async execute(transactionManager: EntityManager): Promise<Device> { - try { - // Get Mattermost device if exists - let mmdevice = await transactionManager.findOne(Device, { - relations: ["user"], - where: { - user: this.user.id, - info: this.user.email, - type: "APP", - subType: "MATTERMOST", - token: this.user.email, - }, - }); - if (!mmdevice) { - // Create Mattermost Device with user mail address - mmdevice = await transactionManager.save(new Device({ - name: "Mattermost", - user: this.user, - info: this.user.email, - type: "APP", - subType: "MATTERMOST", - token: this.user.email, - })); - } - return mmdevice; - } catch (ex) { - console.error(ex); - throw new Error("Failed to create Mattermost device."); - } + async execute(transactionManager: EntityManager): Promise<Device> { + try { + // Get Mattermost device if exists + let mmdevice = await transactionManager.findOne(Device, { + relations: ['user'], + where: { + user: { + id: this.user.id, + }, + info: this.user.email, + type: 'APP', + subType: 'MATTERMOST', + token: this.user.email, + }, + }); + if (!mmdevice) { + // Create Mattermost Device with user mail address + mmdevice = await transactionManager.save( + new Device({ + name: 'Mattermost', + user: this.user, + info: this.user.email, + type: 'APP', + subType: 'MATTERMOST', + token: this.user.email, + }), + ); + } + return mmdevice; + } catch (ex) { + console.error(ex); + throw new Error('Failed to create Mattermost device.'); } + } } diff --git a/src/services/impl/auth/create-update-user/update-user-email.ts b/src/services/impl/auth/create-update-user/update-user-email.ts index c382dcee44793a2fe05ee6ba5ab80e009fe5574e..8162be651028b19c2b3675158e0f3f4a5cf8b608 100644 --- a/src/services/impl/auth/create-update-user/update-user-email.ts +++ b/src/services/impl/auth/create-update-user/update-user-email.ts @@ -1,55 +1,58 @@ -import { Command } from "../../command"; -import { EntityManager } from "typeorm"; -import { User } from "../../../../models/user"; -import { Device } from "../../../../models/device"; +import { Command } from '../../command'; +import { EntityManager } from 'typeorm'; +import { User } from '../../../../models/user'; +import { Device } from '../../../../models/device'; export class UpdateUserEmail implements Command { + constructor(private user: User, private email: string) {} - constructor(private user: User, private email: string) { } + async execute(transactionManager: EntityManager): Promise<User> { + try { + // Update User + this.user.email = this.email; + transactionManager.save(this.user); - async execute(transactionManager: EntityManager): Promise<User> { - try { - // Update User - this.user.email = this.email; - transactionManager.save(this.user); + // Update default device with new mail + const device = await transactionManager.findOne(Device, { + relations: ['user'], + where: { + user: { + id: this.user.id, + }, + name: this.user.email, + type: 'MAIL', + token: this.user.email, + }, + }); + if (device) { + device.name = this.email; + device.token = this.email; + transactionManager.save(device); + } - // Update default device with new mail - const device = await transactionManager.findOne(Device, { - relations: ["user"], - where: { - user: this.user.id, - name: this.user.email, - type: "MAIL", - token: this.user.email, - }, - }); - if (device) { - device.name = this.email; - device.token = this.email; - transactionManager.save(device); - } + // Update Mattermost device with new mail + const mmdevice = await transactionManager.findOne(Device, { + relations: ['user'], + where: { + user: { + id: this.user.id, + }, + info: this.user.email, + type: 'APP', + subType: 'MATTERMOST', + token: this.user.email, + }, + }); + if (mmdevice) { + mmdevice.info = this.email; + mmdevice.token = this.email; + transactionManager.save(mmdevice); + } - // Update Mattermost device with new mail - const mmdevice = await transactionManager.findOne(Device, { - relations: ["user"], - where: { - user: this.user.id, - info: this.user.email, - type: "APP", - subType: "MATTERMOST", - token: this.user.email, - }, - }); - if (mmdevice) { - mmdevice.info = this.email; - mmdevice.token = this.email; - transactionManager.save(mmdevice); - } - - return this.user; - } catch (ex) { - console.error(ex, this.email); - throw new Error("Failed to update user or device with new email."); - } + return this.user; + } catch (ex) { + console.error(ex, this.email); + throw new Error('Failed to update user or device with new email.'); } + } } diff --git a/src/services/impl/auth/get-authenticated-user/GetAuthenticatedUser.ts b/src/services/impl/auth/get-authenticated-user/GetAuthenticatedUser.ts index 83e2642aa20c28506577d9ac02630a61ee4d2363..c5630afff24108aaa6628ade777aa5e53d7a60f8 100644 --- a/src/services/impl/auth/get-authenticated-user/GetAuthenticatedUser.ts +++ b/src/services/impl/auth/get-authenticated-user/GetAuthenticatedUser.ts @@ -1,15 +1,11 @@ -import { Command } from "../../command"; -import { EntityManager } from "typeorm"; -import { User } from "../../../../models/user"; +import { Command } from '../../command'; +import { EntityManager } from 'typeorm'; +import { User } from '../../../../models/user'; -export class GetAuthenticatedUser implements Command{ +export class GetAuthenticatedUser implements Command { + constructor(private username: string) {} - constructor(private username: string){ - - } - - async execute(transactionManager: EntityManager): Promise<User> { - return await transactionManager.findOne(User, {username: this.username} ) - } - -} \ No newline at end of file + async execute(transactionManager: EntityManager): Promise<User> { + return await transactionManager.findOneBy(User, { username: this.username }); + } +} diff --git a/src/services/impl/categories/create-category.ts b/src/services/impl/categories/create-category.ts index c8f9be47ccf2ef1c8abf821e7733ea0e6a8e223c..49c4374540de531059a2e23be5796c724d3d873c 100644 --- a/src/services/impl/categories/create-category.ts +++ b/src/services/impl/categories/create-category.ts @@ -14,7 +14,7 @@ export class CreateCategory implements Command { } // Check if category already exists if ( - await transactionManager.findOne(Category, { + await transactionManager.findOneBy(Category, { name: this.category.name, }) ) { @@ -24,7 +24,7 @@ export class CreateCategory implements Command { const newCategory = new Category({ ...this.category }); if (this.category.parent) { - const parentCategory = await transactionManager.findOne(Category, { + const parentCategory = await transactionManager.findOneBy(Category, { name: this.category.parent, }); if (!parentCategory) { diff --git a/src/services/impl/channels/add-group-to-channel.ts b/src/services/impl/channels/add-group-to-channel.ts index eae547a090712abc44bb182fbb8084100257df26..4621963d154b67c16a630d3c415c3590082e0a89 100644 --- a/src/services/impl/channels/add-group-to-channel.ts +++ b/src/services/impl/channels/add-group-to-channel.ts @@ -6,10 +6,7 @@ import { AuthorizationBag } from '../../../models/authorization-bag'; import { ServiceFactory } from '../../services-factory'; import { GroupsServiceInterface } from '../../groups-service'; import { AuditChannels } from '../../../log/auditing'; -import { ChannelResponse } from '../../../controllers/channels/dto'; -import { Group } from '../../../models/group'; import { GroupResponse } from '../../../controllers/channels/dto'; -import { CernAuthorizationService } from '../../../models/cern-authorization-service'; export class AddGroupToChannel implements Command { constructor(private groupName: string, private channelId: string, private authorizationBag: AuthorizationBag) {} @@ -29,15 +26,13 @@ export class AddGroupToChannel implements Command { if (!(await channel.isAdmin(this.authorizationBag))) throw new ForbiddenError("You don't have the rights to manage this channel."); - //if added group was already added - if (channel.groups.filter(g => g.groupIdentifier === this.groupName).length > 0) { + const groupToAdd = await this.groupsService.GetOrCreateGroup(this.groupName); + if (await channel.isGroupSubscribed(transactionManager, groupToAdd)) { throw new ForbiddenError(`Group ${this.groupName} is already a member.`); } - const groupToAdd = await this.groupsService.GetOrCreateGroup(this.groupName); - channel.addGroup(groupToAdd); - const updatedChannel = await transactionManager.save(channel); - await AuditChannels.setValue(updatedChannel.id, { + await channel.addGroup(transactionManager, groupToAdd); + await AuditChannels.setValue(channel.id, { event: 'AddGroup', user: this.authorizationBag.email, groupIdentifier: this.groupName, diff --git a/src/services/impl/channels/add-member-to-channel.ts b/src/services/impl/channels/add-member-to-channel.ts index f2f8f981c5c8c7a98228282b66e03f0a56501667..c3d2f5bdcd7d7819d4ccca584cbe73ef18038d89 100644 --- a/src/services/impl/channels/add-member-to-channel.ts +++ b/src/services/impl/channels/add-member-to-channel.ts @@ -1,57 +1,106 @@ import { Command } from '../command'; import { EntityManager } from 'typeorm'; import { Channel } from '../../../models/channel'; -import { User } from '../../../models/user'; import { ForbiddenError, NotFoundError } from 'routing-controllers'; import { ServiceFactory } from '../../services-factory'; import { AuthorizationBag } from '../../../models/authorization-bag'; import { UsersServiceInterface } from '../../users-service'; import { AuditChannels } from '../../../log/auditing'; -import { AuthService } from '../../../services/auth-service'; +import { MemberResponse } from '../../../controllers/channels/dto'; import { CernAuthorizationService } from '../../../models/cern-authorization-service'; -import { MemberResponse, ChannelResponse } from '../../../controllers/channels/dto'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { appsignal } = require('../../../../appsignal'); // Update to the location used in the previous step export class AddMemberToChannel implements Command { constructor(private membername: string, private channelId: string, private authorizationBag: AuthorizationBag) {} usersService: UsersServiceInterface = ServiceFactory.getUserService(); - get newUser(): User { - if (!this.membername) return null; - if (this.membername.includes('@')) return new User({ email: this.membername }); - return new User({ username: this.membername }); - } - async execute(transactionManager: EntityManager): Promise<MemberResponse> { + const tracer = appsignal.tracer(); + const span = tracer.createSpan(undefined, tracer.currentSpan()); + span.setCategory('services/impl'); + span.setName('AddMemberToChannel'); + + let child = span.child(); + child.setCategory('get_channel_via_findOne'); const channel = await transactionManager.findOne(Channel, { - relations: ['owner', 'adminGroup', 'members', 'groups'], - where: { - id: this.channelId, + relations: { + owner: true, + adminGroup: true, }, + where: { id: this.channelId }, }); - if (!channel) throw new NotFoundError('Channel does not exist'); + child.close(); - // you need to be channel admin - if (!(await channel.isAdmin(this.authorizationBag))) + if (!channel) { + span.close(); + throw new NotFoundError('Channel does not exist'); + } + + child = span.child(); + child.setCategory('getCurrentUserGroups'); + // Get current user groups, to be used next in SQL to compare with channel groups + const userGroups = await CernAuthorizationService.getCurrentUserGroups(this.authorizationBag.userName); + child.close(); + + child = span.child(); + child.setCategory('isAdmin'); + const hasAdminAccess = await channel.hasAdminAccess(transactionManager, this.authorizationBag, userGroups); + console.debug('hasAdminAccess', hasAdminAccess); + if (!hasAdminAccess) { + child.close(); + span.close(); throw new ForbiddenError("You don't have the rights to manage this channel."); - //if added user is already a direct member or a group member - if (await channel.isMember(this.newUser)) { - throw new ForbiddenError(`User ${this.membername} is already a member or in a member group.`); } + child.close(); + child = span.child(); + child.setCategory('GetOrCreateUserFromUsernameOrEmail'); const userToAdd = await this.usersService.GetOrCreateUserFromUsernameOrEmail(this.membername.toLowerCase().trim()); + child.close(); + + child = span.child(); + child.setCategory('isMemberViaUser'); + const isMemberViaUser = await channel.isMemberViaUser(transactionManager, userToAdd.id); + console.debug('userToAdd.id', userToAdd.id); + console.debug('isMemberViaUser', isMemberViaUser); + if (isMemberViaUser) { + child.close(); + span.close(); + throw new ForbiddenError( + `User ${this.membername} / ${userToAdd.email} is already a member or in a member group.`, + ); + } + child.close(); - //if resolved new user is already a direct member or a group member - if (await channel.isMember(userToAdd)) { + child = span.child(); + child.setCategory('getMemberUserGroups'); + let memberGroups = []; + if (userToAdd.username) { + // Get new member groups, to be used next in SQL to compare with channel groups + memberGroups = await CernAuthorizationService.getCurrentUserGroups(userToAdd.username); + } + child.close(); + + child = span.child(); + child.setCategory('isMemberViaGroup'); + const isMemberViaGroup = await channel.isMemberViaGroup(transactionManager, memberGroups); + console.debug('isMemberViaGroup', isMemberViaGroup); + if (isMemberViaGroup) { + child.close(); + span.close(); throw new ForbiddenError( `User ${this.membername} / ${userToAdd.email} is already a member or in a member group.`, ); } + child.close(); - channel.addMember(userToAdd); - //const ret = await transactionManager.save(channel); // optimized save performance - await transactionManager.createQueryBuilder().relation(Channel, 'members').of(channel).add(userToAdd); + child = span.child(); + child.setCategory('save_via_query_builder'); + await channel.addUser(transactionManager, userToAdd); + child.close(); await AuditChannels.setValue(channel.id, { event: 'AddMember', @@ -59,6 +108,8 @@ export class AddMemberToChannel implements Command { membername: this.membername, }); + span.close(); + return new MemberResponse(userToAdd); } } diff --git a/src/services/impl/channels/create-channel.ts b/src/services/impl/channels/create-channel.ts index 16d492b5da5f7ec78325fe195a34293eaa4fda1a..5d6d2a03336a0d5945ccaaedcca956b77597fe49 100644 --- a/src/services/impl/channels/create-channel.ts +++ b/src/services/impl/channels/create-channel.ts @@ -16,7 +16,7 @@ export class CreateChannel implements Command { async execute(transactionManager: EntityManager): Promise<ChannelResponse> { // Get owner (user who have created the channel) to set as user by default - const user = await transactionManager.findOne(User, { + const user = await transactionManager.findOneBy(User, { id: this.authorizationBag.userId, }); @@ -36,14 +36,14 @@ export class CreateChannel implements Command { } if ( - await transactionManager.findOne(Channel, { + await transactionManager.findOneBy(Channel, { name: channel.name, }) ) { throw new BadRequestError('Channel name already exists'); } if ( - await transactionManager.findOne(Channel, { + await transactionManager.findOneBy(Channel, { slug: channel.slug, }) ) { @@ -51,7 +51,7 @@ export class CreateChannel implements Command { } if (this.channel.adminGroup) { - const adminGroup = await transactionManager.findOne(Group, { groupIdentifier: this.channel.adminGroup }); + const adminGroup = await transactionManager.findOneBy(Group, { groupIdentifier: this.channel.adminGroup }); if (!adminGroup) throw new NotFoundError('Admin group does not exist'); if (adminGroup.groupIdentifier != this.channel.adminGroup) throw new Error('Admin group name does not match provided id.'); diff --git a/src/services/impl/channels/find-all-channels.ts b/src/services/impl/channels/find-all-channels.ts index 974912379a9481e781f423041ca0deff74f88289..c6b043db1338eda7a5ef49cd226e53e675068b29 100644 --- a/src/services/impl/channels/find-all-channels.ts +++ b/src/services/impl/channels/find-all-channels.ts @@ -1,12 +1,14 @@ import { Command } from '../command'; -import { Brackets, EntityManager } from 'typeorm'; +import { EntityManager } from 'typeorm'; import { Channel } from '../../../models/channel'; import { AuthorizationBag } from '../../../models/authorization-bag'; import { CernAuthorizationService } from '../../../models/cern-authorization-service'; -import { UserChannelCollection, UserChannelCollectionType } from '../../../models/user-channel-collection'; import { UnauthorizedError } from 'routing-controllers'; import { Category } from '../../../models/category'; import { ChannelsListResponse, GetChannelResponse, ChannelsQuery } from '../../../controllers/channels/dto'; +import { SubmissionByForm } from '../../../models/channel-enums'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { appsignal } = require('../../../../appsignal'); // Update to the location used in the previous step export class FindAllChannels implements Command { constructor(private query: ChannelsQuery, private authorizationBag: AuthorizationBag) {} @@ -15,170 +17,61 @@ export class FindAllChannels implements Command { if (!this.authorizationBag) throw new UnauthorizedError('Access unauthorized. Please authenticate or use /public/channels instead.'); - console.time('get-all-channels:total:' + this.authorizationBag.userName); + const tracer = appsignal.tracer(); + const span = tracer.createSpan(undefined, tracer.currentSpan()); + span.setCategory('services/impl'); + span.setName('FindAllChannels'); - console.time('get-all-channels:grappa:' + this.authorizationBag.userName); + let child = span.child(); + child.setCategory('getCurrentUserGroups'); // Get current user groups, to be used next in SQL to compare with channel groups const userGroups = await CernAuthorizationService.getCurrentUserGroups(this.authorizationBag.userName); - console.timeEnd('get-all-channels:grappa:' + this.authorizationBag.userName); + child.close(); - console.time('get-all-channels:build-query:' + this.authorizationBag.userName); // Initial query with left joins but no select to avoid slow typeorm aggregation const qb = await transactionManager .getRepository(Channel) .createQueryBuilder('channel') - .leftJoin('channel.members', 'members') - .leftJoin('channel.groups', 'groups') - .leftJoin('channel.unsubscribed', 'unsubscribed') .leftJoin('channel.owner', 'owner') - .leftJoin('channel.adminGroup', 'adminGroup') - .leftJoin('channel.category', 'category') - .leftJoin('channel.tags', 'tags'); + .leftJoin('channel.adminGroup', 'adminGroup'); - if (this.query.searchText) { - qb.andWhere( - new Brackets(qb => { - qb.where( - 'channel.description Like :searchText or channel.name Like :searchText or owner.username Like :searchText', - { searchText: `%${this.query.searchText}%` }, - ); - }), - ); - } + if (this.query.searchText) qb.andWhere(Channel.qbSearchText(this.query.searchText)); if (this.query.category) { - const searchParameterCategory = new Category({}); - searchParameterCategory.id = this.query.category; - const descendants = await transactionManager.getTreeRepository(Category).findDescendants(searchParameterCategory); - const descendantIds = descendants.map(cat => cat.id); - qb.andWhere(sqb => { - const subQuery = sqb - .subQuery() - .select('channel.id') - .from(Channel, 'channel') - .leftJoin('channel.category', 'category') - .where('category.id IN (:...categories)', { categories: descendantIds }) - .getQuery(); - return 'channel.id IN ' + subQuery; - }); + const categoryIds = await Category.getDescendantIds(transactionManager, this.query.category); + qb.leftJoin('channel.category', 'category'); + qb.andWhere(Channel.qbMatchCategory(categoryIds)); } if (this.query.tags) { - console.debug(this.query.tags); - // filter must include all tags - qb.andWhere(sqb => { - const subQuery = sqb - .subQuery() - .select('channel.id') - .from(Channel, 'channel') - .leftJoin('channel.tags', 'tags') - .where('tags.id IN (:...tags)', { tags: this.query.tags }) - .groupBy('channel.id') - .having('COUNT(*) = :count', { count: this.query.tags.length }) - .getQuery(); - return 'channel.id IN ' + subQuery; - }); + qb.leftJoin('channel.tags', 'tags'); + qb.andWhere(Channel.qbMatchTags(this.query.tags)); } - // Filter by member, group, admin, etc. qb.andWhere( - new Brackets(channelsQB => { - if (this.query.ownerFilter) { - channelsQB.orWhere('owner.id = :userId', { - userId: this.authorizationBag.userId, - }); - if (userGroups && userGroups.length > 0) - channelsQB.orWhere('adminGroup.id IN (:...userGroups)', { - userGroups: userGroups.map(g => g.groupId), - }); - - return; - } - if (this.query.subscribedFilter) { - channelsQB.orWhere(':userId IN (members.id)', { - userId: this.authorizationBag.userId, - }); - if (userGroups && userGroups.length > 0) - channelsQB.orWhere('groups.id IN (:...userGroups)', { - userGroups: userGroups.map(g => g.groupId), - }); - - return; - } - - if (!this.authorizationBag.isSupporter) { - channelsQB - .orWhere("channel.visibility = 'PUBLIC'") - .orWhere("channel.visibility = 'INTERNAL'") - .orWhere('owner.id = :userId', { - userId: this.authorizationBag.userId, - }); - - if (userGroups && userGroups.length > 0) { - channelsQB.orWhere('adminGroup.id IN (:...userGroups)', { - userGroups: userGroups.map(g => g.groupId), - }); - } - channelsQB.orWhere(':userId IN (members.id)', { - userId: this.authorizationBag.userId, - }); - if (userGroups && userGroups.length > 0) { - channelsQB.orWhere('groups.id IN (:...userGroups)', { - userGroups: userGroups.map(g => g.groupId), - }); - } - } - }), + Channel.filterChannels( + qb, + this.authorizationBag.userId, + userGroups, + this.authorizationBag.isSupporter, + this.query.ownerFilter, + this.query.subscribedFilter, + this.query.favoritesFilter, + ), ); - if (this.query.subscribedFilter) { - // exclude all unsubscribed channels - qb.andWhere(sqb => { - const subQuery = sqb - .subQuery() - .select('channel.id') - .from(Channel, 'channel') - .leftJoin('channel.unsubscribed', 'unsubscribed') - .where(':userId IN (unsubscribed.id)', { - userId: this.authorizationBag.userId, - }) - .getQuery(); - return 'channel.id NOT IN ' + subQuery; - }); - } - - if (this.query.favoritesFilter) { - qb.andWhere(sqb => { - const subQuery = sqb - .subQuery() - .select('collection.channelId') - .from(UserChannelCollection, 'collection') - .where('collection.type = :type', { - type: UserChannelCollectionType.FAVORITE, - }) - .andWhere('collection.userId = :userId', { - userId: this.authorizationBag.userId, - }) - .getQuery(); - return 'channel.id IN ' + subQuery; - }); - } - console.timeEnd('get-all-channels:build-query:' + this.authorizationBag.userName); - - console.time('get-all-channels:count:' + this.authorizationBag.userName); - // query count + child = span.child(); + child.setCategory('count'); const count = await qb.clone().getCount(); - console.timeEnd('get-all-channels:count:' + this.authorizationBag.userName); + child.close(); // Nothing found, no match, we return an empty list if (count === 0) { - console.timeEnd('get-all-channels:total:' + this.authorizationBag.userName); + span.close(); return new ChannelsListResponse(); } - console.time('get-all-channels:query:' + this.authorizationBag.userName); - // query channel_ids and filter, skip and take only what we need for this page qb.select(['channel.id', 'channel.name']) // select channel_id .orderBy('channel.name', 'ASC') @@ -186,16 +79,17 @@ export class FindAllChannels implements Command { .limit(this.query.take || 10) .offset(this.query.skip || 0); + child = span.child(); + child.setCategory('getRawMany'); const channel_ids = await qb.getRawMany(); - console.timeEnd('get-all-channels:query:' + this.authorizationBag.userName); + child.close(); - console.time('get-all-channels:query-page:' + this.authorizationBag.userName); // select channels and relations for this list of channel_ids only + child = span.child(); + child.setCategory('getSlice'); const channels = await transactionManager .getRepository(Channel) .createQueryBuilder('channel') - .leftJoinAndSelect('channel.members', 'members') - .leftJoinAndSelect('channel.groups', 'groups') .leftJoinAndSelect('channel.unsubscribed', 'unsubscribed') .leftJoinAndSelect('channel.owner', 'owner') .leftJoinAndSelect('channel.adminGroup', 'adminGroup') @@ -207,59 +101,23 @@ export class FindAllChannels implements Command { .loadRelationCountAndMap('channel.notificationCount', 'channel.notifications') .orderBy('channel.name', 'ASC') .getMany(); + child.close(); - console.timeEnd('get-all-channels:query-page:' + this.authorizationBag.userName); - - console.time('get-all-channels:build-return:' + this.authorizationBag.userName); + child = span.child(); + child.setCategory('GetChannelResponse'); const returnChannels: GetChannelResponse[] = await Promise.all( channels.map(async channel => { - // Clearing privacy items, used in relation above for permissions queries - // But should not be returned to the frontend for display - // at least don't return channel.owner, channel.adminGroup, channel.members, channel.groups - - return new GetChannelResponse( - channel, - await channel.isSubscribedWithCache(this.authorizationBag.user, userGroups), - await channel.isAdminWithCache(this.authorizationBag, userGroups), - await channel.canSendByFormWithCache(this.authorizationBag, userGroups), - ); - - /* - let filteredChannel = { - id: channel.id, - slug: channel.slug, - name: channel.name, - description: channel.description, - //unsubscribed: channel.unsubscribed, // should we return this in the main channel list? - visibility: channel.visibility, - subscriptionPolicy: channel.subscriptionPolicy, - archive: channel.archive, - // new version with memoizee, to fix - subscribed: await channel.isSubscribedWithCache(this.authorizationBag.user, userGroups), - manage: await channel.isAdminWithCache(this.authorizationBag, userGroups), - send: await channel.canSendByFormWithCache(this.authorizationBag, userGroups), - category: channel.category, - tags: channel.tags, - channelFlags: channel.channelFlags, - relationships: { - notifications: { - count: channel.notificationCount, - }, - members: { - count: channel.members?.length || 0, - }, - groups: { - count: channel.groups?.length || 0, - }, - }, - sendPrivate: channel.sendPrivate, - }; - return ChannelResponse(filteredChannel);*/ + const hasMemberAccess = await channel.hasMemberAccess(transactionManager, this.authorizationBag, userGroups); + const hasAdminAccess = await channel.hasAdminAccess(transactionManager, this.authorizationBag, userGroups); + const canSendByForm = + (channel.submissionByForm.includes(SubmissionByForm.members) && hasMemberAccess) || + (channel.submissionByForm.includes(SubmissionByForm.administrators) && hasAdminAccess); + return new GetChannelResponse(channel, hasMemberAccess, hasAdminAccess, canSendByForm); }), ); - console.timeEnd('get-all-channels:build-return:' + this.authorizationBag.userName); + child.close(); - console.timeEnd('get-all-channels:total:' + this.authorizationBag.userName); + span.close(); return new ChannelsListResponse(returnChannels, count); } } diff --git a/src/services/impl/channels/find-channel-by-id.ts b/src/services/impl/channels/find-channel-by-id.ts index 1af811d095bdfc9bdecfc8c361efba352586b60f..4a21c46328e4543575eef1b7d02806c3bf3d40d5 100644 --- a/src/services/impl/channels/find-channel-by-id.ts +++ b/src/services/impl/channels/find-channel-by-id.ts @@ -3,7 +3,7 @@ import { EntityManager } from 'typeorm'; import { Channel } from '../../../models/channel'; import { ForbiddenError, NotFoundError } from 'routing-controllers'; import { AuthorizationBag } from '../../../models/authorization-bag'; -import { ChannelResponse, GetChannelResponse } from '../../../controllers/channels/dto'; +import { GetChannelResponse } from '../../../controllers/channels/dto'; import { CernAuthorizationService } from '../../../models/cern-authorization-service'; export class FindChannelById implements Command { @@ -27,9 +27,9 @@ export class FindChannelById implements Command { return new GetChannelResponse( channel, - await channel.isSubscribedWithCache(this.authorizationBag.user, userGroups), - await channel.isAdminWithCache(this.authorizationBag, userGroups), - await channel.canSendByFormWithCache(this.authorizationBag, userGroups), + await channel.hasMemberAccess(transactionManager, this.authorizationBag, userGroups), + await channel.hasAdminAccess(transactionManager, this.authorizationBag, userGroups), + await channel.canSendByForm(transactionManager, this.authorizationBag, userGroups), ); } } diff --git a/src/services/impl/channels/get-channel-groups.ts b/src/services/impl/channels/get-channel-groups.ts index 0b4a62b94094ac85a3312fc0eeeb14048e6d3160..e0a9675eb81a02af0b7d9655a5a6a5206f9c1503 100644 --- a/src/services/impl/channels/get-channel-groups.ts +++ b/src/services/impl/channels/get-channel-groups.ts @@ -22,7 +22,8 @@ export class GetChannelGroups implements Command { if (!(await channel.isAdmin(this.authorizationBag))) throw new ForbiddenError("You don't have the rights to manage this channel."); - const returnGroups = channel.groups.filter(g => g.groupIdentifier.includes(this.query.searchText || '')); + const channelGroups = await channel.groups; + const returnGroups = channelGroups.filter(g => g.groupIdentifier.includes(this.query.searchText || '')); return { totalNumberOfGroups: returnGroups.length, diff --git a/src/services/impl/channels/get-channel-members.ts b/src/services/impl/channels/get-channel-members.ts index 06bd14390853e936744a7dfde6acb090b20e8dcc..6c8d9fe95a49b65d1a08718b601a61194d7ee87e 100644 --- a/src/services/impl/channels/get-channel-members.ts +++ b/src/services/impl/channels/get-channel-members.ts @@ -22,7 +22,8 @@ export class GetChannelMembers implements Command { if (!(await channel.isAdmin(this.authorizationBag))) throw new ForbiddenError("You don't have the rights to manage this channel."); - const returnMembers = channel.members.filter( + const channelMembers = await channel.members; + const returnMembers = channelMembers.filter( m => (m.username && m.username.includes(this.query.searchText || '')) || m.email.includes(this.query.searchText || ''), diff --git a/src/services/impl/channels/remove-group-from-channel.ts b/src/services/impl/channels/remove-group-from-channel.ts index 4f884033548a113c2c2adf206b76f283afa86d0d..9a6143b7fc47e2196408a7abcddcfaca4e90d74a 100644 --- a/src/services/impl/channels/remove-group-from-channel.ts +++ b/src/services/impl/channels/remove-group-from-channel.ts @@ -35,7 +35,7 @@ export class RemoveGroupFromChannel implements Command { if (!group) throw new NotFoundError('Group not found'); - channel.removeGroup(group); + await channel.removeGroup(transactionManager, group); const updatedChannel = await transactionManager.save(channel); if (!updatedChannel) throw new Error('Was not able to update the channel.'); diff --git a/src/services/impl/channels/remove-user-from-channel.ts b/src/services/impl/channels/remove-user-from-channel.ts index c81f46887d268bf35702fc36d4234458a8903ed5..8bff66d53fd80a53f280ba8b4852e2e7ae9f3813 100644 --- a/src/services/impl/channels/remove-user-from-channel.ts +++ b/src/services/impl/channels/remove-user-from-channel.ts @@ -10,13 +10,10 @@ export class RemoveUserFromChannel implements Command { constructor(private memberId: string, private channelId: string, private authorizationBag: AuthorizationBag) {} async execute(transactionManager: EntityManager): Promise<void> { - const channel = await transactionManager.findOne( - Channel, - { id: this.channelId }, - { - relations: ['unsubscribed', 'members', 'groups', 'owner', 'adminGroup'], - }, - ); + const channel = await transactionManager.findOne(Channel, { + where: { id: this.channelId }, + relations: ['unsubscribed', 'members', 'groups', 'owner', 'adminGroup'], + }); if (!channel) throw new NotFoundError('Channel does not exist'); @@ -25,7 +22,7 @@ export class RemoveUserFromChannel implements Command { if (this.memberId !== this.authorizationBag.userId && !(await channel.isAdmin(this.authorizationBag))) throw new ForbiddenError('Access to Channel not Authorized !'); - const user = await transactionManager.findOne(User, { id: this.memberId }); + const user = await transactionManager.findOneBy(User, { id: this.memberId }); if (!user) throw new NotFoundError('User not found'); await transactionManager.createQueryBuilder().relation(Channel, 'members').of(channel).remove(user); diff --git a/src/services/impl/channels/subscribe-to-channel.ts b/src/services/impl/channels/subscribe-to-channel.ts index 84da716007bd5dea44ae06dbab09782adcf3c365..980b1d70e49b48e8dd2cd50a87d32a81aec70902 100644 --- a/src/services/impl/channels/subscribe-to-channel.ts +++ b/src/services/impl/channels/subscribe-to-channel.ts @@ -10,13 +10,10 @@ export class SubscribeToChannel implements Command { constructor(private memberId: string, private channelId: string, private authorizationBag: AuthorizationBag) {} async execute(transactionManager: EntityManager): Promise<void> { - const channel = await transactionManager.findOne( - Channel, - { id: this.channelId }, - { - relations: ['unsubscribed', 'members', 'groups', 'owner', 'adminGroup'], - }, - ); + const channel = await transactionManager.findOne(Channel, { + where: { id: this.channelId }, + relations: ['unsubscribed', 'members', 'groups', 'owner', 'adminGroup'], + }); if (!channel) throw new NotFoundError('Channel does not exist'); @@ -27,8 +24,8 @@ export class SubscribeToChannel implements Command { else if (!(await channel.hasAccess(this.authorizationBag))) throw new ForbiddenError('Access to Channel not Authorized !'); - const user = await transactionManager.findOne(User, { id: this.memberId }); - channel.addMember(user); + const user = await transactionManager.findOneBy(User, { id: this.memberId }); + await channel.addUser(transactionManager, user); const updatedChannel = await transactionManager.save(channel); await AuditChannels.setValue(updatedChannel.id, { diff --git a/src/services/impl/channels/unsubscribe-from-channel.ts b/src/services/impl/channels/unsubscribe-from-channel.ts index 8e1f23468f2fa5e27a3ea6663fde7c78a0354a30..331172b2bbbdf6d62d1839dec38415b3139002ed 100644 --- a/src/services/impl/channels/unsubscribe-from-channel.ts +++ b/src/services/impl/channels/unsubscribe-from-channel.ts @@ -11,13 +11,10 @@ export class UnsubscribeFromChannel implements Command { constructor(private memberId: string, private channelId: string, private authorizationBag: AuthorizationBag) {} async execute(transactionManager: EntityManager): Promise<void> { - const channel = await transactionManager.findOne( - Channel, - { id: this.channelId }, - { - relations: ['unsubscribed', 'members', 'groups', 'owner', 'adminGroup'], - }, - ); + const channel = await transactionManager.findOne(Channel, { + where: { id: this.channelId }, + relations: ['unsubscribed', 'members', 'groups', 'owner', 'adminGroup'], + }); if (!channel) throw new NotFoundError('Channel does not exist'); @@ -31,7 +28,7 @@ export class UnsubscribeFromChannel implements Command { 'This channel is mandatory, unsubscribe is not possible. You can however mute it if needed.', ); - const user = await transactionManager.findOne(User, { id: this.memberId }); + const user = await transactionManager.findOneBy(User, { id: this.memberId }); if (!user) throw new NotFoundError('User not found'); channel.unsubscribeMember(user); diff --git a/src/services/impl/channels/update-channel-admin-group.ts b/src/services/impl/channels/update-channel-admin-group.ts index fe2ed4f0184a05d0727a704a98ddb73f13415dab..98659ecaecdc9fad2d19d3817596ae293a034819 100644 --- a/src/services/impl/channels/update-channel-admin-group.ts +++ b/src/services/impl/channels/update-channel-admin-group.ts @@ -30,7 +30,7 @@ export class UpdateChannelAdminGroup implements Command { const newGroup = new Group({ groupIdentifier: this.newAdminGroup.newAdminGroup }); - let groupToAdd = await transactionManager.findOne(Group, { + let groupToAdd = await transactionManager.findOneBy(Group, { groupIdentifier: this.newAdminGroup.newAdminGroup, }); if (!groupToAdd && (await newGroup.exists())) { diff --git a/src/services/impl/channels/update-channel.ts b/src/services/impl/channels/update-channel.ts index a2b65c6347f5a40984f85d3c322a8715862540c2..fc4dc3d156b4d9bc432a3800d2b1bc1e38b613d6 100644 --- a/src/services/impl/channels/update-channel.ts +++ b/src/services/impl/channels/update-channel.ts @@ -35,16 +35,13 @@ export class UpdateChannel implements Command { } if ( - await transactionManager.findOne( - Channel, - { + await transactionManager.findOne(Channel, { + where: { id: Not(channel.id), name: this.channel.name, }, - { - withDeleted: true, - }, - ) + withDeleted: true, + }) ) { throw new BadRequestError('Channel name already exists'); } diff --git a/src/services/impl/devices/update-user-device.ts b/src/services/impl/devices/update-user-device.ts index 631716b23ca18d7d1de53ed7713c53a3a27403d2..9e4c494cf699123116a00eba6a01b9649bd2ce97 100644 --- a/src/services/impl/devices/update-user-device.ts +++ b/src/services/impl/devices/update-user-device.ts @@ -27,7 +27,7 @@ export class UpdateUserDevice implements Command { if (result.affected !== 1) throw new ForbiddenError('The device does not exist or you are not the owner'); - const response = await transactionManager.findOne(Device, { id: this.deviceId }); + const response = await transactionManager.findOneBy(Device, { id: this.deviceId }); return new DeviceResponse(response); } diff --git a/src/services/impl/groups/get-or-create-group.ts b/src/services/impl/groups/get-or-create-group.ts index c2edd6f1c80da53e91cd5b3e0db1b4f1dfc7efec..68848ab298580e042257bae8392f1eaf1d5f3fd8 100644 --- a/src/services/impl/groups/get-or-create-group.ts +++ b/src/services/impl/groups/get-or-create-group.ts @@ -7,7 +7,7 @@ export class GetOrCreateGroup implements Command { constructor(private groupIdentifier: string) {} async execute(transactionManager: EntityManager): Promise<Group> { - let group = await transactionManager.findOne(Group, { + let group = await transactionManager.findOneBy(Group, { groupIdentifier: this.groupIdentifier, }); if (group) return group; diff --git a/src/services/impl/mutes/create-unsubscribe-mute.ts b/src/services/impl/mutes/create-unsubscribe-mute.ts index 1fc217c2858e9a37ae61fe3f93a4df15e7d6720a..58abb133ee9d3ee6687c79c01016a818ca54264a 100644 --- a/src/services/impl/mutes/create-unsubscribe-mute.ts +++ b/src/services/impl/mutes/create-unsubscribe-mute.ts @@ -1,5 +1,5 @@ import { Command } from '../command'; -import { EntityManager } from 'typeorm'; +import { EntityManager, IsNull } from 'typeorm'; import { decodeEmail, Mute } from '../../../models/mute'; import { User } from '../../../models/user'; import { BadRequestError } from 'routing-controllers'; @@ -7,20 +7,14 @@ import { AuthService } from '../../../services/auth-service'; import { ServiceFactory } from '../../../services/services-factory'; export class CreateUnSubscribeMute implements Command { - constructor( - private mute: Mute, - private blob: string, - private email: string, - ) {} + constructor(private mute: Mute, private blob: string, private email: string) {} authService: AuthService = ServiceFactory.getAuthenticationService(); async execute(transactionManager: EntityManager) { try { - if (!this.email || !this.blob) - throw new Error('Error: missing email or blob in request'); - if (decodeEmail(this.blob) !== this.email) - throw new Error('Error: tempered unsubscribe request'); + if (!this.email || !this.blob) throw new Error('Error: missing email or blob in request'); + if (decodeEmail(this.blob) !== this.email) throw new Error('Error: tempered unsubscribe request'); } catch (e) { console.error(e); // Return a generic error, no need to explain why it fails @@ -28,7 +22,7 @@ export class CreateUnSubscribeMute implements Command { } // Retrieve existing user if any for this email - let user = await transactionManager.findOne(User, { email: this.email }); + let user = await transactionManager.findOneBy(User, { email: this.email }); // Check if a global permanent unsubscribe record already exists for this user if ( @@ -37,7 +31,7 @@ export class CreateUnSubscribeMute implements Command { relations: ['user', 'target'], where: { user: user, - target: null, + target: IsNull(), type: 'PERMANENT', }, })) @@ -45,7 +39,7 @@ export class CreateUnSubscribeMute implements Command { throw new BadRequestError('This email is already unsubscribed'); } - let mute = new Mute({ + const mute = new Mute({ type: this.mute.type, target: null, user: user, diff --git a/src/services/impl/mutes/create-user-mute.ts b/src/services/impl/mutes/create-user-mute.ts index 2f91a90edd0e49c0afad8602c07265c662226595..318e062a6aaf95d32715d99f97fa301b61fb12c3 100644 --- a/src/services/impl/mutes/create-user-mute.ts +++ b/src/services/impl/mutes/create-user-mute.ts @@ -32,7 +32,7 @@ export class CreateUserMute implements Command { ); if (savedMute) { return await transactionManager.findOne(Mute, { - relations: ["target"], + relations: ['target'], where: { id: savedMute.id, }, diff --git a/src/services/impl/mutes/get-user-mutes.ts b/src/services/impl/mutes/get-user-mutes.ts index 5147a21b0464cce07bd7b11ed470e4d9d4103b66..3b3b064499d598f8c29dccb11de4a56bd6dbb4c5 100644 --- a/src/services/impl/mutes/get-user-mutes.ts +++ b/src/services/impl/mutes/get-user-mutes.ts @@ -1,20 +1,19 @@ -import { Command } from "../command"; -import { EntityManager } from "typeorm"; -import { Mute } from "../../../models/mute"; -import { AuthorizationBag } from "../../../models/authorization-bag"; +import { Command } from '../command'; +import { EntityManager, IsNull } from 'typeorm'; +import { Mute } from '../../../models/mute'; +import { AuthorizationBag } from '../../../models/authorization-bag'; export class GetUserMutes implements Command { - constructor( - private channelId: string, - private authorizationBag: AuthorizationBag - ) {} + constructor(private channelId: string, private authorizationBag: AuthorizationBag) {} private async fetchGlobalMutes(transactionManager: EntityManager) { return await transactionManager.find(Mute, { - relations: ["target"], + relations: ['target'], where: { - user: this.authorizationBag.userId, - target: null + user: { + id: this.authorizationBag.userId, + }, + target: IsNull(), }, }); } @@ -22,10 +21,10 @@ export class GetUserMutes implements Command { private async fetchChannelMutes(transactionManager: EntityManager) { return await transactionManager .getRepository(Mute) - .createQueryBuilder("mute") - .leftJoin("mute.user", "user") - .leftJoinAndSelect("mute.target", "target") - .where("user.id = :userId AND target.id = :channelId", { + .createQueryBuilder('mute') + .leftJoin('mute.user', 'user') + .leftJoinAndSelect('mute.target', 'target') + .where('user.id = :userId AND target.id = :channelId', { userId: this.authorizationBag.userId, channelId: this.channelId, }) @@ -35,10 +34,10 @@ export class GetUserMutes implements Command { private async fetchAllChannelsMutes(transactionManager: EntityManager) { return await transactionManager .getRepository(Mute) - .createQueryBuilder("mute") - .leftJoin("mute.user", "user") - .leftJoinAndSelect("mute.target", "target") - .where("user.id = :userId AND target.id IS NOT NULL", { + .createQueryBuilder('mute') + .leftJoin('mute.user', 'user') + .leftJoinAndSelect('mute.target', 'target') + .where('user.id = :userId AND target.id IS NOT NULL', { userId: this.authorizationBag.userId, }) .getMany(); @@ -46,7 +45,7 @@ export class GetUserMutes implements Command { async execute(transactionManager: EntityManager) { const result = { - globalMutes: await this.fetchGlobalMutes(transactionManager) + globalMutes: await this.fetchGlobalMutes(transactionManager), }; if (this.channelId) { diff --git a/src/services/impl/notifications/find-all-notifications.ts b/src/services/impl/notifications/find-all-notifications.ts index 336a3d51004ea0fcf5e125c084bd7f6b12c257dc..1054fc8832b42fb1f1e3d28449d3c275aa0201f0 100644 --- a/src/services/impl/notifications/find-all-notifications.ts +++ b/src/services/impl/notifications/find-all-notifications.ts @@ -33,7 +33,7 @@ export class FindAllNotifications implements Command { throw new ForbiddenError('Access to Channel not Authorized!'); const isAdmin = await channel.isAdmin(this.authorizationBag); - const userCanSend = await channel.canSendByForm(this.authorizationBag); + const userCanSend = await channel.canSendByForm(transactionManager, this.authorizationBag, userGroups); if (this.authorizationBag) console.timeEnd('get-all-notifications:channel:' + this.authorizationBag.userName); diff --git a/src/services/impl/notifications/send-notification.ts b/src/services/impl/notifications/send-notification.ts index b6a74b5ca12849d18b86f6d47972e9a3bc2e660b..a0d6d2548d8b36fe99a4e90dee8921a82220c0b8 100644 --- a/src/services/impl/notifications/send-notification.ts +++ b/src/services/impl/notifications/send-notification.ts @@ -17,8 +17,11 @@ import { GroupsServiceInterface } from '../../groups-service'; import * as memoize from 'memoizee'; import { AuditNotifications } from '../../../log/auditing'; import { GetNotificationResponse, SendNotificationRequest } from '../../../controllers/notifications/dto'; -import { auth } from 'firebase-admin'; -import { SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG } from 'constants'; + +import { CernAuthorizationService } from '../../../models/cern-authorization-service'; +import { SubmissionByForm } from '../../../models/channel-enums'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { appsignal } = require('../../../../appsignal'); // Update to the location used in the previous step export class SendNotification implements Command { private usersService: UsersServiceInterface = ServiceFactory.getUserService(); @@ -26,32 +29,82 @@ export class SendNotification implements Command { constructor(private notification: SendNotificationRequest, private authorizationBag: AuthorizationBag) {} + async hasAccess(transactionManager: EntityManager, channel: Channel, userGroups: any[]): Promise<boolean> { + if (!this.authorizationBag) { + // Call from the /unauthenticated + // restricted by direct access in openshift + return true; + } + + const hasApiKeyAccess = channel.hasApiKeyAccess(this.authorizationBag); + console.debug('hasApiKeyAccess', hasApiKeyAccess); + if (hasApiKeyAccess) return true; + + if (channel.submissionByForm.includes(SubmissionByForm.administrators)) { + const hasAdminAccess = await channel.hasAdminAccess(transactionManager, this.authorizationBag, userGroups); + console.debug('hasAdminAccess', hasAdminAccess); + if (hasAdminAccess) return true; + } + + const isTargetedNotification = + this.notification.targetUsers?.length > 0 || + this.notification.targetGroups?.length > 0 || + this.notification.targetData?.length > 0; + if (isTargetedNotification) { + // Targeted notifications are not allowed for members + return; + } + + if (channel.submissionByForm.includes(SubmissionByForm.members)) { + const hasMemberAccess = await channel.hasMemberAccess(transactionManager, this.authorizationBag, userGroups); + console.debug('hasMemberAccess', hasMemberAccess); + if (hasMemberAccess) return true; + } + + return false; + } + async execute(transactionManager: EntityManager): Promise<GetNotificationResponse> { + const tracer = appsignal.tracer(); + const span = tracer.createSpan(undefined, tracer.currentSpan()); + span.setCategory('services/impl'); + span.setName('SendNotification'); + + let child = span.child(); + child.setCategory('get_channel_via_findOne'); const targetChannel = await transactionManager.findOne(Channel, { - relations: ['members', 'groups', 'owner', 'adminGroup', 'category'], + relations: { + owner: true, + adminGroup: true, + category: true, + }, where: { id: this.notification.target }, }); + child.close(); - if (!targetChannel) throw new NotFoundError('Channel does not exist'); - - if (this.authorizationBag) { - if ( - !(await targetChannel.canSendByForm( - this.authorizationBag, - this.notification.targetUsers?.length > 0 || - this.notification.targetGroups?.length > 0 || - this.notification.targetData?.length > 0, - )) - ) - throw new ForbiddenError('Sending to Channel not Authorized !'); - - if (this.notification.priority === PriorityLevel.CRITICAL) { - if (!(await targetChannel.canSendCritical(this.authorizationBag))) - throw new ForbiddenError('Sending Critical Notifications to Channel not Authorized !'); - } - } else { - // Call from the /unauthenticated - // restricted by direct access in openshift + if (!targetChannel) { + span.close(); + throw new NotFoundError('Channel does not exist'); + } + + child = span.child(); + child.setCategory('getCurrentUserGroups'); + // Get current user groups, to be used next in SQL to compare with channel groups + const userGroups = await CernAuthorizationService.getCurrentUserGroups(this.authorizationBag.userName); + child.close(); + + child = span.child(); + child.setCategory('has-access'); + if (!(await this.hasAccess(transactionManager, targetChannel, userGroups))) { + child.close(); + span.close(); + throw new ForbiddenError('Sending to Channel not Authorized !'); + } + child.close(); + + if (this.notification.priority === PriorityLevel.CRITICAL) { + if (!(await targetChannel.canSendCritical(this.authorizationBag))) + throw new ForbiddenError('Sending Critical Notifications to Channel not Authorized !'); } this.validateFields(); @@ -129,10 +182,10 @@ export class SendNotification implements Command { from: newNotification.source, }); + span.close(); // Get back object from DB, without full info about target Channel return new GetNotificationResponse( await transactionManager.findOne(Notification, { - relations: ['targetUsers', 'targetGroups'], where: { id: newNotification.id }, }), ); @@ -245,7 +298,7 @@ export class SendNotification implements Command { targetUsers.push(user); if (intersection) return; - if (await targetChannel.isMember(user)) return; + if (await targetChannel.isUserSubscribed(transactionManager, user)) return; usersToSubscribe.push(user); }), @@ -255,10 +308,10 @@ export class SendNotification implements Command { return targetUsers; } - for (const user of usersToSubscribe) { - targetChannel.addMember(user); - await transactionManager.createQueryBuilder().relation(Channel, 'members').of(targetChannel).add(user); - } + console.debug('subscribing target users', targetUsers); + // optimized save performance + await transactionManager.createQueryBuilder().relation(Channel, 'members').of(targetChannel).add(usersToSubscribe); + return targetUsers; } @@ -287,7 +340,7 @@ export class SendNotification implements Command { targetGroups.push(group); if (intersection) return; - if (group && channel.groups.some(g => g.groupIdentifier === group.groupIdentifier)) return; + if (await channel.isGroupSubscribed(transactionManager, group)) return; groupsToSubscribe.push(group); }), @@ -297,9 +350,10 @@ export class SendNotification implements Command { return targetGroups; } - // subscribe groups after creation to avoid inconsistent state - groupsToSubscribe.forEach(group => channel.addGroup(group)); - await transactionManager.save(channel); + console.debug('subscribing target groups', targetGroups); + + // optimized save performance + await transactionManager.createQueryBuilder().relation(Channel, 'groups').of(channel).add(groupsToSubscribe); return targetGroups; } diff --git a/src/services/impl/notifications/update-user-notification.ts b/src/services/impl/notifications/update-user-notification.ts index 6e037de711e6ad8d9484c3106ce8fb9d31bade78..fd2352d6f20269e21e4427bdff3944824c79896d 100644 --- a/src/services/impl/notifications/update-user-notification.ts +++ b/src/services/impl/notifications/update-user-notification.ts @@ -12,7 +12,7 @@ export class UpdateUserNotification implements Command { async execute(transactionManager: EntityManager): Promise<any> { if (await this.isNotificationOfUser(transactionManager)) { - let userNotification = await transactionManager.findOne(UserNotification, { + let userNotification = await transactionManager.findOneBy(UserNotification, { user: { id: this.authorizationBag.userId }, notification: this.notification.notification, }); @@ -23,18 +23,17 @@ export class UpdateUserNotification implements Command { delete userNotification.user; return { - ...(await transactionManager.findOne(Notification, { id: this.notification.notification })), + ...(await transactionManager.findOneBy(Notification, { id: this.notification.notification })), ...userNotification, }; } } async isNotificationOfUser(transactionManager: EntityManager): Promise<boolean> { - const notification = await transactionManager.findOne( - Notification, - { id: this.notification.notification }, - { relations: ['users', 'users.user'] }, - ); + const notification = await transactionManager.findOne(Notification, { + where: { id: this.notification.notification }, + relations: ['users', 'users.user'], + }); return notification.users.filter(u => u.user.id === this.authorizationBag.userId).length === 1; } } diff --git a/src/services/impl/preferences/get-user-preferences.ts b/src/services/impl/preferences/get-user-preferences.ts index 780be22142af6e09eafc7fbf1a62b0b5c86bf928..e8987e2abde6f5b57e706831663c1b5d0f90002a 100644 --- a/src/services/impl/preferences/get-user-preferences.ts +++ b/src/services/impl/preferences/get-user-preferences.ts @@ -1,20 +1,21 @@ -import { Command } from "../command"; -import { EntityManager } from "typeorm"; -import { Preference } from "../../../models/preference"; -import { AuthorizationBag } from "../../../models/authorization-bag"; +import { Command } from '../command'; +import { EntityManager, IsNull } from 'typeorm'; +import { Preference } from '../../../models/preference'; +import { AuthorizationBag } from '../../../models/authorization-bag'; export class GetUserPreferences implements Command { - constructor( - private channelId: string, - private authorizationBag: AuthorizationBag - ) {} + constructor(private channelId: string, private authorizationBag: AuthorizationBag) {} private async fetchGlobalPreferences(transactionManager: EntityManager) { return await transactionManager.find(Preference, { - relations: ["target", "disabledChannels", "devices"], + relations: { + target: true, + disabledChannels: true, + devices: true, + }, where: { - user: this.authorizationBag.userId, - target: null + user: { id: this.authorizationBag.userId }, + target: IsNull(), }, }); } @@ -22,11 +23,11 @@ export class GetUserPreferences implements Command { private async fetchChannelPreferences(transactionManager: EntityManager) { return await transactionManager .getRepository(Preference) - .createQueryBuilder("preference") - .leftJoin("preference.user", "user") - .leftJoinAndSelect("preference.target", "target") - .leftJoinAndSelect("preference.devices", "device") - .where("user.id = :userId AND target.id = :channelId", { + .createQueryBuilder('preference') + .leftJoin('preference.user', 'user') + .leftJoinAndSelect('preference.target', 'target') + .leftJoinAndSelect('preference.devices', 'device') + .where('user.id = :userId AND target.id = :channelId', { userId: this.authorizationBag.userId, channelId: this.channelId, }) @@ -35,7 +36,7 @@ export class GetUserPreferences implements Command { async execute(transactionManager: EntityManager) { const result = { - globalPreferences: await this.fetchGlobalPreferences(transactionManager) + globalPreferences: await this.fetchGlobalPreferences(transactionManager), }; if (this.channelId) { diff --git a/src/services/impl/preferences/set-enabled-global-preferences.ts b/src/services/impl/preferences/set-enabled-global-preferences.ts index 26d88fdd481ea4d4c2ec87915fc864c0fd7c032e..21e3c1dd5a71c1e7ef37da3f4ce5ec8d6606b5c1 100644 --- a/src/services/impl/preferences/set-enabled-global-preferences.ts +++ b/src/services/impl/preferences/set-enabled-global-preferences.ts @@ -15,29 +15,25 @@ export class SetEnabledGlobalPreference implements Command { ) {} async execute(transactionManager: EntityManager) { - let preference = await transactionManager.findOne(Preference, { - where: { id: this.preferenceId, user: this.authorizationBag.userId }, + const preference = await transactionManager.findOne(Preference, { + where: { + id: this.preferenceId, + user: { id: this.authorizationBag.userId }, + }, relations: ['target', 'disabledChannels', 'devices'], }); - let channel = await transactionManager.findOne(Channel, { + const channel = await transactionManager.findOne(Channel, { where: { id: this.channelId }, }); if (preference.target) - throw new ForbiddenError( - 'Enable/disable preference is only allowed for global preferences.', - ); + throw new ForbiddenError('Enable/disable preference is only allowed for global preferences.'); - if (!preference) - throw new ForbiddenError( - 'The preference does not exist or you are not the owner.', - ); + if (!preference) throw new ForbiddenError('The preference does not exist or you are not the owner.'); if (this.isEnabled) { - preference.disabledChannels = preference.disabledChannels.filter( - channel => channel.id !== this.channelId, - ); + preference.disabledChannels = preference.disabledChannels.filter(channel => channel.id !== this.channelId); } else if (!preference.disabledChannels.some(c => c.id === channel.id)) { preference.disabledChannels.push(channel); } diff --git a/src/services/impl/tags/create-tag.ts b/src/services/impl/tags/create-tag.ts index e52cb74e065b433cd49babdd8c22651e6aeb6cfe..9cb21b9dca898cc99db24a1a7962fda042b91627 100644 --- a/src/services/impl/tags/create-tag.ts +++ b/src/services/impl/tags/create-tag.ts @@ -1,30 +1,32 @@ -import { Command } from "../command"; -import { EntityManager } from "typeorm"; -import { Tag } from "../../../models/tag"; -import { AuthorizationBag } from "../../../models/authorization-bag"; -import { BadRequestError } from "routing-controllers"; +import { Command } from '../command'; +import { EntityManager } from 'typeorm'; +import { Tag } from '../../../models/tag'; +import { AuthorizationBag } from '../../../models/authorization-bag'; +import { BadRequestError } from 'routing-controllers'; export class CreateTag implements Command { - constructor(private tag: Tag, private authorizationBag: AuthorizationBag) { } + constructor(private tag: Tag, private authorizationBag: AuthorizationBag) {} async execute(transactionManager: EntityManager) { // Check tag length if (this.tag.name.length < 2 || this.tag.name.length > 32) { - throw new BadRequestError("Tag should be between 2 and 32 characters.") + throw new BadRequestError('Tag should be between 2 and 32 characters.'); } // Check if tag already exists - if (await transactionManager.findOne(Tag, { - name: this.tag.name, - }) ) { - throw new BadRequestError("Tag already exists"); + if ( + await transactionManager.findOneBy(Tag, { + name: this.tag.name, + }) + ) { + throw new BadRequestError('Tag already exists'); } - const tag = new Tag({ ...this.tag }) + const tag = new Tag({ ...this.tag }); const savedTag = await transactionManager.save(tag); if (savedTag) { - return await transactionManager.findOne(Tag, { - id: savedTag.id, + return await transactionManager.findOneBy(Tag, { + id: savedTag.id, }); } } diff --git a/src/services/impl/usersettings/delete-draft.ts b/src/services/impl/usersettings/delete-draft.ts index 2bbb0de5ec2bd3f0fa58c73d4c77a80fad9f085f..37f0c858d4ad650cf81224a26be7b67e3b10600e 100644 --- a/src/services/impl/usersettings/delete-draft.ts +++ b/src/services/impl/usersettings/delete-draft.ts @@ -1,25 +1,24 @@ -import {Command} from "../command"; -import {EntityManager} from "typeorm"; -import {UserSettings} from "../../../models/user-settings"; -import {AuthorizationBag} from "../../../models/authorization-bag"; -import {NotFoundError} from "routing-controllers"; +import { Command } from '../command'; +import { EntityManager } from 'typeorm'; +import { UserSettings } from '../../../models/user-settings'; +import { AuthorizationBag } from '../../../models/authorization-bag'; +import { NotFoundError } from 'routing-controllers'; export class DeleteDraft implements Command { - constructor(private authorizationBag: AuthorizationBag) { - } - - async execute(transactionManager: EntityManager) { - let userSetting = await transactionManager.findOne(UserSettings, { - where: { - user: this.authorizationBag.userId, - }, - }); + constructor(private authorizationBag: AuthorizationBag) {} - if (!userSetting) { - throw new NotFoundError("Draft not found") - } + async execute(transactionManager: EntityManager) { + const userSetting = await transactionManager.findOne(UserSettings, { + where: { + user: { id: this.authorizationBag.userId }, + }, + }); - userSetting.draft = null; - await transactionManager.save(userSetting); + if (!userSetting) { + throw new NotFoundError('Draft not found'); } + + userSetting.draft = null; + await transactionManager.save(userSetting); + } } diff --git a/src/services/impl/usersettings/get-draft.ts b/src/services/impl/usersettings/get-draft.ts index 6e2d191ed67e1a9fd2990865cd5f985288118fe8..98dd1299d3172b02266a41c18b0cf5f72e82909c 100644 --- a/src/services/impl/usersettings/get-draft.ts +++ b/src/services/impl/usersettings/get-draft.ts @@ -1,21 +1,20 @@ -import {Command} from "../command"; -import {EntityManager} from "typeorm"; -import {UserSettings} from "../../../models/user-settings"; -import {AuthorizationBag} from "../../../models/authorization-bag"; +import { Command } from '../command'; +import { EntityManager } from 'typeorm'; +import { UserSettings } from '../../../models/user-settings'; +import { AuthorizationBag } from '../../../models/authorization-bag'; export class GetDraft implements Command { - constructor(private authorizationBag: AuthorizationBag) { - } + constructor(private authorizationBag: AuthorizationBag) {} - async execute(transactionManager: EntityManager) { - const userSetting = await transactionManager.findOne(UserSettings, { - where: { - user: this.authorizationBag.userId, - }, - }); + async execute(transactionManager: EntityManager) { + const userSetting = await transactionManager.findOne(UserSettings, { + where: { + user: { id: this.authorizationBag.userId }, + }, + }); - if (userSetting) { - return userSetting.draft; - } + if (userSetting) { + return userSetting.draft; } + } } diff --git a/src/services/impl/usersettings/get-user-settings.ts b/src/services/impl/usersettings/get-user-settings.ts index b8427d1fcb5da99430f544688d12548cf43f4b00..c09bc38c4510c5742322b5025e5180321d7f2435 100644 --- a/src/services/impl/usersettings/get-user-settings.ts +++ b/src/services/impl/usersettings/get-user-settings.ts @@ -1,35 +1,32 @@ -import { Command } from "../command"; -import { EntityManager } from "typeorm"; -import { UserSettings } from "../../../models/user-settings"; -import { User } from "../../../models/user"; -import { AuthorizationBag } from "../../../models/authorization-bag"; -import { NotFoundError } from "routing-controllers"; +import { Command } from '../command'; +import { EntityManager } from 'typeorm'; +import { UserSettings } from '../../../models/user-settings'; +import { User } from '../../../models/user'; +import { AuthorizationBag } from '../../../models/authorization-bag'; +import { NotFoundError } from 'routing-controllers'; export class GetUserSettings implements Command { - constructor(private authorizationBag: AuthorizationBag) { - } - - async execute(transactionManager: EntityManager) { + constructor(private authorizationBag: AuthorizationBag) {} - let userSettings = await transactionManager.findOne(UserSettings, { - where: { - user: this.authorizationBag.userId, - }, - }); + async execute(transactionManager: EntityManager) { + let userSettings = await transactionManager.findOne(UserSettings, { + where: { + user: { + id: this.authorizationBag.userId + }, + }, + }); - if (!userSettings) { - const user = await transactionManager.findOne( - User, - { id: this.authorizationBag.userId} - ); + if (!userSettings) { + const user = await transactionManager.findOneBy(User, { id: this.authorizationBag.userId }); - userSettings = new UserSettings({ - user: user, - draft: "", - favoriteList: [], - }); - return await transactionManager.save(userSettings); - } - return userSettings; + userSettings = new UserSettings({ + user: user, + draft: '', + favoriteList: [], + }); + return await transactionManager.save(userSettings); } + return userSettings; + } } diff --git a/src/services/impl/usersettings/save-draft.ts b/src/services/impl/usersettings/save-draft.ts index 0446f94497562a092a562d7ddf21e2299a65d022..b34d5f6b42c7f0deabb2284d9dae7ddeb854e119 100644 --- a/src/services/impl/usersettings/save-draft.ts +++ b/src/services/impl/usersettings/save-draft.ts @@ -1,26 +1,25 @@ -import {Command} from "../command"; -import {EntityManager} from "typeorm"; -import {UserSettings} from "../../../models/user-settings"; -import {AuthorizationBag} from "../../../models/authorization-bag"; +import { Command } from '../command'; +import { EntityManager } from 'typeorm'; +import { UserSettings } from '../../../models/user-settings'; +import { AuthorizationBag } from '../../../models/authorization-bag'; export class SaveDraft implements Command { - constructor(private draft: string, private authorizationBag: AuthorizationBag) { - } - - async execute(transactionManager: EntityManager) { - let userSetting = await transactionManager.findOne(UserSettings, { - where: { - user: this.authorizationBag.userId, - }, - }); + constructor(private draft: string, private authorizationBag: AuthorizationBag) {} - if (!userSetting) { - userSetting = new UserSettings({ - user: this.authorizationBag.userId, - }) - } + async execute(transactionManager: EntityManager) { + let userSetting = await transactionManager.findOne(UserSettings, { + where: { + user: { id: this.authorizationBag.userId }, + }, + }); - userSetting.draft = this.draft; - await transactionManager.save(userSetting); + if (!userSetting) { + userSetting = new UserSettings({ + user: this.authorizationBag.userId, + }); } + + userSetting.draft = this.draft; + await transactionManager.save(userSetting); + } }