From f1c6beacf29092f66f470d7c7b765cd92291ae02 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 12:38:06 +0800 Subject: [PATCH 001/106] use super-simple-queue --- server/index.js | 26 +- server/package-lock.json | 1614 ++++++++++------- server/package.json | 1 + .../SuperSimpleQueue/SuperSimpleQueue.js | 146 ++ .../SuperSimpleQueueHelper.js | 99 + server/service/networkService.js | 2 +- 6 files changed, 1185 insertions(+), 703 deletions(-) mode change 100755 => 100644 server/package-lock.json create mode 100644 server/service/SuperSimpleQueue/SuperSimpleQueue.js create mode 100644 server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js diff --git a/server/index.js b/server/index.js index 4b74ef46c..a4fe8cd7f 100755 --- a/server/index.js +++ b/server/index.js @@ -53,6 +53,9 @@ import { Queue, Worker } from "bullmq"; import PulseQueue from "./service/PulseQueue/PulseQueue.js"; import PulseQueueHelper from "./service/PulseQueue/PulseQueueHelper.js"; +import SuperSimpleQueue from "./service/SuperSimpleQueue/SuperSimpleQueue.js"; +import SuperSimpleQueueHelper from "./service/SuperSimpleQueue/SuperSimpleQueueHelper.js"; + //Network service and dependencies import NetworkService from "./service/networkService.js"; import axios from "axios"; @@ -210,23 +213,38 @@ const startApp = async () => { // stringService, // }); - const pulseQueueHelper = new PulseQueueHelper({ + // const pulseQueueHelper = new PulseQueueHelper({ + // db, + // logger, + // networkService, + // statusService, + // notificationService, + // }); + // const pulseQueue = await PulseQueue.create({ + // appSettings, + // db, + // pulseQueueHelper, + // logger, + // }); + const superSimpleQueueHelper = new SuperSimpleQueueHelper({ db, logger, networkService, statusService, notificationService, }); - const pulseQueue = await PulseQueue.create({ + + const superSimpleQueue = await SuperSimpleQueue.create({ appSettings, db, - pulseQueueHelper, logger, + helper: superSimpleQueueHelper, }); // Register services // ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue); - ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); + // ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); + ServiceRegistry.register(JobQueue.SERVICE_NAME, superSimpleQueue); ServiceRegistry.register(MongoDB.SERVICE_NAME, db); ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService); ServiceRegistry.register(EmailService.SERVICE_NAME, emailService); diff --git a/server/package-lock.json b/server/package-lock.json old mode 100755 new mode 100644 index 3fd675af3..427356e90 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,6 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", + "super-simple-scheduler": "1.0.7", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -51,36 +52,33 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", - "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } @@ -92,9 +90,9 @@ "license": "Apache-2.0" }, "node_modules/@bcoe/v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.1.tgz", - "integrity": "sha512-W+a0/JpU28AqH4IKtwUPcEUnUyXMDLALcn5/JLczGGT9fHE2sIby/xP/oQnx3nxkForzgzPy201RAKcB4xPAFQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -122,9 +120,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "license": "MIT", "optional": true, "dependencies": { @@ -132,10 +130,11 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -154,6 +153,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -166,17 +166,19 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -184,34 +186,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@eslint/config-helpers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "Apache-2.0", "engines": { - "node": "*" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -222,10 +210,11 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -244,22 +233,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -267,56 +246,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.5.tgz", - "integrity": "sha512-d3iiHxdpg5+ZcJ6jnDSOT8Z0O0VMVGy34jAnYLUX8yd36b1qn8f1TwOA/Lc7TsOh03IkPJ38eGI5qD2EjNkoEA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.13", @@ -327,9 +310,9 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", @@ -364,6 +347,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } @@ -373,6 +357,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" @@ -386,6 +371,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -399,6 +385,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -408,10 +395,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -853,9 +841,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -1027,23 +1015,6 @@ "mongodb": "^6.5.0" } }, - "node_modules/@pulsecron/pulse/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -1131,10 +1102,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -1147,15 +1119,16 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.4.tgz", - "integrity": "sha512-99l6wv4HEzBQhvaU/UGoeBoCK61SCROQaCCGyQSgX2tEQ3rKkNZ2S7CEWnS/4s1LV+8ODdK21UeyR1fHP2mXug==", + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", + "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/triple-beam": { @@ -1192,11 +1165,21 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1209,20 +1192,18 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "license": "MIT", - "dependencies": { - "debug": "4" - }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -1230,6 +1211,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1340,9 +1322,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -1463,12 +1445,14 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -1491,9 +1475,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.24.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", - "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "funding": [ { "type": "opencollective", @@ -1510,10 +1494,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -1523,9 +1507,9 @@ } }, "node_modules/bson": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", - "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "license": "Apache-2.0", "engines": { "node": ">=16.20.1" @@ -1646,9 +1630,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1659,13 +1643,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -1709,9 +1693,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001690", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", - "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -1834,6 +1818,24 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2063,15 +2065,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2318,13 +2311,13 @@ } }, "node_modules/cssnano": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.6.tgz", - "integrity": "sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.7.tgz", + "integrity": "sha512-evKu7yiDIF7oS+EIpwFlMF730ijRyLFaM2o5cTxRGJR9OKHKkc+qP443ZEVR9kZG0syaAJJCPJyfv5pbrxlSng==", "license": "MIT", "dependencies": { - "cssnano-preset-default": "^7.0.6", - "lilconfig": "^3.1.2" + "cssnano-preset-default": "^7.0.7", + "lilconfig": "^3.1.3" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -2334,63 +2327,63 @@ "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-preset-default": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.6.tgz", - "integrity": "sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.7.tgz", + "integrity": "sha512-jW6CG/7PNB6MufOrlovs1TvBTEVmhY45yz+bd0h6nw3h6d+1e+/TX+0fflZ+LzvZombbT5f+KC063w9VoHeHow==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^5.0.0", - "postcss-calc": "^10.0.2", - "postcss-colormin": "^7.0.2", - "postcss-convert-values": "^7.0.4", - "postcss-discard-comments": "^7.0.3", - "postcss-discard-duplicates": "^7.0.1", - "postcss-discard-empty": "^7.0.0", - "postcss-discard-overridden": "^7.0.0", - "postcss-merge-longhand": "^7.0.4", - "postcss-merge-rules": "^7.0.4", - "postcss-minify-font-values": "^7.0.0", - "postcss-minify-gradients": "^7.0.0", - "postcss-minify-params": "^7.0.2", - "postcss-minify-selectors": "^7.0.4", - "postcss-normalize-charset": "^7.0.0", - "postcss-normalize-display-values": "^7.0.0", - "postcss-normalize-positions": "^7.0.0", - "postcss-normalize-repeat-style": "^7.0.0", - "postcss-normalize-string": "^7.0.0", - "postcss-normalize-timing-functions": "^7.0.0", - "postcss-normalize-unicode": "^7.0.2", - "postcss-normalize-url": "^7.0.0", - "postcss-normalize-whitespace": "^7.0.0", - "postcss-ordered-values": "^7.0.1", - "postcss-reduce-initial": "^7.0.2", - "postcss-reduce-transforms": "^7.0.0", - "postcss-svgo": "^7.0.1", - "postcss-unique-selectors": "^7.0.3" + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.3", + "postcss-convert-values": "^7.0.5", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.5", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.3", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.3", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.3", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.0.2", + "postcss-unique-selectors": "^7.0.4" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-utils": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", - "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/csso": { @@ -2426,6 +2419,15 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/date.js": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz", @@ -2451,9 +2453,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2494,7 +2496,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -2534,9 +2537,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2646,9 +2649,9 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -2660,9 +2663,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2707,9 +2710,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.74", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", - "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", + "version": "1.5.175", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.175.tgz", + "integrity": "sha512-Nqpef9mOVo7pZfl9NIUhj7tgtRTsMzCzRTJDP1ccim4Wb4YHOz3Le87uxeZq68OCNwau2iQ/X7UwdAZ3ReOkmg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2734,9 +2737,9 @@ } }, "node_modules/encoding-sniffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", - "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", @@ -2759,9 +2762,9 @@ } }, "node_modules/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==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -2816,9 +2819,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2827,6 +2830,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2868,22 +2886,23 @@ } }, "node_modules/eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.11.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.20.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.20.1", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.29.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -2891,9 +2910,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2932,6 +2951,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", "dev": true, + "license": "MIT", "dependencies": { "eslint-utils": "^3.0.0", "globals": "^13.24.0", @@ -2949,6 +2969,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -2960,10 +2981,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -2980,6 +3002,7 @@ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^2.0.0" }, @@ -2998,15 +3021,17 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3014,56 +3039,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", - "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -3075,14 +3050,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3096,6 +3072,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -3108,6 +3085,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -3120,6 +3098,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -3129,6 +3108,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -3213,19 +3193,22 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fecha": { "version": "4.2.3", @@ -3233,11 +3216,35 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -3322,6 +3329,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -3331,10 +3339,11 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", @@ -3363,12 +3372,12 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -3379,19 +3388,33 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3440,18 +3463,19 @@ } }, "node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/get-caller-file": { @@ -3464,21 +3488,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3487,6 +3511,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -3508,15 +3545,40 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { @@ -3587,6 +3649,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3610,9 +3687,9 @@ } }, "node_modules/helmet": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.0.0.tgz", - "integrity": "sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3626,19 +3703,18 @@ "license": "MIT" }, "node_modules/htmlnano": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.1.tgz", - "integrity": "sha512-kAERyg/LuNZYmdqgCdYvugyLWNFAm8MWXpQMz1pLpetmCbFwoMxvkSoaAMlFrOC4OKTWI4KlZGT/RsNxg4ghOw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz", + "integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==", "license": "MIT", "dependencies": { "cosmiconfig": "^9.0.0", - "posthtml": "^0.16.5", - "timsort": "^0.3.0" + "posthtml": "^0.16.5" }, "peerDependencies": { "cssnano": "^7.0.0", "postcss": "^8.3.11", - "purgecss": "^6.0.0", + "purgecss": "^7.0.2", "relateurl": "^0.2.7", "srcset": "5.0.1", "svgo": "^3.0.2", @@ -3708,16 +3784,16 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-interval": { @@ -3766,6 +3842,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -3778,9 +3855,9 @@ "license": "ISC" }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3798,6 +3875,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -3809,9 +3887,9 @@ "license": "ISC" }, "node_modules/ioredis": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.5.0.tgz", - "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", + "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", @@ -3952,13 +4030,31 @@ "license": "ISC" }, "node_modules/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==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-4.0.2.tgz", + "integrity": "sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==", "license": "MIT", "dependencies": { - "node-fetch": "^2.6.1", - "unfetch": "^4.2.0" + "node-fetch": "^3.2.0", + "unfetch": "^5.0.0" + } + }, + "node_modules/isomorphic-unfetch/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/istanbul-lib-coverage": { @@ -3986,22 +4082,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/istanbul-reports": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", @@ -4075,7 +4155,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -4087,13 +4168,15 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsonwebtoken": { "version": "9.0.2", @@ -4118,13 +4201,14 @@ } }, "node_modules/juice": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.0.tgz", - "integrity": "sha512-sGF8hPz9/Wg+YXbaNDqc1Iuoaw+J/P9lBHNQKXAGc9pPNjCd4fyPai0Zxj7MRtdjMr0lcgk5PjEIkP2b8R9F3w==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.1.tgz", + "integrity": "sha512-R3KLud4l/sN9AMmFZs0QY7cugGSiKvPhGyIsufCV5nJ0MjSlngUE7k80TmFeK9I62wOXrjWBtYA1knVs2OkF8w==", "license": "MIT", "dependencies": { "cheerio": "^1.0.0", "commander": "^12.1.0", + "entities": "^4.5.0", "mensch": "^0.3.4", "slick": "^1.12.2", "web-resource-inliner": "^7.0.0" @@ -4137,21 +4221,21 @@ } }, "node_modules/juice/node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", + "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.1.0", + "domutils": "^3.2.2", "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", + "undici": "^7.10.0", "whatwg-mimetype": "^4.0.0" }, "engines": { @@ -4162,9 +4246,9 @@ } }, "node_modules/juice/node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -4176,8 +4260,20 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/juice/node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/just-extend": { @@ -4188,12 +4284,12 @@ "license": "MIT" }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -4222,6 +4318,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -4237,6 +4334,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -4301,6 +4399,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "dev": true, "license": "MIT" }, @@ -4356,7 +4455,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", @@ -4405,15 +4505,15 @@ } }, "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, "node_modules/loupe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", - "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", "dev": true, "license": "MIT" }, @@ -4424,25 +4524,41 @@ "license": "ISC" }, "node_modules/luxon": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", - "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", + "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/mailersend": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.3.0.tgz", - "integrity": "sha512-pe498Ry7VaAb+oqcYqmPw1V7FlECG/mcqahQ3SiK54en4ZkyRwjyxoQwA9VU4s3npB+I44LlQGUudObZQe4/jA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.6.0.tgz", + "integrity": "sha512-YX2Gyc6Wyw4Q4IsJ4np2Reof8nFWQ2OP/yXZTZcGCz4B7B1BOAYs71Kjb1uNRTTfDChP7rzzWBoOaX6iNlvPAg==", "license": "MIT", "dependencies": { - "gaxios": "^5.0.1", - "isomorphic-unfetch": "^3.1.0", + "gaxios": "^6.0.0", + "isomorphic-unfetch": "^4.0.0", "qs": "^6.11.0" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4510,9 +4626,9 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4530,19 +4646,26 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -4643,6 +4766,30 @@ "mjml-cli": "bin/mjml" } }, + "node_modules/mjml-cli/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mjml-cli/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mjml-column": { "version": "5.0.0-alpha.6", "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-5.0.0-alpha.6.tgz", @@ -5022,6 +5169,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/mocha/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -5073,13 +5230,13 @@ } }, "node_modules/mongodb": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.0.tgz", - "integrity": "sha512-KeESYR5TEaFxOuwRqkOm3XOsMqCSkdeDMjaW5u2nuKfX7rqaofp7JQGoi7sVqQcNJTKuveNbzZtWMstb8ABP6Q==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", + "bson": "^6.10.4", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -5119,24 +5276,24 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", - "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^14.1.0 || ^13.0.0" } }, "node_modules/mongoose": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.10.1.tgz", - "integrity": "sha512-5beTeBZnJNndRXU9rxPol0JmTWZMAtgkPbooROkGilswvrZALDERY4cJrGZmgGwDS9dl0mxiB7si+Mv9Yms2fg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.0.tgz", + "integrity": "sha512-gLuAZsbwY0PHjrvfuXvUkUq9tXjyAjN3ioXph5Y6Seu7/Uo8xJaM+rrMbL/x34K4T3UTgtXRyfoq1YU16qKyIw==", "license": "MIT", "dependencies": { - "bson": "^6.10.1", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.13.0", + "mongodb": "~6.17.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -5178,9 +5335,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", - "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", + "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", "license": "MIT", "optionalDependencies": { "msgpackr-extract": "^3.0.2" @@ -5209,9 +5366,10 @@ } }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -5227,16 +5385,16 @@ } }, "node_modules/nan": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", - "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", "license": "MIT", "optional": true }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -5255,12 +5413,13 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -5302,6 +5461,26 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -5366,9 +5545,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", - "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -5403,17 +5582,6 @@ "url": "https://opencollective.com/nodemon" } }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5424,19 +5592,6 @@ "node": ">=4" } }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/nodemon/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -5487,9 +5642,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5542,6 +5697,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -5593,9 +5749,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/papaparse": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz", - "integrity": "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", "license": "MIT" }, "node_modules/parent-module": { @@ -5629,12 +5785,12 @@ } }, "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { - "entities": "^4.5.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -5665,6 +5821,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5716,9 +5884,9 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -5753,9 +5921,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -5772,7 +5940,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5781,12 +5949,12 @@ } }, "node_modules/postcss-calc": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.2.tgz", - "integrity": "sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -5797,12 +5965,12 @@ } }, "node_modules/postcss-colormin": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz", - "integrity": "sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.3.tgz", + "integrity": "sha512-xZxQcSyIVZbSsl1vjoqZAcMYYdnJsIyG8OvqShuuqf12S88qQboxxEy0ohNCOLwVPXTU+hFHvJPACRL2B5ohTA==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" @@ -5811,114 +5979,114 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-convert-values": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.4.tgz", - "integrity": "sha512-e2LSXPqEHVW6aoGbjV9RsSSNDO3A0rZLCBxN24zvxF25WknMPpX8Dm9UxxThyEbaytzggRuZxaGXqaOhxQ514Q==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.5.tgz", + "integrity": "sha512-0VFhH8nElpIs3uXKnVtotDJJNX0OGYSZmdt4XfSfvOMrFw1jKfpwpZxfC4iN73CTM/MWakDEmsHQXkISYj4BXw==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-comments": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.3.tgz", - "integrity": "sha512-q6fjd4WU4afNhWOA2WltHgCbkRhZPgQe7cXF74fuVB/ge4QbM9HEaOIzGSiMvM+g/cOsNAUGdf2JDzqA2F8iLA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-duplicates": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.1.tgz", - "integrity": "sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-empty": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", - "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-overridden": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", - "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-longhand": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.4.tgz", - "integrity": "sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^7.0.4" + "stylehacks": "^7.0.5" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-rules": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.4.tgz", - "integrity": "sha512-ZsaamiMVu7uBYsIdGtKJ64PkcQt6Pcpep/uO90EpLS3dxJi6OXamIobTYcImyXGoW0Wpugh7DSD3XzxZS9JCPg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.5.tgz", + "integrity": "sha512-ZonhuSwEaWA3+xYbOdJoEReKIBs5eDiBVLAGpYZpNFPzXZcEE5VKR7/qBEQvTZpiwjqhhqEQ+ax5O3VShBj9Wg==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "caniuse-api": "^3.0.0", - "cssnano-utils": "^5.0.0", - "postcss-selector-parser": "^6.1.2" + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-font-values": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", - "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -5927,75 +6095,75 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-gradients": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", - "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "license": "MIT", "dependencies": { "colord": "^2.9.3", - "cssnano-utils": "^5.0.0", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-params": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.2.tgz", - "integrity": "sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.3.tgz", + "integrity": "sha512-vUKV2+f5mtjewYieanLX0xemxIp1t0W0H/D11u+kQV/MWdygOO7xPMkbK+r9P6Lhms8MgzKARF/g5OPXhb8tgg==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", - "cssnano-utils": "^5.0.0", + "browserslist": "^4.24.5", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-selectors": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.4.tgz", - "integrity": "sha512-JG55VADcNb4xFCf75hXkzc1rNeURhlo7ugf6JjiiKRfMsKlDzN9CXHZDyiG6x/zGchpjQS+UAgb1d4nqXqOpmA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-charset": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", - "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-display-values": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", - "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6004,13 +6172,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-positions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", - "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6019,13 +6187,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", - "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6034,13 +6202,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-string": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", - "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6049,13 +6217,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", - "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6064,29 +6232,29 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-unicode": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.2.tgz", - "integrity": "sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.3.tgz", + "integrity": "sha512-EcoA29LvG3F+EpOh03iqu+tJY3uYYKzArqKJHxDhUYLa2u58aqGq16K6/AOsXD9yqLN8O6y9mmePKN5cx6krOw==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", - "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6095,13 +6263,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-whitespace": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", - "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6110,45 +6278,45 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-ordered-values": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", - "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "license": "MIT", "dependencies": { - "cssnano-utils": "^5.0.0", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-initial": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.2.tgz", - "integrity": "sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.3.tgz", + "integrity": "sha512-RFvkZaqiWtGMlVjlUHpaxGqEL27lgt+Q2Ixjf83CRAzqdo+TsDyGPtJUbPx2MuYIJ+sCQc2TrOvRnhcXQfgIVA==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", + "browserslist": "^4.24.5", "caniuse-api": "^3.0.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-transforms": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", - "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -6157,13 +6325,13 @@ "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -6174,9 +6342,9 @@ } }, "node_modules/postcss-svgo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", - "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.2.tgz", + "integrity": "sha512-5Dzy66JlnRM6pkdOTF8+cGsB1fnERTE8Nc+Eed++fOWo1hdsBptCsbG8UuJkgtZt75bRtMJIrPeZmtfANixdFA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -6186,22 +6354,22 @@ "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-unique-selectors": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.3.tgz", - "integrity": "sha512-J+58u5Ic5T1QjP/LDV9g3Cx4CNOgB5vz+kM6+OxHHhFACdcDeKhBXjQmB7fnIZM12YSTvsL0Opwco83DmacW2g==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-value-parser": { @@ -6335,14 +6503,15 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", - "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz", + "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -6361,9 +6530,9 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -6411,9 +6580,9 @@ "license": "MIT" }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -6448,7 +6617,8 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", @@ -6531,12 +6701,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6591,9 +6755,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7104,19 +7268,43 @@ } }, "node_modules/stylehacks": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.4.tgz", - "integrity": "sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.5.tgz", + "integrity": "sha512-5kNb7V37BNf0Q3w+1pxfa+oiNPS++/b4Jil9e/kPDgrk1zjEd6uR7SZeJiYaLYH6RRSC1XX2/37OTeU/4FvuIA==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", - "postcss-selector-parser": "^6.1.2" + "browserslist": "^4.24.5", + "postcss-selector-parser": "^7.1.0" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" + } + }, + "node_modules/super-simple-scheduler": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.7.tgz", + "integrity": "sha512-kxKcBWE4rYgUP+XHgvO5m8nzyTI+5UoC8VeLkXJiC7EdSJrOwocyYUBuuDzE+wDNVKEv20UineXKhe1P0twYiA==", + "license": "MIT", + "dependencies": { + "human-interval": "2.0.1", + "uuid": "11.1.0", + "winston": "3.17.0" + } + }, + "node_modules/super-simple-scheduler/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" } }, "node_modules/supports-color": { @@ -7167,9 +7355,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz", - "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==", + "version": "5.25.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.25.2.tgz", + "integrity": "sha512-V4JyoygUe5nCbn7bAD0fVKSC0yNcL3ROIQtGC7M0NATKuyosCSmMU6T0yDZIIuGpSxjsjZh/D2Ejb8lnF2jjxw==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -7202,12 +7390,6 @@ "tar-stream": "^2.1.4" } }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -7239,18 +7421,38 @@ "node": ">=18" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/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==", "license": "MIT" }, - "node_modules/timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7283,15 +7485,15 @@ } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/triple-beam": { @@ -7320,6 +7522,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -7342,6 +7545,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -7389,25 +7593,28 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", + "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", "license": "MIT", "engines": { - "node": ">=18.17" + "node": ">=20.18.1" } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, "node_modules/unfetch": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", - "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", - "license": "MIT" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz", + "integrity": "sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==", + "license": "MIT", + "workspaces": [ + "./packages/isomorphic-unfetch" + ] }, "node_modules/unpipe": { "version": "1.0.0", @@ -7419,9 +7626,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -7439,7 +7646,7 @@ "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -7453,6 +7660,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -7643,6 +7851,15 @@ "node": ">=4.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -7686,16 +7903,16 @@ } }, "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/which": { @@ -7754,6 +7971,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } diff --git a/server/package.json b/server/package.json index 51e7cd218..561d235c9 100755 --- a/server/package.json +++ b/server/package.json @@ -40,6 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", + "super-simple-scheduler": "1.0.7", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js new file mode 100644 index 000000000..fab46b260 --- /dev/null +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -0,0 +1,146 @@ +import Scheduler from "super-simple-scheduler"; +const SERVICE_NAME = "SuperSimpleQueue"; + +class SuperSimpleQueue { + static SERVICE_NAME = SERVICE_NAME; + + constructor({ appSettings, db, logger, helper }) { + this.appSettings = appSettings; + this.db = db; + this.logger = logger; + this.helper = helper; + } + + static async create({ appSettings, db, logger, helper }) { + const instance = new SuperSimpleQueue({ appSettings, db, logger, helper }); + await instance.init(); + return instance; + } + + init = async () => { + this.scheduler = new Scheduler({ + logLevel: process.env.LOG_LEVEL, + debug: process.env.NODE_ENV, + }); + this.scheduler.start(); + this.scheduler.addTemplate("test", this.helper.getMonitorJob()); + const monitors = await this.db.getAllMonitors(); + for (const monitor of monitors) { + await this.addJob(monitor._id, monitor); + } + }; + + addJob = async (monitorId, monitor) => { + this.scheduler.addJob({ + id: monitorId.toString(), + template: "test", + repeat: monitor.interval, + data: monitor, + }); + }; + + deleteJob = async (monitor) => { + this.scheduler.removeJob(monitor._id.toString()); + }; + + pauseJob = async (monitor) => { + const result = this.scheduler.pauseJob(monitor._id.toString()); + if (result === false) { + throw new Error("Failed to resume monitor"); + } + this.logger.debug({ + message: `Paused monitor ${monitor._id}`, + service: SERVICE_NAME, + method: "pauseJob", + }); + }; + + resumeJob = async (monitor) => { + const result = this.scheduler.resumeJob(monitor._id.toString()); + if (result === false) { + throw new Error("Failed to resume monitor"); + } + this.logger.debug({ + message: `Resumed monitor ${monitor._id}`, + service: SERVICE_NAME, + method: "resumeJob", + }); + }; + + updateJob = async (monitor) => { + this.scheduler.updateJob(monitor._id.toString(), monitor.interval); + }; + + shutdown = async () => { + this.scheduler.stop(); + }; + + getMetrics = async () => { + const jobs = this.scheduler.getJobs(); + const metrics = jobs.reduce( + (acc, job) => { + acc.totalRuns += job.runCount || 0; + acc.totalFailures += job.failCount || 0; + acc.jobs++; + if (job.failCount > 0 && job.lastFailedAt >= job.lsatRunAt) { + acc.failingJobs++; + } + + if (job.lockedAt) { + acc.activeJobs++; + } + + if (job.failCount > 0) { + acc.jobsWithFailures.push({ + monitorId: job.id, + monitorUrl: job?.data?.url || null, + monitorType: job?.data?.type || null, + failedAt: job.lastFailedAt, + failCount: job.failCount, + failReason: job.lastFailReason, + }); + } + return acc; + }, + { + jobs: 0, + activeJobs: 0, + failingJobs: 0, + jobsWithFailures: [], + totalRuns: 0, + totalFailures: 0, + } + ); + return metrics; + }; + + getJobs = async () => { + const jobs = this.scheduler.getJobs(); + return jobs.map((job) => { + return { + monitorId: job.id, + monitorUrl: job?.data?.url || null, + monitorType: job?.data?.type || null, + active: job.active, + lockedAt: job.lockedAt, + runCount: job.runCount || 0, + failCount: job.failCount || 0, + failReason: job.lastFailReason, + lastRunAt: job.lastRunAt, + lastFinishedAt: job.lastFinishedAt, + lastRunTook: job.lockedAt ? null : job.lastFinishedAt - job.lastRunAt, + lastFailedAt: job.lastFailedAt, + }; + }); + }; + + flushQueues = async () => { + console.log("flush not implemented"); + }; + + obliterate = async () => { + console.log("obliterate not implemented"); + }; +} + +export default SuperSimpleQueue; diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js new file mode 100644 index 000000000..45c5978b9 --- /dev/null +++ b/server/service/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -0,0 +1,99 @@ +const SERVICE_NAME = "SuperSimpleQueueHelper"; + +class SuperSimpleQueueHelper { + constructor({ db, logger, networkService, statusService, notificationService }) { + this.db = db; + this.logger = logger; + this.networkService = networkService; + this.statusService = statusService; + this.notificationService = notificationService; + } + + getMonitorJob = () => { + return async (monitor) => { + try { + const monitorId = monitor._id; + if (!monitorId) { + throw new Error("No monitor id"); + } + + const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId); + if (maintenanceWindowActive) { + this.logger.info({ + message: `Monitor ${monitorId} is in maintenance window`, + service: SERVICE_NAME, + method: "getMonitorJob", + }); + return; + } + const networkResponse = await this.networkService.getStatus(monitor); + if (!networkResponse) { + throw new Error("No network response"); + } + + const { + monitor: updatedMonitor, + statusChanged, + prevStatus, + } = await this.statusService.updateStatus(networkResponse); + + this.notificationService + .handleNotifications({ + ...networkResponse, + monitor: updatedMonitor, + prevStatus, + statusChanged, + }) + .catch((error) => { + this.logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "getMonitorJob", + details: `Error sending notifications for job ${monitor._id}: ${error.message}`, + stack: error.stack, + }); + }); + } catch (error) { + this.logger.warn({ + message: error.message, + service: error.service || SERVICE_NAME, + method: error.method || "getMonitorJob", + stack: error.stack, + }); + throw error; + } + }; + }; + + async isInMaintenanceWindow(monitorId) { + const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId); + // Check for active maintenance window: + const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => { + if (window.active) { + const start = new Date(window.start); + const end = new Date(window.end); + const now = new Date(); + const repeatInterval = window.repeat || 0; + + // If start is < now and end > now, we're in maintenance + if (start <= now && end >= now) return true; + + // If maintenance window was set in the past with a repeat, + // we need to advance start and end to see if we are in range + + while (start < now && repeatInterval !== 0) { + start.setTime(start.getTime() + repeatInterval); + end.setTime(end.getTime() + repeatInterval); + if (start <= now && end >= now) { + return true; + } + } + return false; + } + return acc; + }, false); + return maintenanceWindowIsActive; + } +} + +export default SuperSimpleQueueHelper; diff --git a/server/service/networkService.js b/server/service/networkService.js index 08b2f065e..01f72d37d 100755 --- a/server/service/networkService.js +++ b/server/service/networkService.js @@ -256,7 +256,7 @@ class NetworkService { async requestPagespeed(monitor) { try { const url = monitor.url; - const updatedMonitor = { ...monitor }; + const updatedMonitor = monitor.toObject(); let pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; const dbSettings = await this.settingsService.getDBSettings(); From ca13185d846be17ec60e914f1953917dec8d1ca6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 13:40:41 +0800 Subject: [PATCH 002/106] convert monitor to object --- server/service/SuperSimpleQueue/SuperSimpleQueue.js | 2 +- server/service/networkService.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index fab46b260..ec6c16ea9 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -35,7 +35,7 @@ class SuperSimpleQueue { id: monitorId.toString(), template: "test", repeat: monitor.interval, - data: monitor, + data: monitor.toObject(), }); }; diff --git a/server/service/networkService.js b/server/service/networkService.js index 01f72d37d..08b2f065e 100755 --- a/server/service/networkService.js +++ b/server/service/networkService.js @@ -256,7 +256,7 @@ class NetworkService { async requestPagespeed(monitor) { try { const url = monitor.url; - const updatedMonitor = monitor.toObject(); + const updatedMonitor = { ...monitor }; let pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; const dbSettings = await this.settingsService.getDBSettings(); From 38f5c41272552249d8781615b5585ef6229ebee9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 13:45:18 +0800 Subject: [PATCH 003/106] implement flush --- .../SuperSimpleQueue/SuperSimpleQueue.js | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index ec6c16ea9..578f9dd3f 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -18,15 +18,26 @@ class SuperSimpleQueue { } init = async () => { - this.scheduler = new Scheduler({ - logLevel: process.env.LOG_LEVEL, - debug: process.env.NODE_ENV, - }); - this.scheduler.start(); - this.scheduler.addTemplate("test", this.helper.getMonitorJob()); - const monitors = await this.db.getAllMonitors(); - for (const monitor of monitors) { - await this.addJob(monitor._id, monitor); + try { + this.scheduler = new Scheduler({ + logLevel: process.env.LOG_LEVEL, + debug: process.env.NODE_ENV, + }); + this.scheduler.start(); + this.scheduler.addTemplate("test", this.helper.getMonitorJob()); + const monitors = await this.db.getAllMonitors(); + for (const monitor of monitors) { + await this.addJob(monitor._id, monitor); + } + return true; + } catch (error) { + this.logger.error({ + message: "Failed to initialize SuperSimpleQueue", + service: SERVICE_NAME, + method: "init", + details: error, + }); + return false; } }; @@ -135,7 +146,12 @@ class SuperSimpleQueue { }; flushQueues = async () => { - console.log("flush not implemented"); + const stopRes = this.scheduler.stop(); + const flushRes = this.scheduler.flushJobs(); + const initRes = await this.init(); + return { + success: stopRes && flushRes && initRes, + }; }; obliterate = async () => { From ce103c6b599bba20a46946f220ec666e8ce91bd6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 27 Jun 2025 13:51:39 +0800 Subject: [PATCH 004/106] format --- server/templates/addReview.mjml | 2 +- server/templates/employeeActivation.mjml | 6 ++++-- server/templates/hardwareIncident.mjml | 6 +++--- server/templates/noIncidentsThisWeek.mjml | 2 +- server/templates/passwordReset.mjml | 4 ++-- server/templates/serverIsDown.mjml | 8 ++++---- server/templates/serverIsUp.mjml | 8 ++++---- server/templates/welcomeEmail.mjml | 2 +- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/server/templates/addReview.mjml b/server/templates/addReview.mjml index 9d00be7d0..644970f6e 100755 --- a/server/templates/addReview.mjml +++ b/server/templates/addReview.mjml @@ -30,7 +30,7 @@ -

Hello {{name}}!

+

Hello {{ name }}!

We hope you’re finding Checkmate helpful in monitoring your infrastructure. Your support means a lot to us, and we truly appreciate having you as diff --git a/server/templates/employeeActivation.mjml b/server/templates/employeeActivation.mjml index 3e35db6e8..317832c1f 100755 --- a/server/templates/employeeActivation.mjml +++ b/server/templates/employeeActivation.mjml @@ -29,10 +29,12 @@ -

Hello {{name}}!

+

Hello {{ name }}!

One of the admins created an account for you on the Checkmate server.

You can go ahead and create your account using this link.

-

{{link}}

+

+ {{ link }} +

Thank you.

diff --git a/server/templates/hardwareIncident.mjml b/server/templates/hardwareIncident.mjml index 19fb589fc..3f50a3d58 100755 --- a/server/templates/hardwareIncident.mjml +++ b/server/templates/hardwareIncident.mjml @@ -47,10 +47,10 @@ -

Hello {{name}}!

-

{{monitor}} at {{url}} has the following infrastructure alerts:

+

Hello {{ name }}!

+

{{ monitor }} at {{ url }} has the following infrastructure alerts:

{{#each alerts}} -

• {{this}}

+

• {{ this }}

{{/each}}
diff --git a/server/templates/noIncidentsThisWeek.mjml b/server/templates/noIncidentsThisWeek.mjml index d31d63d10..d160e44de 100755 --- a/server/templates/noIncidentsThisWeek.mjml +++ b/server/templates/noIncidentsThisWeek.mjml @@ -45,7 +45,7 @@ -

Hello {{name}}!

+

Hello {{ name }}!

There were no incidents this week. Good job!

Current monitors:

Google: 100% availability

diff --git a/server/templates/passwordReset.mjml b/server/templates/passwordReset.mjml index e0b85a4f4..5da29b341 100755 --- a/server/templates/passwordReset.mjml +++ b/server/templates/passwordReset.mjml @@ -31,10 +31,10 @@ -

Hello {{name}}!

+

Hello {{ name }}!

You are receiving this email because a password reset request has been made - for {{email}}. Please use the link below on the site to reset your password. + for {{ email }}. Please use the link below on the site to reset your password.

Reset Password

If you didn't request this, please ignore this email.

diff --git a/server/templates/serverIsDown.mjml b/server/templates/serverIsDown.mjml index b75d970e6..d46011070 100755 --- a/server/templates/serverIsDown.mjml +++ b/server/templates/serverIsDown.mjml @@ -36,7 +36,7 @@ font-size="18px" color="red" > - {{monitor}} is down + {{ monitor }} is down
-

Hello {{name}}!

+

Hello {{ name }}!

We detected an incident on one of your monitors. Your service is currently down. We'll send a message to you once it is up again.

-

Monitor name: {{monitor}}

-

URL: {{url}}

+

Monitor name: {{ monitor }}

+

URL: {{ url }}

diff --git a/server/templates/serverIsUp.mjml b/server/templates/serverIsUp.mjml index beb34481d..074c5e3dd 100755 --- a/server/templates/serverIsUp.mjml +++ b/server/templates/serverIsUp.mjml @@ -36,7 +36,7 @@ font-size="18px" color="green" > - {{monitor}} is up + {{ monitor }} is up
-

Hello {{name}}!

+

Hello {{ name }}!

Your latest incident is resolved and your monitored service is up again.

-

Monitor name: {{monitor}}

-

URL: {{url}}

+

Monitor name: {{ monitor }}

+

URL: {{ url }}

diff --git a/server/templates/welcomeEmail.mjml b/server/templates/welcomeEmail.mjml index 663518ec2..372e4c8ae 100755 --- a/server/templates/welcomeEmail.mjml +++ b/server/templates/welcomeEmail.mjml @@ -30,7 +30,7 @@ -

Hello {{name}}!

+

Hello {{ name }}!

Thank you for trying out Checkmate! We developed it with great care to meet our own needs, and we're excited to share it with you. From c3f5d249a08bcc3db4994488cb098ff0a645f81e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 7 Jul 2025 08:55:57 -0700 Subject: [PATCH 005/106] use latest library --- server/package-lock.json | 104 +++++++++--------- server/package.json | 2 +- .../SuperSimpleQueue/SuperSimpleQueue.js | 4 +- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 427356e90..a7396bee2 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,7 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.7", + "super-simple-scheduler": "1.0.12", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -172,9 +172,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -187,9 +187,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -247,9 +247,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", - "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", + "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", "dev": true, "license": "MIT", "engines": { @@ -813,16 +813,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1123,9 +1123,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", - "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "version": "24.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", + "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -2258,9 +2258,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -2287,9 +2287,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -2663,9 +2663,9 @@ } }, "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2710,9 +2710,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.175", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.175.tgz", - "integrity": "sha512-Nqpef9mOVo7pZfl9NIUhj7tgtRTsMzCzRTJDP1ccim4Wb4YHOz3Le87uxeZq68OCNwau2iQ/X7UwdAZ3ReOkmg==", + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2886,19 +2886,19 @@ } }, "node_modules/eslint": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", - "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", + "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.1", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.30.1", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5286,9 +5286,9 @@ } }, "node_modules/mongoose": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.0.tgz", - "integrity": "sha512-gLuAZsbwY0PHjrvfuXvUkUq9tXjyAjN3ioXph5Y6Seu7/Uo8xJaM+rrMbL/x34K4T3UTgtXRyfoq1YU16qKyIw==", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", + "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -6509,9 +6509,9 @@ } }, "node_modules/prettier": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz", - "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -7284,9 +7284,9 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.7.tgz", - "integrity": "sha512-kxKcBWE4rYgUP+XHgvO5m8nzyTI+5UoC8VeLkXJiC7EdSJrOwocyYUBuuDzE+wDNVKEv20UineXKhe1P0twYiA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.12.tgz", + "integrity": "sha512-DQeSfnHYKkaaMW6OhZ1+AGneGR94H3/ux+EuHKd0oOnIzhjYUcC613boGWUjcleEQSbXaEW40TO8HWXyi76M1Q==", "license": "MIT", "dependencies": { "human-interval": "2.0.1", @@ -7355,9 +7355,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.25.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.25.2.tgz", - "integrity": "sha512-V4JyoygUe5nCbn7bAD0fVKSC0yNcL3ROIQtGC7M0NATKuyosCSmMU6T0yDZIIuGpSxjsjZh/D2Ejb8lnF2jjxw==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.26.0.tgz", + "integrity": "sha512-U8m1LruHrk33gIIT5qDKhXMygT4FonRGBE92zMbxP4i9ULolPlKISy5Pd3RCES8pWdbGzXhvm/Q6jdA/HsrClg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -7593,9 +7593,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", + "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", "license": "MIT", "engines": { "node": ">=20.18.1" diff --git a/server/package.json b/server/package.json index 561d235c9..361d8b29b 100755 --- a/server/package.json +++ b/server/package.json @@ -40,7 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.7", + "super-simple-scheduler": "1.0.12", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index 578f9dd3f..c528099b6 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -87,7 +87,7 @@ class SuperSimpleQueue { }; getMetrics = async () => { - const jobs = this.scheduler.getJobs(); + const jobs = await this.scheduler.getJobs(); const metrics = jobs.reduce( (acc, job) => { acc.totalRuns += job.runCount || 0; @@ -126,7 +126,7 @@ class SuperSimpleQueue { }; getJobs = async () => { - const jobs = this.scheduler.getJobs(); + const jobs = await this.scheduler.getJobs(); return jobs.map((job) => { return { monitorId: job.id, From cb2016a5dce749d1f5b602151583e4bdd5cf57f5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 8 Jul 2025 11:04:20 -0700 Subject: [PATCH 006/106] update queue version --- server/package-lock.json | 73 ++++++++++++------- server/package.json | 2 +- .../SuperSimpleQueue/SuperSimpleQueue.js | 11 ++- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index a7396bee2..8ea8f17d9 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,7 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.12", + "super-simple-scheduler": "1.0.17", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -120,9 +120,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", + "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", "license": "MIT", "optional": true, "dependencies": { @@ -1123,9 +1123,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", - "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", + "version": "24.0.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.11.tgz", + "integrity": "sha512-CJV8eqrYnwQJGMrvcRhQmZfpyniDavB+7nAZYJc6w99hFYJyFN3INV1/2W3QfQrqM36WTLrijJ1fxxvGBmCSxA==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -1198,9 +1198,9 @@ } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -1693,9 +1693,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "funding": [ { "type": "opencollective", @@ -2710,9 +2710,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.179", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", - "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", + "version": "1.5.180", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.180.tgz", + "integrity": "sha512-ED+GEyEh3kYMwt2faNmgMB0b8O5qtATGgR4RmRsIp4T6p7B8vdMbIedYndnvZfsaXvSzegtpfqRMDNCjjiSduA==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -5286,9 +5286,9 @@ } }, "node_modules/mongoose": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", - "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.2.tgz", + "integrity": "sha512-52T4XPhDgJL4cqooBsOORzYBH3ddMwABMEF/LV7TgvD2DEKZlnYTc2HF9ch1U2lcKjhE4pQ+WuInfLFJbguGcQ==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -7284,16 +7284,39 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.12.tgz", - "integrity": "sha512-DQeSfnHYKkaaMW6OhZ1+AGneGR94H3/ux+EuHKd0oOnIzhjYUcC613boGWUjcleEQSbXaEW40TO8HWXyi76M1Q==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.17.tgz", + "integrity": "sha512-PIs2dymRu6lVgvddhwjCORzCgf7jDCEoRSZu5xwEsNZIjxStMgpGeV9N1EbKzkr3T+NNojUcywAZDL4lTrNH7g==", "license": "MIT", "dependencies": { "human-interval": "2.0.1", + "mongoose": "8.16.1", "uuid": "11.1.0", "winston": "3.17.0" } }, + "node_modules/super-simple-scheduler/node_modules/mongoose": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", + "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.17.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, "node_modules/super-simple-scheduler/node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -7355,9 +7378,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.26.0.tgz", - "integrity": "sha512-U8m1LruHrk33gIIT5qDKhXMygT4FonRGBE92zMbxP4i9ULolPlKISy5Pd3RCES8pWdbGzXhvm/Q6jdA/HsrClg==", + "version": "5.26.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.26.2.tgz", + "integrity": "sha512-WmMS9iMlHQejNm/Uw5ZTo4e3M2QMmEavRz7WLWVsq7Mlx4PSHJbY+VCrLsAz9wLxyHVgrJdt7N8+SdQLa52Ykg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" diff --git a/server/package.json b/server/package.json index 361d8b29b..bc5cf4128 100755 --- a/server/package.json +++ b/server/package.json @@ -40,7 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.12", + "super-simple-scheduler": "1.0.17", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index c528099b6..d5e3e6ee6 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -20,11 +20,14 @@ class SuperSimpleQueue { init = async () => { try { this.scheduler = new Scheduler({ - logLevel: process.env.LOG_LEVEL, - debug: process.env.NODE_ENV, + storeType: "mongo", + logLevel: "debug", + debug: true, + dbUri: this.appSettings.dbConnectionString, }); this.scheduler.start(); - this.scheduler.addTemplate("test", this.helper.getMonitorJob()); + + this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob()); const monitors = await this.db.getAllMonitors(); for (const monitor of monitors) { await this.addJob(monitor._id, monitor); @@ -44,7 +47,7 @@ class SuperSimpleQueue { addJob = async (monitorId, monitor) => { this.scheduler.addJob({ id: monitorId.toString(), - template: "test", + template: "monitor-job", repeat: monitor.interval, data: monitor.toObject(), }); From 1456c4f749b2dfd67013650441009df99153c1fa Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 10 Jul 2025 16:06:38 -0700 Subject: [PATCH 007/106] bump version --- server/package-lock.json | 9 +++++---- server/package.json | 2 +- server/service/SuperSimpleQueue/SuperSimpleQueue.js | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 8ea8f17d9..6fbb33de6 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -33,7 +33,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.17", + "super-simple-scheduler": "1.2.3", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -7284,12 +7284,13 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.0.17.tgz", - "integrity": "sha512-PIs2dymRu6lVgvddhwjCORzCgf7jDCEoRSZu5xwEsNZIjxStMgpGeV9N1EbKzkr3T+NNojUcywAZDL4lTrNH7g==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.2.3.tgz", + "integrity": "sha512-lpA/xzwRXl9KXN/+mznHfF2cMDQOXejJYna/bk3L0SPBDr28afrtx3orEKZ2JVDwF+f3Np01YlYamT51UU8OCw==", "license": "MIT", "dependencies": { "human-interval": "2.0.1", + "ioredis": "5.6.1", "mongoose": "8.16.1", "uuid": "11.1.0", "winston": "3.17.0" diff --git a/server/package.json b/server/package.json index bc5cf4128..494b81e2c 100755 --- a/server/package.json +++ b/server/package.json @@ -40,7 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.0.17", + "super-simple-scheduler": "1.2.3", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, diff --git a/server/service/SuperSimpleQueue/SuperSimpleQueue.js b/server/service/SuperSimpleQueue/SuperSimpleQueue.js index d5e3e6ee6..f0b59640e 100644 --- a/server/service/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/service/SuperSimpleQueue/SuperSimpleQueue.js @@ -20,7 +20,8 @@ class SuperSimpleQueue { init = async () => { try { this.scheduler = new Scheduler({ - storeType: "mongo", + // storeType: "mongo", + // storeType: "redis", logLevel: "debug", debug: true, dbUri: this.appSettings.dbConnectionString, From 1cbec8220dff3394293391d9eebc016f3824711f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 10 Jul 2025 16:08:04 -0700 Subject: [PATCH 008/106] add diagnostic page --- .../Components/Charts/CustomGauge/index.jsx | 4 +- client/src/Hooks/logHooks.js | 77 ++++++++++++++++++- client/src/Hooks/queueHooks.js | 57 -------------- .../Diagnostics/components/gauges/index.jsx | 74 ++++++++++++++++++ .../Diagnostics/components/gauges/utils.js | 4 + client/src/Pages/Logs/Diagnostics/index.jsx | 30 ++++++++ client/src/Pages/Logs/Logs/index.jsx | 2 +- client/src/Pages/Logs/Queue/index.jsx | 2 +- client/src/Pages/Logs/index.jsx | 5 +- client/src/Utils/NetworkService.js | 8 ++ client/src/locales/en.json | 6 +- server/controllers/diagnosticController.js | 73 ++++++++++++++++++ server/routes/diagnosticRoute.js | 1 + 13 files changed, 279 insertions(+), 64 deletions(-) delete mode 100644 client/src/Hooks/queueHooks.js create mode 100644 client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx create mode 100644 client/src/Pages/Logs/Diagnostics/components/gauges/utils.js create mode 100644 client/src/Pages/Logs/Diagnostics/index.jsx diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/Charts/CustomGauge/index.jsx index 3c7e13610..359b5b328 100644 --- a/client/src/Components/Charts/CustomGauge/index.jsx +++ b/client/src/Components/Charts/CustomGauge/index.jsx @@ -54,8 +54,8 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold = const fillColor = progressWithinRange > threshold - ? theme.palette.error.lowContrast // CAIO_REVIEW - : theme.palette.accent.main; // CAIO_REVIEW + ? theme.palette.error.lowContrast + : theme.palette.accent.main; return ( { return [logs, isLoading, error]; }; -export { useFetchLogs }; +const useFetchQueueData = (trigger) => { + const [jobs, setJobs] = useState(undefined); + const [metrics, setMetrics] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + + useEffect(() => { + const fetchJobs = async () => { + try { + setIsLoading(true); + const response = await networkService.getQueueData(); + if (response.status === 200) { + setJobs(response.data.data.jobs); + setMetrics(response.data.data.metrics); + } + } catch (error) { + setError(error); + } finally { + setIsLoading(false); + } + }; + + fetchJobs(); + }, [trigger]); + + return [jobs, metrics, isLoading, error]; +}; + +const useFlushQueue = () => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + + const flushQueue = async (trigger, setTrigger) => { + try { + setIsLoading(true); + await networkService.flushQueue(); + createToast({ + body: "Queue flushed", + }); + } catch (error) { + setError(error); + createToast({ + body: error.message, + }); + } finally { + setIsLoading(false); + setTrigger(!trigger); + } + }; + return [flushQueue, isLoading, error]; +}; + +const useFetchDiagnostics = () => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + const [diagnostics, setDiagnostics] = useState(undefined); + + useEffect(() => { + const fetchDiagnostics = async () => { + try { + setIsLoading(true); + const response = await networkService.getDiagnostics(); + setDiagnostics(response.data.data); + } catch (error) { + setError(error); + } finally { + setIsLoading(false); + } + }; + fetchDiagnostics(); + }, []); + + return [diagnostics, isLoading, error]; +}; + +export { useFetchLogs, useFetchQueueData, useFlushQueue, useFetchDiagnostics }; diff --git a/client/src/Hooks/queueHooks.js b/client/src/Hooks/queueHooks.js deleted file mode 100644 index f7a25b3d3..000000000 --- a/client/src/Hooks/queueHooks.js +++ /dev/null @@ -1,57 +0,0 @@ -import { useState, useEffect } from "react"; -import { networkService } from "../main"; -import { createToast } from "../Utils/toastUtils"; - -const useFetchQueueData = (trigger) => { - const [jobs, setJobs] = useState(undefined); - const [metrics, setMetrics] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - useEffect(() => { - const fetchJobs = async () => { - try { - setIsLoading(true); - const response = await networkService.getQueueData(); - if (response.status === 200) { - setJobs(response.data.data.jobs); - setMetrics(response.data.data.metrics); - } - } catch (error) { - setError(error); - } finally { - setIsLoading(false); - } - }; - - fetchJobs(); - }, [trigger]); - - return [jobs, metrics, isLoading, error]; -}; - -const useFlushQueue = () => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - const flushQueue = async (trigger, setTrigger) => { - try { - setIsLoading(true); - await networkService.flushQueue(); - createToast({ - body: "Queue flushed", - }); - } catch (error) { - setError(error); - createToast({ - body: error.message, - }); - } finally { - setIsLoading(false); - setTrigger(!trigger); - } - }; - return [flushQueue, isLoading, error]; -}; - -export { useFetchQueueData, useFlushQueue }; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx new file mode 100644 index 000000000..21d9a35d8 --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -0,0 +1,74 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Gauge from "../../../../../Components/Charts/CustomGauge"; +import Typography from "@mui/material/Typography"; + +// Utils +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; +import { getPercentage } from "./utils"; + +const GaugeBox = ({ title, subtitle, children }) => { + const theme = useTheme(); + return ( + + {children} + + {title} + {subtitle} + + ); +}; + +const Gauges = ({ diagnostics }) => { + const heapTotalSize = getPercentage( + diagnostics?.v8HeapStats?.totalHeapSizeMb, + diagnostics?.v8HeapStats?.heapSizeLimitMb + ); + + const heapUsedSize = getPercentage( + diagnostics?.v8HeapStats?.usedHeapSizeMb, + diagnostics?.v8HeapStats?.heapSizeLimitMb + ); + + const actualHeapUsed = getPercentage( + diagnostics?.v8HeapStats?.usedHeapSizeMb, + diagnostics?.v8HeapStats?.totalHeapSizeMb + ); + const theme = useTheme(); + + return ( + + + + + + + + + + + + ); +}; + +Gauges.propTypes = { + diagnostics: PropTypes.object.isRequired, +}; + +export default Gauges; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js b/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js new file mode 100644 index 000000000..bdc570a3b --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js @@ -0,0 +1,4 @@ +export const getPercentage = (value, total) => { + if (!value || !total) return 0; + return (value / total) * 100; +}; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx new file mode 100644 index 000000000..9d7ee4639 --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -0,0 +1,30 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Gauges from "./components/gauges"; + +import { useTheme } from "@emotion/react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useFetchDiagnostics } from "../../../Hooks/logHooks"; + +const Diagnostics = () => { + // Local state + + // Hooks + const theme = useTheme(); + const { t } = useTranslation(); + const [diagnostics, isLoading, error] = useFetchDiagnostics(); + console.log(diagnostics); + // Setup + return ( + + + {t("diagnosticsPage.description")} + + + + ); +}; + +export default Diagnostics; diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx index 6c5e1a301..d8bd323a3 100644 --- a/client/src/Pages/Logs/Logs/index.jsx +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -51,7 +51,7 @@ const Logs = () => { return ( - {t("logsPage.description")} + {t("logsPage.description")} { const theme = useTheme(); // Local state - const [value, setValue] = useState(0); + const [value, setValue] = useState(2); // Handlers const handleChange = (event, newValue) => { @@ -31,9 +32,11 @@ const Logs = () => { > + {value === 0 && } {value === 1 && } + {value === 2 && } ); }; diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 7fba2ce18..895d90d30 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1091,6 +1091,14 @@ class NetworkService { }, }); } + + async getDiagnostics() { + return this.axiosInstance.get(`/diagnostic/system`, { + headers: { + "Content-Type": "application/json", + }, + }); + } } export default NetworkService; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index ddc07f166..54f9600e2 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -451,13 +451,17 @@ }, "tabs": { "logs": "Server logs", - "queue": "Job queue" + "queue": "Job queue", + "diagnostics": "Diagnostics" }, "title": "Logs", "toast": { "fetchLogsSuccess": "Logs fetched successfully" } }, + "diagnosticsPage": { + "description": "System diagnostics" + }, "low": "low", "maintenance": "maintenance", "maintenanceRepeat": "Maintenance Repeat", diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index 8fa5fa42b..c4223dc04 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -1,6 +1,14 @@ import { handleError } from "./controllerUtils.js"; +import v8 from "v8"; +import os from "os"; + const SERVICE_NAME = "diagnosticController"; +const obs = new PerformanceObserver((items) => { + const entry = items.getEntries()[0]; + performance.clearMarks(); +}); +obs.observe({ entryTypes: ["measure"] }); class DiagnosticController { constructor(db) { this.db = db; @@ -44,5 +52,70 @@ class DiagnosticController { next(handleError(error, SERVICE_NAME, "getDbStats")); } } + + async getSystemStats(req, res, next) { + try { + // Memory Usage + const totalMemory = os.totalmem(); + const freeMemory = os.freemem(); + + const osStats = { + freeMemoryMb: freeMemory / 1024 / 1024, // MB + totalMemoryMb: totalMemory / 1024 / 1024, // MB + }; + + const used = process.memoryUsage(); + const memoryUsage = {}; + for (let key in used) { + memoryUsage[`${key}Mb`] = Math.round((used[key] / 1024 / 1024) * 100) / 100; // MB + } + + // CPU Usage + const cpuUsage = process.cpuUsage(); + const cpuMetrics = { + userUsageMs: cpuUsage.user / 1000, // ms + systemUsageMs: cpuUsage.system / 1000, // ms + }; + + // V8 Heap Statistics + const heapStats = v8.getHeapStatistics(); + const v8Metrics = { + totalHeapSizeMb: heapStats.total_heap_size / 1024 / 1024, // MB + usedHeapSizeMb: heapStats.used_heap_size / 1024 / 1024, // MB + heapSizeLimitMb: heapStats.heap_size_limit / 1024 / 1024, // MB + }; + + // Event Loop Delay + let eventLoopDelay = 0; + performance.mark("start"); + await new Promise((resolve) => setTimeout(resolve, 0)); + performance.mark("end"); + performance.measure("eventLoopDelay", "start", "end"); + const entries = performance.getEntriesByName("eventLoopDelay"); + if (entries.length > 0) { + eventLoopDelay = entries[0].duration; + } + + // Uptime + const uptime = process.uptime(); // seconds + + // Combine Metrics + const diagnostics = { + osStats, + memoryUsage, + cpuUsage: cpuMetrics, + v8HeapStats: v8Metrics, + eventLoopDelayMs: eventLoopDelay, + uptimeSeconds: uptime, + }; + + return res.success({ + msg: "OK", + data: diagnostics, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMemoryUsage")); + } + } } export default DiagnosticController; diff --git a/server/routes/diagnosticRoute.js b/server/routes/diagnosticRoute.js index 616d01ccf..0d8f4df84 100755 --- a/server/routes/diagnosticRoute.js +++ b/server/routes/diagnosticRoute.js @@ -13,6 +13,7 @@ class DiagnosticRoutes { ); this.router.post("/db/stats", this.diagnosticController.getDbStats); + this.router.get("/system", this.diagnosticController.getSystemStats); } getRouter() { From 2c1a01bb6ac50b5fd4ca6fdd167a9ab741ab6405 Mon Sep 17 00:00:00 2001 From: shanika Jayawardane Date: Fri, 11 Jul 2025 07:44:02 -0600 Subject: [PATCH 009/106] add the All select option to monitor maintenance window for all monitors same as status pages --- .../Components/MonitorList/index.jsx | 107 ++++++++++++++++++ .../Maintenance/CreateMaintenance/index.jsx | 55 ++++++++- 2 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx new file mode 100644 index 000000000..36d6594dc --- /dev/null +++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx @@ -0,0 +1,107 @@ +// Components +import { Stack, Typography } from "@mui/material"; +import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded"; +import DeleteIcon from "@mui/icons-material/Delete"; + +// Utils +import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; +import { useTheme } from "@emotion/react"; +import Checkbox from "../../../../../Components/Inputs/Checkbox"; +const MonitorListItem = ({ + monitor, + innerRef, + draggableProps, + dragHandleProps, + onDelete, +}) => { + const theme = useTheme(); + return ( + + + {monitor.name} + { + onDelete(monitor); + }} + /> + + ); +}; + +const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => { + const onDelete = (monitorToDelete) => { + const newMonitors = selectedMonitors.filter( + (monitor) => monitor._id !== monitorToDelete._id + ); + setSelectedMonitors(newMonitors); + }; + const reorder = (list, startIndex, endIndex) => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + + return result; + }; + + const onDragEnd = (result) => { + // dropped outside the list + if (!result.destination) { + return; + } + + const reorderedMonitors = reorder( + selectedMonitors, + result.source.index, + result.destination.index + ); + + setSelectedMonitors(reorderedMonitors); + }; + + return ( + + + {(provided, snapshot) => ( + + {selectedMonitors?.map((monitor, index) => ( + + {(provided, snapshot) => ( + + )} + + ))} + {provided.placeholder} + + )} + + + ); +}; + +export default MonitorList; diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx index e71c7112c..c4e839e02 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -9,6 +9,8 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker"; import { maintenanceWindowValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; +import MonitorList from "./Components/MonitorList"; +import Checkbox from "../../../Components/Inputs/Checkbox"; import dayjs from "dayjs"; import Select from "../../../Components/Inputs/Select"; @@ -129,6 +131,7 @@ const CreateMaintenance = () => { monitors: [], }); const [errors, setErrors] = useState({}); + const [selectedMonitors, setSelectedMonitors] = useState([]); useEffect(() => { const fetchMonitors = async () => { @@ -165,6 +168,7 @@ const CreateMaintenance = () => { durationUnit, monitors: monitor ? [monitor] : [], }); + setSelectedMonitors(monitor ? [monitor] : []); } catch (error) { createToast({ body: "Failed to fetch data" }); logger.error("Failed to fetch monitors", error); @@ -175,6 +179,11 @@ const CreateMaintenance = () => { fetchMonitors(); }, [user]); + // Sync form.monitors with selectedMonitors + useEffect(() => { + setForm((prev) => ({ ...prev, monitors: selectedMonitors })); + }, [selectedMonitors]); + const handleSearch = (value) => { setSearch(value); }; @@ -212,6 +221,33 @@ const CreateMaintenance = () => { }); }; + const handleMonitorsChange = (selected) => { + if (selected.some((m) => m._id === "__all__")) { + if (selectedMonitors.length !== monitors.length) { + setSelectedMonitors(monitors); + } else { + setSelectedMonitors([]); + } + } else { + // Ensure all are objects + const selectedObjs = selected + .map((sel) => + typeof sel === "string" ? monitors.find((m) => m._id === sel) : sel + ) + .filter(Boolean); + setSelectedMonitors(selectedObjs); + } + const { error } = maintenanceWindowValidation.validate( + { monitors: selected }, + { abortEarly: false } + ); + setErrors((prev) => { + return buildErrors(prev, "monitors", error); + }); + }; + + const allSelected = selectedMonitors.length === monitors.length && monitors.length > 0; + const handleSubmit = async () => { if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return; // Build timestamp for maintenance window from startDate and startTime @@ -267,6 +303,10 @@ const CreateMaintenance = () => { } }; + // Add Select All option to the dropdown + const selectAllOption = { _id: "__all__", name: "Select All" }; + const monitorOptions = [selectAllOption, ...monitors]; + return ( { label={t("addMonitors")} multiple={true} isAdorned={false} - options={monitors ? monitors : []} + options={monitorOptions} filteredBy="name" secondaryLabel={"type"} inputValue={search} - value={form.monitors} - handleInputChange={handleSearch} - handleChange={handleSelectMonitors} + value={selectedMonitors} + handleInputChange={setSearch} + handleChange={handleMonitorsChange} error={errors["monitors"]} - disabled={maintenanceWindowId !== undefined} + disabled={form.isAllMonitors} + /> + + Date: Fri, 11 Jul 2025 07:59:12 -0600 Subject: [PATCH 010/106] add the translation to selectAll --- client/src/Pages/Maintenance/CreateMaintenance/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx index c4e839e02..0be00e8da 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -304,7 +304,7 @@ const CreateMaintenance = () => { }; // Add Select All option to the dropdown - const selectAllOption = { _id: "__all__", name: "Select All" }; + const selectAllOption = { _id: "__all__", name: t("selectAll") }; const monitorOptions = [selectAllOption, ...monitors]; return ( From b1b4fd3f6406fa83468a8726bfe45a35e2e9a933 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Jul 2025 11:23:36 -0700 Subject: [PATCH 011/106] add 1s cpu usage metric, return standard units of ms and bytes --- server/controllers/diagnosticController.js | 43 +++++++++++++++------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index c4223dc04..4410a28a5 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -53,15 +53,34 @@ class DiagnosticController { } } - async getSystemStats(req, res, next) { + async getCPUUsage() { + try { + const startUsage = process.cpuUsage(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + const endUsage = process.cpuUsage(startUsage); + const cpuUsage = { + userUsageMs: endUsage.user / 1000, + systemUsageMs: endUsage.system / 1000, + usagePercentage: ((endUsage.user + endUsage.system) / 1000 / 1000) * 100, + }; + return cpuUsage; + } catch (error) { + return { + userUsageMs: 0, + systemUsageMs: 0, + }; + } + } + + getSystemStats = async (req, res, next) => { try { // Memory Usage const totalMemory = os.totalmem(); const freeMemory = os.freemem(); const osStats = { - freeMemoryMb: freeMemory / 1024 / 1024, // MB - totalMemoryMb: totalMemory / 1024 / 1024, // MB + freeMemoryBytes: freeMemory, // bytes + totalMemoryBytes: totalMemory, // bytes }; const used = process.memoryUsage(); @@ -71,18 +90,14 @@ class DiagnosticController { } // CPU Usage - const cpuUsage = process.cpuUsage(); - const cpuMetrics = { - userUsageMs: cpuUsage.user / 1000, // ms - systemUsageMs: cpuUsage.system / 1000, // ms - }; + const cpuMetrics = await this.getCPUUsage(); // V8 Heap Statistics const heapStats = v8.getHeapStatistics(); const v8Metrics = { - totalHeapSizeMb: heapStats.total_heap_size / 1024 / 1024, // MB - usedHeapSizeMb: heapStats.used_heap_size / 1024 / 1024, // MB - heapSizeLimitMb: heapStats.heap_size_limit / 1024 / 1024, // MB + totalHeapSizeBytes: heapStats.total_heap_size, // bytes + usedHeapSizeBytes: heapStats.used_heap_size, // bytes + heapSizeLimitBytes: heapStats.heap_size_limit, // bytes }; // Event Loop Delay @@ -97,7 +112,7 @@ class DiagnosticController { } // Uptime - const uptime = process.uptime(); // seconds + const uptimeMs = process.uptime() * 1000; // ms // Combine Metrics const diagnostics = { @@ -106,7 +121,7 @@ class DiagnosticController { cpuUsage: cpuMetrics, v8HeapStats: v8Metrics, eventLoopDelayMs: eventLoopDelay, - uptimeSeconds: uptime, + uptimeMs, }; return res.success({ @@ -116,6 +131,6 @@ class DiagnosticController { } catch (error) { next(handleError(error, SERVICE_NAME, "getMemoryUsage")); } - } + }; } export default DiagnosticController; From 5b395b96cd5f7001dee9d62c5a20e03471b6993f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Jul 2025 11:23:50 -0700 Subject: [PATCH 012/106] implemet stats --- .../Components/Charts/CustomGauge/index.jsx | 35 ++++++- .../Diagnostics/components/gauges/index.jsx | 73 ++++++++++---- .../Diagnostics/components/gauges/utils.js | 4 - .../Diagnostics/components/stats/index.jsx | 97 +++++++++++++++++++ client/src/Pages/Logs/Diagnostics/index.jsx | 20 +++- .../src/Pages/Logs/Diagnostics/utils/utils.js | 16 +++ client/src/Pages/Logs/Logs/index.jsx | 4 + .../Logs/Queue/components/Metrics/index.jsx | 27 +++--- client/src/Pages/Logs/Queue/index.jsx | 69 +++++++------ client/src/Utils/timeUtils.js | 4 + client/src/locales/en.json | 22 ++++- 11 files changed, 294 insertions(+), 77 deletions(-) delete mode 100644 client/src/Pages/Logs/Diagnostics/components/gauges/utils.js create mode 100644 client/src/Pages/Logs/Diagnostics/components/stats/index.jsx create mode 100644 client/src/Pages/Logs/Diagnostics/utils/utils.js diff --git a/client/src/Components/Charts/CustomGauge/index.jsx b/client/src/Components/Charts/CustomGauge/index.jsx index 359b5b328..d9718d72c 100644 --- a/client/src/Components/Charts/CustomGauge/index.jsx +++ b/client/src/Components/Charts/CustomGauge/index.jsx @@ -1,8 +1,10 @@ import { useTheme } from "@emotion/react"; import { useEffect, useState, useMemo } from "react"; +import Stack from "@mui/material/Stack"; import { Box, Typography } from "@mui/material"; import PropTypes from "prop-types"; import "./index.css"; +import CircularProgress from "@mui/material/CircularProgress"; const MINIMUM_VALUE = 0; const MAXIMUM_VALUE = 100; @@ -16,6 +18,8 @@ const MAXIMUM_VALUE = 100; * @param {number} [props.radius=60] - Radius of the gauge circle * @param {number} [props.strokeWidth=15] - Width of the gauge stroke * @param {number} [props.threshold=50] - Threshold for color change + * @param {number} [props.precision=1] - Precision of the progress percentage + * @param {string} [props.unit="%"] - Unit of progress * * @example * { +const CustomGauge = ({ + isLoading = false, + progress = 0, + radius = 70, + strokeWidth = 15, + threshold = 50, + precision = 1, + unit = "%", +}) => { const theme = useTheme(); // Calculate the length of the stroke for the circle const { circumference, totalSize, strokeLength } = useMemo( @@ -57,6 +69,20 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold = ? theme.palette.error.lowContrast : theme.palette.accent.main; + if (isLoading) { + return ( + + + + ); + } + return ( - {`${progressWithinRange.toFixed(1)}%`} + {`${progressWithinRange.toFixed(precision)}${unit}`} ); @@ -112,8 +138,11 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold = export default CustomGauge; CustomGauge.propTypes = { + isLoading: PropTypes.bool, progress: PropTypes.number, radius: PropTypes.number, strokeWidth: PropTypes.number, threshold: PropTypes.number, + precision: PropTypes.number, + unit: PropTypes.string, }; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx index 21d9a35d8..e9f235112 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -1,12 +1,12 @@ import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; import Gauge from "../../../../../Components/Charts/CustomGauge"; import Typography from "@mui/material/Typography"; // Utils import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getPercentage } from "./utils"; +import { getPercentage } from "../../utils/utils"; +import { useTranslation } from "react-i18next"; const GaugeBox = ({ title, subtitle, children }) => { const theme = useTheme(); @@ -14,6 +14,8 @@ const GaugeBox = ({ title, subtitle, children }) => { {children} @@ -23,52 +25,85 @@ const GaugeBox = ({ title, subtitle, children }) => { ); }; -const Gauges = ({ diagnostics }) => { +GaugeBox.propTypes = { + title: PropTypes.string.isRequired, + subtitle: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, +}; + +const Gauges = ({ diagnostics, isLoading }) => { const heapTotalSize = getPercentage( - diagnostics?.v8HeapStats?.totalHeapSizeMb, - diagnostics?.v8HeapStats?.heapSizeLimitMb + diagnostics?.v8HeapStats?.totalHeapSizeBytes, + diagnostics?.v8HeapStats?.heapSizeLimitBytes ); const heapUsedSize = getPercentage( - diagnostics?.v8HeapStats?.usedHeapSizeMb, - diagnostics?.v8HeapStats?.heapSizeLimitMb + diagnostics?.v8HeapStats?.usedHeapSizeBytes, + diagnostics?.v8HeapStats?.heapSizeLimitBytes ); const actualHeapUsed = getPercentage( - diagnostics?.v8HeapStats?.usedHeapSizeMb, - diagnostics?.v8HeapStats?.totalHeapSizeMb + diagnostics?.v8HeapStats?.usedHeapSizeBytes, + diagnostics?.v8HeapStats?.totalHeapSizeBytes ); + const theme = useTheme(); + const { t } = useTranslation(); return ( - + - + - + + + + ); }; Gauges.propTypes = { - diagnostics: PropTypes.object.isRequired, + diagnostics: PropTypes.object, + isLoading: PropTypes.bool, }; export default Gauges; diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js b/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js deleted file mode 100644 index bdc570a3b..000000000 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/utils.js +++ /dev/null @@ -1,4 +0,0 @@ -export const getPercentage = (value, total) => { - if (!value || !total) return 0; - return (value / total) * 100; -}; diff --git a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx b/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx new file mode 100644 index 000000000..103d1c698 --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx @@ -0,0 +1,97 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CircularProgress from "@mui/material/CircularProgress"; + +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; + +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; +import { formatBytes } from "../../utils/utils"; +import { useTranslation } from "react-i18next"; + +const StatsCard = ({ title, value, unit = "", isLoading }) => { + const theme = useTheme(); + return ( + + {isLoading ? ( + + + + ) : ( + + + {title} + + + {value} {unit} + + + )} + + ); +}; + +StatsCard.propTypes = { + title: PropTypes.string, + value: PropTypes.string, + unit: PropTypes.string, + isLoading: PropTypes.bool, +}; + +const Stats = ({ diagnostics, isLoading }) => { + const theme = useTheme(); + const { t } = useTranslation(); + return ( + + + + + + + + + + + ); +}; + +Stats.propTypes = { + diagnostics: PropTypes.object, + isLoading: PropTypes.bool, +}; + +export default Stats; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx index 9d7ee4639..e000c05bc 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -2,6 +2,8 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Gauges from "./components/gauges"; +import Stats from "./components/stats"; +import Divider from "@mui/material/Divider"; import { useTheme } from "@emotion/react"; import { useState } from "react"; @@ -15,14 +17,26 @@ const Diagnostics = () => { const theme = useTheme(); const { t } = useTranslation(); const [diagnostics, isLoading, error] = useFetchDiagnostics(); - console.log(diagnostics); // Setup return ( - {t("diagnosticsPage.description")} + {t("diagnosticsPage.diagnosticDescription")} - + + + + + ); }; diff --git a/client/src/Pages/Logs/Diagnostics/utils/utils.js b/client/src/Pages/Logs/Diagnostics/utils/utils.js new file mode 100644 index 000000000..a3b75631f --- /dev/null +++ b/client/src/Pages/Logs/Diagnostics/utils/utils.js @@ -0,0 +1,16 @@ +export const getPercentage = (value, total) => { + if (!value || !total) return 0; + return (value / total) * 100; +}; + +export const formatBytes = (bytes) => { + if (!bytes) return "N/A"; + + if (bytes === 0) return "0 Bytes"; + + if (bytes >= 1024 * 1024 * 1024) { + return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; + } + + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; +}; diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx index d8bd323a3..b65f9c0fd 100644 --- a/client/src/Pages/Logs/Logs/index.jsx +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -2,6 +2,7 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Select from "../../../Components/Inputs/Select"; import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; import { useFetchLogs } from "../../../Hooks/logHooks"; import { useTheme } from "@emotion/react"; @@ -53,10 +54,13 @@ const Logs = () => { {t("logsPage.description")} + + {t("logsPage.logLevelSelect.title")} tz._id === form.timezone) || null} + inputValue={timezoneValue} + handleInputChange={(newVal) => setTimezoneValue(newVal)} + handleChange={(newValue) => { + setTimezoneValue(""); + handleFormChange({ + target: { name: "timezone", value: newValue?._id || "" }, + }); + }} + isAdorned={false} /> From a7e26d553ff0d1a41aa6f37f0dc64b130c5c6bac Mon Sep 17 00:00:00 2001 From: Yashaswee Kesharwani Date: Sat, 12 Jul 2025 23:22:42 +0530 Subject: [PATCH 014/106] chore: format code using npm run format --- client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx index 5ed9bb122..de47cfc78 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx @@ -16,7 +16,6 @@ import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useState } from "react"; - const TabSettings = ({ isCreate, tabValue, From 39d666df6b51c7c779adc1f27b0af794ba8d4f92 Mon Sep 17 00:00:00 2001 From: shanika Jayawardane Date: Sat, 12 Jul 2025 15:13:24 -0600 Subject: [PATCH 015/106] add the checkboxes for dropdown list and remove searchbox list inside it --- .../Maintenance/CreateMaintenance/index.jsx | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx index 0be00e8da..19cb5b16d 100644 --- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -222,23 +222,14 @@ const CreateMaintenance = () => { }; const handleMonitorsChange = (selected) => { - if (selected.some((m) => m._id === "__all__")) { - if (selectedMonitors.length !== monitors.length) { - setSelectedMonitors(monitors); - } else { - setSelectedMonitors([]); - } - } else { - // Ensure all are objects - const selectedObjs = selected - .map((sel) => - typeof sel === "string" ? monitors.find((m) => m._id === sel) : sel - ) - .filter(Boolean); - setSelectedMonitors(selectedObjs); - } + // Ensure all are objects + const selectedObjs = selected + .map((sel) => (typeof sel === "string" ? monitors.find((m) => m._id === sel) : sel)) + .filter(Boolean); + setSelectedMonitors(selectedObjs); + const { error } = maintenanceWindowValidation.validate( - { monitors: selected }, + { monitors: selectedObjs }, { abortEarly: false } ); setErrors((prev) => { @@ -246,8 +237,6 @@ const CreateMaintenance = () => { }); }; - const allSelected = selectedMonitors.length === monitors.length && monitors.length > 0; - const handleSubmit = async () => { if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return; // Build timestamp for maintenance window from startDate and startTime @@ -303,10 +292,6 @@ const CreateMaintenance = () => { } }; - // Add Select All option to the dropdown - const selectAllOption = { _id: "__all__", name: t("selectAll") }; - const monitorOptions = [selectAllOption, ...monitors]; - return ( { id={"monitors"} label={t("addMonitors")} multiple={true} - isAdorned={false} - options={monitorOptions} + isAdorned={true} + options={monitors} filteredBy="name" secondaryLabel={"type"} inputValue={search} From 2bb78181163b4d445895632d8a3beb92827ecc42 Mon Sep 17 00:00:00 2001 From: Yashaswee Kesharwani Date: Sun, 13 Jul 2025 03:52:16 +0530 Subject: [PATCH 016/106] fix: replaced remaining Selects with Search in Settings Tab - optimised with useMemo and standardised the no unit found --- client/src/Components/Inputs/Search/index.jsx | 9 ++++- .../src/Pages/Settings/SettingsTimeZone.jsx | 40 +++++++++++++++---- .../Create/Components/Tabs/Settings.jsx | 36 ++++++++++++----- client/src/locales/en.json | 4 +- 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/client/src/Components/Inputs/Search/index.jsx b/client/src/Components/Inputs/Search/index.jsx index 4624b2cba..f536bd5b5 100644 --- a/client/src/Components/Inputs/Search/index.jsx +++ b/client/src/Components/Inputs/Search/index.jsx @@ -24,6 +24,7 @@ import { useTranslation } from "react-i18next"; * @param {Function} props.handleChange - Function to call when the input changes * @param {Function} Prop.onBlur - Function to call when the input is blured * @param {Object} props.sx - Additional styles to apply to the component + * @param {string} props.unit - Label to identify type of options * @returns {JSX.Element} The rendered Search component */ @@ -68,6 +69,7 @@ const Search = ({ startAdornment, endAdornment, onBlur, + unit = "option", }) => { const theme = useTheme(); const { t } = useTranslation(); @@ -186,7 +188,12 @@ const Search = ({ ); if (filtered.length === 0) { - return [{ [filteredBy]: "No monitors found", noOptions: true }]; + return [ + { + [filteredBy]: t("general.noOptionsFound", { unit: unit }), + noOptions: true, + }, + ]; } return filtered; }} diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx index 346e4e546..4b17f3f81 100644 --- a/client/src/Pages/Settings/SettingsTimeZone.jsx +++ b/client/src/Pages/Settings/SettingsTimeZone.jsx @@ -2,16 +2,37 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import ConfigBox from "../../Components/ConfigBox"; -import Select from "../../Components/Inputs/Select"; +import Search from "../../Components/Inputs/Search"; import timezones from "../../Utils/timezones.json"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; import { useTranslation } from "react-i18next"; +import { useCallback, useMemo, useState } from "react"; const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => { const theme = useTheme(); const { t } = useTranslation(); + const [rawInput, setRawInput] = useState(""); + + const selectedTimezone = useMemo( + () => timezones.find((tz) => tz._id === timezone) ?? null, + [timezone, timezones] + ); + + const handleTimezoneChange = useCallback( + (newValue) => { + setRawInput(""); + handleChange({ + target: { + name: "timezone", + value: newValue?._id ?? "", + }, + }); + }, + [handleChange] + ); + return ( @@ -28,12 +49,17 @@ const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => { - @@ -445,8 +445,8 @@ const Configure = () => { name="expectedValue" label={t("expectedValue")} isOptional={true} - placeholder={expectedValuePlaceholders[form.matchMethod || "equal"]} - value={form.expectedValue} + placeholder={expectedValuePlaceholders[form?.matchMethod || "equal"]} + value={form?.expectedValue} onChange={onChange} error={errors["expectedValue"] ? true : false} helperText={errors["expectedValue"]} @@ -466,7 +466,7 @@ const Configure = () => { label="JSON Path" isOptional={true} placeholder="data.status" - value={form.jsonPath} + value={form?.jsonPath} onChange={onChange} error={errors["jsonPath"] ? true : false} helperText={errors["jsonPath"]} diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 3abd726da..59e2a0a21 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -111,30 +111,6 @@ class NetworkService { return this.axiosInstance.post(`/monitors`, monitor); } - /** - * - * ************************************ - * Check the endpoint resolution - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {Object} config.monitorURL - The monitor url to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - */ - async checkEndpointResolution(config) { - const { monitorURL } = config; - const params = new URLSearchParams(); - - if (monitorURL) params.append("monitorURL", monitorURL); - - return this.axiosInstance.get(`/monitors/resolution/url?${params.toString()}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - /** * ************************************ * Get all uptime monitors for a Team From ddf3e8251d7bdc44dd033cde214a6e3c39a46a37 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 24 Jul 2025 15:06:46 -0700 Subject: [PATCH 102/106] add an error service, base controller --- server/controllers/authController.js | 30 ++-- server/controllers/baseController.js | 8 + server/controllers/checkController.js | 25 +-- server/controllers/controllerUtils.js | 4 +- server/controllers/diagnosticController.js | 12 +- server/controllers/inviteController.js | 18 +- server/controllers/logController.js | 13 +- .../maintenanceWindowController.js | 33 ++-- server/controllers/monitorController.js | 87 +++++----- server/controllers/notificationController.js | 76 ++++----- server/controllers/queueController.js | 33 ++-- server/controllers/settingsController.js | 29 ++-- server/controllers/statusPageController.js | 36 ++-- server/index.js | 35 +++- server/middleware/handleErrors.js | 1 - server/service/business/inviteService.js | 6 +- server/service/business/monitorService.js | 18 +- server/service/business/userService.js | 8 +- server/service/infrastructure/errorService.js | 161 ++++++++++++++++++ server/utils/errorUtils.js | 80 --------- 20 files changed, 412 insertions(+), 301 deletions(-) create mode 100644 server/controllers/baseController.js create mode 100644 server/service/infrastructure/errorService.js diff --git a/server/controllers/authController.js b/server/controllers/authController.js index 12d2f77f4..8dac9f028 100755 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -1,3 +1,4 @@ +import BaseController from "./baseController.js"; import { registrationBodyValidation, loginValidation, @@ -10,7 +11,6 @@ import { editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, } from "../validation/joi.js"; -import { asyncHandler, createError } from "../utils/errorUtils.js"; const SERVICE_NAME = "authController"; @@ -23,7 +23,7 @@ const SERVICE_NAME = "authController"; * @class AuthController * @description Manages user authentication and authorization operations */ -class AuthController { +class AuthController extends BaseController { /** * Creates an instance of AuthController. * @@ -36,7 +36,8 @@ class AuthController { * @param {Object} dependencies.logger - Logger service * @param {Object} dependencies.userService - User business logic service */ - constructor({ db, settingsService, emailService, jobQueue, stringService, logger, userService }) { + constructor({ db, settingsService, emailService, jobQueue, stringService, logger, userService, errorService }) { + super(); this.db = db; this.settingsService = settingsService; this.emailService = emailService; @@ -44,6 +45,7 @@ class AuthController { this.stringService = stringService; this.logger = logger; this.userService = userService; + this.errorService = errorService; } /** @@ -85,7 +87,7 @@ class AuthController { * "inviteToken": "abc123..." * } */ - registerUser = asyncHandler( + registerUser = this.asyncHandler( async (req, res) => { if (req.body?.email) { req.body.email = req.body.email?.toLowerCase(); @@ -121,7 +123,7 @@ class AuthController { * "password": "SecurePass123!" * } */ - loginUser = asyncHandler( + loginUser = this.asyncHandler( async (req, res) => { if (req.body?.email) { req.body.email = req.body.email?.toLowerCase(); @@ -173,7 +175,7 @@ class AuthController { * "newPassword": "NewPass123!" * } */ - editUser = asyncHandler( + editUser = this.asyncHandler( async (req, res) => { await editUserBodyValidation.validateAsync(req.body); @@ -200,7 +202,7 @@ class AuthController { * GET /auth/users/superadmin * // Response: { "data": true } or { "data": false } */ - checkSuperadminExists = asyncHandler( + checkSuperadminExists = this.asyncHandler( async (req, res) => { const superAdminExists = await this.userService.checkSuperadminExists(); return res.success({ @@ -230,7 +232,7 @@ class AuthController { * "email": "john@example.com" * } */ - requestRecovery = asyncHandler( + requestRecovery = this.asyncHandler( async (req, res) => { await recoveryValidation.validateAsync(req.body); const email = req?.body?.email; @@ -262,7 +264,7 @@ class AuthController { * "recoveryToken": "abc123..." * } */ - validateRecovery = asyncHandler( + validateRecovery = this.asyncHandler( async (req, res) => { await recoveryTokenBodyValidation.validateAsync(req.body); await this.userService.validateRecovery(req.body.recoveryToken); @@ -294,7 +296,7 @@ class AuthController { * "recoveryToken": "abc123..." * } */ - resetPassword = asyncHandler( + resetPassword = this.asyncHandler( async (req, res) => { await newPasswordValidation.validateAsync(req.body); const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); @@ -326,7 +328,7 @@ class AuthController { * DELETE /auth/user * // Requires JWT authentication */ - deleteUser = asyncHandler( + deleteUser = this.asyncHandler( async (req, res) => { await this.userService.deleteUser(req.user); return res.success({ @@ -350,7 +352,7 @@ class AuthController { * GET /auth/users * // Requires JWT authentication with admin/superadmin role */ - getAllUsers = asyncHandler( + getAllUsers = this.asyncHandler( async (req, res) => { const allUsers = await this.userService.getAllUsers(); return res.success({ @@ -381,7 +383,7 @@ class AuthController { * GET /auth/users/507f1f77bcf86cd799439011 * // Requires JWT authentication with superadmin role */ - getUserById = asyncHandler( + getUserById = this.asyncHandler( async (req, res) => { await getUserByIdParamValidation.validateAsync(req.params); const userId = req?.params?.userId; @@ -431,7 +433,7 @@ class AuthController { * } * // Requires JWT authentication with superadmin role */ - editUserById = asyncHandler( + editUserById = this.asyncHandler( async (req, res) => { const roles = req?.user?.role; if (!roles.includes("superadmin")) { diff --git a/server/controllers/baseController.js b/server/controllers/baseController.js new file mode 100644 index 000000000..81df59deb --- /dev/null +++ b/server/controllers/baseController.js @@ -0,0 +1,8 @@ +import { asyncHandler } from "../service/infrastructure/errorService.js"; + +class BaseController { + constructor() { + this.asyncHandler = asyncHandler; + } +} +export default BaseController; diff --git a/server/controllers/checkController.js b/server/controllers/checkController.js index 304f89c6d..693f5a32d 100755 --- a/server/controllers/checkController.js +++ b/server/controllers/checkController.js @@ -1,3 +1,4 @@ +import BaseController from "./baseController.js"; import { getChecksParamValidation, getChecksQueryValidation, @@ -9,7 +10,6 @@ import { ackAllChecksParamValidation, ackAllChecksBodyValidation, } from "../validation/joi.js"; -import { asyncHandler } from "../utils/errorUtils.js"; const SERVICE_NAME = "checkController"; @@ -22,7 +22,7 @@ const SERVICE_NAME = "checkController"; * @class CheckController * @description Manages check operations and monitoring data */ -class CheckController { +class CheckController extends BaseController { /** * Creates an instance of CheckController. * @@ -31,12 +31,15 @@ class CheckController { * @param {Object} dependencies.settingsService - Service for application settings * @param {Object} dependencies.stringService - Service for string/localization * @param {Object} dependencies.checkService - Check business logic service + * @param {Object} dependencies.errorService - Error service for error handling */ - constructor({ db, settingsService, stringService, checkService }) { + constructor({ db, settingsService, stringService, checkService, errorService }) { + super(); this.db = db; this.settingsService = settingsService; this.stringService = stringService; this.checkService = checkService; + this.errorService = errorService; } /** @@ -67,7 +70,7 @@ class CheckController { * GET /checks/monitor/507f1f77bcf86cd799439011?page=1&rowsPerPage=10&status=down * // Requires JWT authentication */ - getChecksByMonitor = asyncHandler( + getChecksByMonitor = this.asyncHandler( async (req, res) => { await getChecksParamValidation.validateAsync(req.params); await getChecksQueryValidation.validateAsync(req.query); @@ -109,7 +112,7 @@ class CheckController { * GET /checks/team?page=1&rowsPerPage=20&status=down&ack=false * // Requires JWT authentication */ - getChecksByTeam = asyncHandler( + getChecksByTeam = this.asyncHandler( async (req, res) => { await getTeamChecksQueryValidation.validateAsync(req.query); const checkData = await this.checkService.getChecksByTeam({ @@ -140,7 +143,7 @@ class CheckController { * // Requires JWT authentication * // Response includes counts by status, time ranges, etc. */ - getChecksSummaryByTeamId = asyncHandler( + getChecksSummaryByTeamId = this.asyncHandler( async (req, res) => { const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId }); return res.success({ @@ -176,7 +179,7 @@ class CheckController { * } * // Requires JWT authentication */ - ackCheck = asyncHandler( + ackCheck = this.asyncHandler( async (req, res) => { await ackCheckBodyValidation.validateAsync(req.body); @@ -220,7 +223,7 @@ class CheckController { * } * // Requires JWT authentication */ - ackAllChecks = asyncHandler( + ackAllChecks = this.asyncHandler( async (req, res) => { await ackAllChecksParamValidation.validateAsync(req.params); await ackAllChecksBodyValidation.validateAsync(req.body); @@ -266,7 +269,7 @@ class CheckController { * // Requires JWT authentication * // Response: { "data": { "deletedCount": 150 } } */ - deleteChecks = asyncHandler( + deleteChecks = this.asyncHandler( async (req, res) => { await deleteChecksParamValidation.validateAsync(req.params); @@ -300,7 +303,7 @@ class CheckController { * // Requires JWT authentication * // Response: { "data": { "deletedCount": 1250 } } */ - deleteChecksByTeamId = asyncHandler( + deleteChecksByTeamId = this.asyncHandler( async (req, res) => { await deleteChecksByTeamIdParamValidation.validateAsync(req.params); @@ -336,7 +339,7 @@ class CheckController { * // Requires JWT authentication * // Sets check TTL to 30 days */ - updateChecksTTL = asyncHandler( + updateChecksTTL = this.asyncHandler( async (req, res) => { await updateChecksTTLBodyValidation.validateAsync(req.body); diff --git a/server/controllers/controllerUtils.js b/server/controllers/controllerUtils.js index 2b3a4198c..f21ba154b 100755 --- a/server/controllers/controllerUtils.js +++ b/server/controllers/controllerUtils.js @@ -1,12 +1,10 @@ -import { createServerError } from "../utils/errorUtils.js"; - const fetchMonitorCertificate = async (sslChecker, monitor) => { const monitorUrl = new URL(monitor.url); const hostname = monitorUrl.hostname; const cert = await sslChecker(hostname); // Throw an error if no cert or if cert.validTo is not present if (cert?.validTo === null || cert?.validTo === undefined) { - throw createServerError("Certificate not found"); + throw new Error("Certificate not found"); } return cert; }; diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index 585684211..308755109 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -1,6 +1,5 @@ -import { asyncHandler } from "../utils/errorUtils.js"; - const SERVICE_NAME = "diagnosticController"; +import BaseController from "./baseController.js"; /** * Diagnostic Controller * @@ -10,15 +9,18 @@ const SERVICE_NAME = "diagnosticController"; * @class DiagnosticController * @description Manages system diagnostics and performance monitoring */ -class DiagnosticController { +class DiagnosticController extends BaseController { /** * Creates an instance of DiagnosticController. * * @param {Object} dependencies - The dependencies required by the controller * @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring + * @param {Object} dependencies.errorService - Service for error handling */ - constructor({ diagnosticService }) { + constructor({ diagnosticService, errorService }) { + super(); this.diagnosticService = diagnosticService; + this.errorService = errorService; } /** @@ -41,7 +43,7 @@ class DiagnosticController { * // - Database connection status * // - Active processes/connections */ - getSystemStats = asyncHandler( + getSystemStats = this.asyncHandler( async (req, res) => { const diagnostics = await this.diagnosticService.getSystemStats(); return res.success({ diff --git a/server/controllers/inviteController.js b/server/controllers/inviteController.js index 75e98cb68..4cf7ec8ed 100755 --- a/server/controllers/inviteController.js +++ b/server/controllers/inviteController.js @@ -1,22 +1,24 @@ import { inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js"; -import { asyncHandler } from "../utils/errorUtils.js"; - +import BaseController from "./baseController.js"; const SERVICE_NAME = "inviteController"; /** * Controller for handling user invitation operations * Manages invite token generation, email sending, and token verification */ -class InviteController { +class InviteController extends BaseController { /** * Creates a new InviteController instance * @param {Object} dependencies - Dependencies injected into the controller * @param {Object} dependencies.stringService - Service for internationalized strings - * @param {Object} dependencies.inviteService - Service for invite-related operations + * @param {Object} dependencies.inviteServ ice - Service for invite-related operations + * @param {Object} dependencies.errorService - Service for error handling */ - constructor({ stringService, inviteService }) { + constructor({ stringService, inviteService, errorService }) { + super(); this.stringService = stringService; this.inviteService = inviteService; + this.errorService = errorService; } /** @@ -28,7 +30,7 @@ class InviteController { * @param {Object} res - Express response object * @returns {Promise} Response with invite token data */ - getInviteToken = asyncHandler( + getInviteToken = this.asyncHandler( async (req, res) => { const invite = req.body; const teamId = req?.user?.teamId; @@ -54,7 +56,7 @@ class InviteController { * @param {Object} res - Express response object * @returns {Promise} Response with invite token data */ - sendInviteEmail = asyncHandler( + sendInviteEmail = this.asyncHandler( async (req, res) => { const inviteRequest = req.body; inviteRequest.teamId = req?.user?.teamId; @@ -81,7 +83,7 @@ class InviteController { * @param {Object} res - Express response object * @returns {Promise} Response with verified invite data */ - verifyInviteToken = asyncHandler( + verifyInviteToken = this.asyncHandler( async (req, res) => { await inviteVerificationBodyValidation.validateAsync(req.body); const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token }); diff --git a/server/controllers/logController.js b/server/controllers/logController.js index f4887ce9a..14f7a6d58 100644 --- a/server/controllers/logController.js +++ b/server/controllers/logController.js @@ -1,14 +1,15 @@ -import { asyncHandler } from "../utils/errorUtils.js"; - +import BaseController from "./baseController.js"; const SERVICE_NAME = "LogController"; -class LogController { - constructor(logger) { +class LogController extends BaseController { + constructor({ logger, errorService }) { + super(); this.logger = logger; + this.errorService = errorService; } - getLogs = asyncHandler( - async (req, res, next) => { + getLogs = this.asyncHandler( + async (req, res) => { const logs = await this.logger.getLogs(); res.success({ msg: "Logs fetched successfully", diff --git a/server/controllers/maintenanceWindowController.js b/server/controllers/maintenanceWindowController.js index 95349fa66..59ea10140 100755 --- a/server/controllers/maintenanceWindowController.js +++ b/server/controllers/maintenanceWindowController.js @@ -7,25 +7,28 @@ import { getMaintenanceWindowsByTeamIdQueryValidation, deleteMaintenanceWindowByIdParamValidation, } from "../validation/joi.js"; -import { asyncHandler } from "../utils/errorUtils.js"; +import BaseController from "./baseController.js"; const SERVICE_NAME = "maintenanceWindowController"; -class MaintenanceWindowController { - constructor({ db, settingsService, stringService, maintenanceWindowService }) { +class MaintenanceWindowController extends BaseController { + constructor({ db, settingsService, stringService, maintenanceWindowService, errorService }) { + super(); this.db = db; this.settingsService = settingsService; this.stringService = stringService; this.maintenanceWindowService = maintenanceWindowService; + this.errorService = errorService; + this.asyncHandler = errorService.asyncHandler; } - createMaintenanceWindows = asyncHandler( + createMaintenanceWindows = this.asyncHandler( async (req, res) => { await createMaintenanceWindowBodyValidation.validateAsync(req.body); const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body }); @@ -38,13 +41,13 @@ class MaintenanceWindowController { "createMaintenanceWindows" ); - getMaintenanceWindowById = asyncHandler( + getMaintenanceWindowById = this.asyncHandler( async (req, res) => { await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); const teamId = req.user.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId }); @@ -58,14 +61,14 @@ class MaintenanceWindowController { "getMaintenanceWindowById" ); - getMaintenanceWindowsByTeamId = asyncHandler( + getMaintenanceWindowsByTeamId = this.asyncHandler( async (req, res) => { await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query); const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query }); @@ -79,13 +82,13 @@ class MaintenanceWindowController { "getMaintenanceWindowsByTeamId" ); - getMaintenanceWindowsByMonitorId = asyncHandler( + getMaintenanceWindowsByMonitorId = this.asyncHandler( async (req, res) => { await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params); const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId }); @@ -99,13 +102,13 @@ class MaintenanceWindowController { "getMaintenanceWindowsByMonitorId" ); - deleteMaintenanceWindow = asyncHandler( + deleteMaintenanceWindow = this.asyncHandler( async (req, res) => { await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params); const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId }); @@ -118,14 +121,14 @@ class MaintenanceWindowController { "deleteMaintenanceWindow" ); - editMaintenanceWindow = asyncHandler( + editMaintenanceWindow = this.asyncHandler( async (req, res) => { await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); const teamId = req.user.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId }); diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 0f0dd3e76..4e2c021d2 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -13,30 +13,31 @@ import { getHardwareDetailsByIdQueryValidation, } from "../validation/joi.js"; import sslChecker from "ssl-checker"; -import { asyncHandler } from "../utils/errorUtils.js"; import { fetchMonitorCertificate } from "./controllerUtils.js"; +import BaseController from "./baseController.js"; const SERVICE_NAME = "monitorController"; -class MonitorController { - constructor({ db, settingsService, jobQueue, stringService, emailService, monitorService }) { +class MonitorController extends BaseController { + constructor({ db, settingsService, jobQueue, stringService, emailService, monitorService, errorService }) { + super(); this.db = db; this.settingsService = settingsService; this.jobQueue = jobQueue; this.stringService = stringService; this.emailService = emailService; this.monitorService = monitorService; + this.errorService = errorService; + this.asyncHandler = errorService.asyncHandler; } async verifyTeamAccess(teamId, monitorId) { const monitor = await this.db.getMonitorById(monitorId); if (!monitor.teamId.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; + throw this.errorService.createAuthorizationError(); } } - getAllMonitors = asyncHandler( + getAllMonitors = this.asyncHandler( async (req, res) => { const monitors = await this.monitorService.getAllMonitors(); return res.success({ @@ -48,7 +49,7 @@ class MonitorController { "getAllMonitors" ); - getUptimeDetailsById = asyncHandler( + getUptimeDetailsById = this.asyncHandler( async (req, res) => { const monitorId = req?.params?.monitorId; const dateRange = req?.query?.dateRange; @@ -57,7 +58,7 @@ class MonitorController { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const data = await this.monitorService.getUptimeDetailsById({ @@ -75,7 +76,7 @@ class MonitorController { "getUptimeDetailsById" ); - getMonitorStatsById = asyncHandler( + getMonitorStatsById = this.asyncHandler( async (req, res) => { await getMonitorStatsByIdParamValidation.validateAsync(req.params); await getMonitorStatsByIdQueryValidation.validateAsync(req.query); @@ -85,7 +86,7 @@ class MonitorController { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const monitorStats = await this.monitorService.getMonitorStatsById({ @@ -116,7 +117,7 @@ class MonitorController { * @returns {Promise} * @throws {Error} - Throws error if monitor not found or other database errors */ - getHardwareDetailsById = asyncHandler( + getHardwareDetailsById = this.asyncHandler( async (req, res) => { await getHardwareDetailsByIdParamValidation.validateAsync(req.params); await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); @@ -125,7 +126,7 @@ class MonitorController { const dateRange = req?.query?.dateRange; const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const monitor = await this.monitorService.getHardwareDetailsById({ @@ -143,8 +144,8 @@ class MonitorController { "getHardwareDetailsById" ); - getMonitorCertificate = asyncHandler( - async (req, res, next) => { + getMonitorCertificate = this.asyncHandler( + async (req, res) => { await getCertificateParamValidation.validateAsync(req.params); const { monitorId } = req.params; @@ -162,14 +163,14 @@ class MonitorController { "getMonitorCertificate" ); - getMonitorById = asyncHandler( + getMonitorById = this.asyncHandler( async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); await getMonitorByIdQueryValidation.validateAsync(req.query); const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId }); @@ -183,7 +184,7 @@ class MonitorController { "getMonitorById" ); - createMonitor = asyncHandler( + createMonitor = this.asyncHandler( async (req, res) => { await createMonitorBodyValidation.validateAsync(req.body); @@ -201,30 +202,30 @@ class MonitorController { "createMonitor" ); - createBulkMonitors = asyncHandler( + createBulkMonitors = this.asyncHandler( async (req, res) => { if (!req.file) { - throw new Error("No file uploaded"); + throw this.errorService.createBadRequestError("No file uploaded"); } if (!req.file.mimetype.includes("csv")) { - throw new Error("File is not a CSV"); + throw this.errorService.createBadRequestError("File is not a CSV"); } if (req.file.size === 0) { - throw new Error("File is empty"); + throw this.errorService.createBadRequestError("File is empty"); } const userId = req?.user?._id; const teamId = req?.user?.teamId; if (!userId || !teamId) { - throw new Error("Missing userId or teamId"); + throw this.errorService.createBadRequestError("Missing userId or teamId"); } const fileData = req?.file?.buffer?.toString("utf-8"); if (!fileData) { - throw new Error("Cannot get file from buffer"); + throw this.errorService.createBadRequestError("Cannot get file from buffer"); } const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId }); @@ -238,13 +239,13 @@ class MonitorController { "createBulkMonitors" ); - deleteMonitor = asyncHandler( + deleteMonitor = this.asyncHandler( async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); const monitorId = req.params.monitorId; const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId }); @@ -255,11 +256,11 @@ class MonitorController { "deleteMonitor" ); - deleteAllMonitors = asyncHandler( + deleteAllMonitors = this.asyncHandler( async (req, res) => { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const deletedCount = await this.monitorService.deleteAllMonitors({ teamId }); @@ -270,7 +271,7 @@ class MonitorController { "deleteAllMonitors" ); - editMonitor = asyncHandler( + editMonitor = this.asyncHandler( async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); await editMonitorBodyValidation.validateAsync(req.body); @@ -278,7 +279,7 @@ class MonitorController { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body }); @@ -292,14 +293,14 @@ class MonitorController { "editMonitor" ); - pauseMonitor = asyncHandler( + pauseMonitor = this.asyncHandler( async (req, res) => { await pauseMonitorParamValidation.validateAsync(req.params); const monitorId = req.params.monitorId; const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId }); @@ -313,7 +314,7 @@ class MonitorController { "pauseMonitor" ); - addDemoMonitors = asyncHandler( + addDemoMonitors = this.asyncHandler( async (req, res) => { const { _id, teamId } = req.user; const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId }); @@ -327,11 +328,11 @@ class MonitorController { "addDemoMonitors" ); - sendTestEmail = asyncHandler( + sendTestEmail = this.asyncHandler( async (req, res) => { const { to } = req.body; if (!to || typeof to !== "string") { - throw new Error(this.stringService.errorForValidEmailAddress); + throw this.errorService.createBadRequestError(this.stringService.errorForValidEmailAddress); } const messageId = await this.monitorService.sendTestEmail({ to }); @@ -344,8 +345,8 @@ class MonitorController { "sendTestEmail" ); - getMonitorsByTeamId = asyncHandler( - async (req, res, next) => { + getMonitorsByTeamId = this.asyncHandler( + async (req, res) => { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); @@ -363,7 +364,7 @@ class MonitorController { "getMonitorsByTeamId" ); - getMonitorsAndSummaryByTeamId = asyncHandler( + getMonitorsAndSummaryByTeamId = this.asyncHandler( async (req, res) => { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); @@ -372,7 +373,7 @@ class MonitorController { const type = req?.query?.type; const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain }); @@ -386,7 +387,7 @@ class MonitorController { "getMonitorsAndSummaryByTeamId" ); - getMonitorsWithChecksByTeamId = asyncHandler( + getMonitorsWithChecksByTeamId = this.asyncHandler( async (req, res) => { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); @@ -395,7 +396,7 @@ class MonitorController { let { limit, type, page, rowsPerPage, filter, field, order } = req.query; const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({ @@ -419,11 +420,11 @@ class MonitorController { "getMonitorsWithChecksByTeamId" ); - exportMonitorsToCSV = asyncHandler( + exportMonitorsToCSV = this.asyncHandler( async (req, res) => { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("Team ID is required"); + throw this.errorService.createBadRequestError("Team ID is required"); } const csv = await this.monitorService.exportMonitorsToCSV({ teamId }); diff --git a/server/controllers/notificationController.js b/server/controllers/notificationController.js index 41af21ece..32ae22aae 100755 --- a/server/controllers/notificationController.js +++ b/server/controllers/notificationController.js @@ -1,27 +1,27 @@ import { createNotificationBodyValidation } from "../validation/joi.js"; -import { asyncHandler } from "../utils/errorUtils.js"; +import BaseController from "./baseController.js"; const SERVICE_NAME = "NotificationController"; -class NotificationController { - constructor({ notificationService, stringService, statusService, db }) { +class NotificationController extends BaseController { + constructor({ notificationService, stringService, statusService, db, errorService }) { + super(); this.notificationService = notificationService; this.stringService = stringService; this.statusService = statusService; this.db = db; + this.errorService = errorService; + this.asyncHandler = errorService.asyncHandler; } - testNotification = asyncHandler( - async (req, res, next) => { + testNotification = this.asyncHandler( + async (req, res) => { const notification = req.body; const success = await this.notificationService.sendTestNotification(notification); if (!success) { - return res.error({ - msg: "Sending notification failed", - status: 400, - }); + throw this.errorService.createServerError("Sending notification failed"); } return res.success({ @@ -32,8 +32,8 @@ class NotificationController { "testNotification" ); - createNotification = asyncHandler( - async (req, res, next) => { + createNotification = this.asyncHandler( + async (req, res) => { await createNotificationBodyValidation.validateAsync(req.body, { abortEarly: false, }); @@ -42,12 +42,12 @@ class NotificationController { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("Team ID is required"); } const userId = req?.user?._id; if (!userId) { - throw new Error("No user ID in request"); + throw this.errorService.createBadRequestError("User ID is required"); } body.userId = userId; body.teamId = teamId; @@ -62,11 +62,11 @@ class NotificationController { "createNotification" ); - getNotificationsByTeamId = asyncHandler( - async (req, res, next) => { + getNotificationsByTeamId = this.asyncHandler( + async (req, res) => { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("Team ID is required"); } const notifications = await this.db.getNotificationsByTeamId(teamId); @@ -80,18 +80,16 @@ class NotificationController { "getNotificationsByTeamId" ); - deleteNotification = asyncHandler( - async (req, res, next) => { + deleteNotification = this.asyncHandler( + async (req, res) => { const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("Team ID is required"); } const notification = await this.db.getNotificationById(req.params.id); if (!notification.teamId.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; + throw this.errorService.createAuthorizationError(); } await this.db.deleteNotificationById(req.params.id); @@ -103,19 +101,17 @@ class NotificationController { "deleteNotification" ); - getNotificationById = asyncHandler( - async (req, res, next) => { + getNotificationById = this.asyncHandler( + async (req, res) => { const notification = await this.db.getNotificationById(req.params.id); const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("Team ID is required"); } if (!notification.teamId.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; + throw this.errorService.createAuthorizationError(); } return res.success({ msg: "Notification fetched successfully", @@ -126,23 +122,21 @@ class NotificationController { "getNotificationById" ); - editNotification = asyncHandler( - async (req, res, next) => { + editNotification = this.asyncHandler( + async (req, res) => { await createNotificationBodyValidation.validateAsync(req.body, { abortEarly: false, }); const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("Team ID is required"); } const notification = await this.db.getNotificationById(req.params.id); if (!notification.teamId.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; + throw this.errorService.createAuthorizationError(); } const editedNotification = await this.db.editNotification(req.params.id, req.body); @@ -155,26 +149,24 @@ class NotificationController { "editNotification" ); - testAllNotifications = asyncHandler( - async (req, res, next) => { + testAllNotifications = this.asyncHandler( + async (req, res) => { const monitorId = req.body.monitorId; const teamId = req?.user?.teamId; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("Team ID is required"); } const monitor = await this.db.getMonitorById(monitorId); if (!monitor.teamId.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; + throw this.errorService.createAuthorizationError(); } const notifications = monitor.notifications; - if (notifications.length === 0) throw new Error("No notifications"); + if (notifications.length === 0) throw this.errorService.createBadRequestError("No notifications"); const result = await this.notificationService.testAllNotifications(notifications); - if (!result) throw new Error("Failed to send all notifications"); + if (!result) throw this.errorService.createServerError("Failed to send all notifications"); return res.success({ msg: "All notifications sent successfully", }); diff --git a/server/controllers/queueController.js b/server/controllers/queueController.js index faa0febd7..72864da38 100755 --- a/server/controllers/queueController.js +++ b/server/controllers/queueController.js @@ -1,15 +1,16 @@ -import { asyncHandler } from "../utils/errorUtils.js"; - +import BaseController from "./baseController.js"; const SERVICE_NAME = "JobQueueController"; -class JobQueueController { - constructor(jobQueue, stringService) { +class JobQueueController extends BaseController { + constructor({ jobQueue, stringService, errorService }) { + super(); this.jobQueue = jobQueue; this.stringService = stringService; + this.errorService = errorService; } - getMetrics = asyncHandler( - async (req, res, next) => { + getMetrics = this.asyncHandler( + async (req, res) => { const metrics = await this.jobQueue.getMetrics(); res.success({ msg: this.stringService.queueGetMetrics, @@ -20,8 +21,8 @@ class JobQueueController { "getMetrics" ); - getJobs = asyncHandler( - async (req, res, next) => { + getJobs = this.asyncHandler( + async (req, res) => { const jobs = await this.jobQueue.getJobs(); return res.success({ msg: this.stringService.queueGetJobs, @@ -32,8 +33,8 @@ class JobQueueController { "getJobs" ); - getAllMetrics = asyncHandler( - async (req, res, next) => { + getAllMetrics = this.asyncHandler( + async (req, res) => { const jobs = await this.jobQueue.getJobs(); const metrics = await this.jobQueue.getMetrics(); return res.success({ @@ -45,8 +46,8 @@ class JobQueueController { "getAllMetrics" ); - addJob = asyncHandler( - async (req, res, next) => { + addJob = this.asyncHandler( + async (req, res) => { await this.jobQueue.addJob(Math.random().toString(36).substring(7)); return res.success({ msg: this.stringService.queueAddJob, @@ -56,8 +57,8 @@ class JobQueueController { "addJob" ); - flushQueue = asyncHandler( - async (req, res, next) => { + flushQueue = this.asyncHandler( + async (req, res) => { const result = await this.jobQueue.flushQueues(); return res.success({ msg: this.stringService.jobQueueFlush, @@ -68,8 +69,8 @@ class JobQueueController { "flushQueue" ); - checkQueueHealth = asyncHandler( - async (req, res, next) => { + checkQueueHealth = this.asyncHandler( + async (req, res) => { const stuckQueues = await this.jobQueue.checkQueueHealth(); return res.success({ msg: this.stringService.queueHealthCheck, diff --git a/server/controllers/settingsController.js b/server/controllers/settingsController.js index 798fbdd5e..c54ba9e7e 100755 --- a/server/controllers/settingsController.js +++ b/server/controllers/settingsController.js @@ -1,15 +1,17 @@ import { updateAppSettingsBodyValidation } from "../validation/joi.js"; import { sendTestEmailBodyValidation } from "../validation/joi.js"; -import { asyncHandler, createServerError } from "../utils/errorUtils.js"; +import BaseController from "./baseController.js"; const SERVICE_NAME = "SettingsController"; -class SettingsController { - constructor({ db, settingsService, stringService, emailService }) { +class SettingsController extends BaseController { + constructor({ db, settingsService, stringService, emailService, errorService }) { + super(); this.db = db; this.settingsService = settingsService; this.stringService = stringService; this.emailService = emailService; + this.errorService = errorService; } buildAppSettings = (dbSettings) => { @@ -33,8 +35,8 @@ class SettingsController { return returnSettings; }; - getAppSettings = asyncHandler( - async (req, res, next) => { + getAppSettings = this.asyncHandler( + async (req, res) => { const dbSettings = await this.settingsService.getDBSettings(); const returnSettings = this.buildAppSettings(dbSettings); @@ -47,8 +49,8 @@ class SettingsController { "getAppSettings" ); - updateAppSettings = asyncHandler( - async (req, res, next) => { + updateAppSettings = this.asyncHandler( + async (req, res) => { await updateAppSettingsBodyValidation.validateAsync(req.body); const updatedSettings = await this.db.updateAppSettings(req.body); @@ -62,14 +64,9 @@ class SettingsController { "updateAppSettings" ); - sendTestEmail = asyncHandler( - async (req, res, next) => { - try { - await sendTestEmailBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + sendTestEmail = this.asyncHandler( + async (req, res) => { + await sendTestEmailBodyValidation.validateAsync(req.body); const { to, @@ -107,7 +104,7 @@ class SettingsController { }); if (!messageId) { - throw createServerError("Failed to send test email."); + throw this.errorService.createServerError("Failed to send test email."); } return res.success({ diff --git a/server/controllers/statusPageController.js b/server/controllers/statusPageController.js index 8333e8949..79ebb50ab 100755 --- a/server/controllers/statusPageController.js +++ b/server/controllers/statusPageController.js @@ -1,16 +1,18 @@ import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js"; -import { asyncHandler } from "../utils/errorUtils.js"; +import BaseController from "./baseController.js"; const SERVICE_NAME = "statusPageController"; -class StatusPageController { - constructor(db, stringService) { +class StatusPageController extends BaseController { + constructor({ db, stringService, errorService }) { + super(); this.db = db; this.stringService = stringService; + this.errorService = errorService; } - createStatusPage = asyncHandler( - async (req, res, next) => { + createStatusPage = this.asyncHandler( + async (req, res) => { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); @@ -30,16 +32,14 @@ class StatusPageController { "createStatusPage" ); - updateStatusPage = asyncHandler( - async (req, res, next) => { + updateStatusPage = this.asyncHandler( + async (req, res) => { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); const statusPage = await this.db.updateStatusPage(req.body, req.file); if (statusPage === null) { - const error = new Error(this.stringService.statusPageNotFound); - error.status = 404; - throw error; + throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound); } return res.success({ msg: this.stringService.statusPageUpdate, @@ -50,8 +50,8 @@ class StatusPageController { "updateStatusPage" ); - getStatusPage = asyncHandler( - async (req, res, next) => { + getStatusPage = this.asyncHandler( + async (req, res) => { const statusPage = await this.db.getStatusPage(); return res.success({ msg: this.stringService.statusPageByUrl, @@ -62,8 +62,8 @@ class StatusPageController { "getStatusPage" ); - getStatusPageByUrl = asyncHandler( - async (req, res, next) => { + getStatusPageByUrl = this.asyncHandler( + async (req, res) => { await getStatusPageParamValidation.validateAsync(req.params); await getStatusPageQueryValidation.validateAsync(req.query); @@ -77,8 +77,8 @@ class StatusPageController { "getStatusPageByUrl" ); - getStatusPagesByTeamId = asyncHandler( - async (req, res, next) => { + getStatusPagesByTeamId = this.asyncHandler( + async (req, res) => { const teamId = req.user.teamId; const statusPages = await this.db.getStatusPagesByTeamId(teamId); @@ -91,8 +91,8 @@ class StatusPageController { "getStatusPagesByTeamId" ); - deleteStatusPage = asyncHandler( - async (req, res, next) => { + deleteStatusPage = this.asyncHandler( + async (req, res) => { await this.db.deleteStatusPage(req.params.url); return res.success({ msg: this.stringService.statusPageDelete, diff --git a/server/index.js b/server/index.js index 541f9ae14..7953d160b 100755 --- a/server/index.js +++ b/server/index.js @@ -3,7 +3,6 @@ import fs from "fs"; import swaggerUi from "swagger-ui-express"; import jwt from "jsonwebtoken"; import papaparse from "papaparse"; - import express from "express"; import helmet from "helmet"; import cors from "cors"; @@ -58,6 +57,8 @@ import PulseQueueHelper from "./service/infrastructure/PulseQueue/PulseQueueHelp import SuperSimpleQueue from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; import SuperSimpleQueueHelper from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import ErrorService from "./service/infrastructure/errorService.js"; + // Business services import UserService from "./service/business/userService.js"; import CheckService from "./service/business/checkService.js"; @@ -186,6 +187,7 @@ const startApp = async () => { }); const redisService = new RedisService({ Redis: IORedis, logger }); + const errorService = new ErrorService(); // const jobQueueHelper = new JobQueueHelper({ // redisService, @@ -240,6 +242,7 @@ const startApp = async () => { logger, stringService, jwt, + errorService, }); const checkService = new CheckService({ db, @@ -252,6 +255,7 @@ const startApp = async () => { settingsService, emailService, stringService, + errorService, }); const maintenanceWindowService = new MaintenanceWindowService({ db, @@ -266,11 +270,13 @@ const startApp = async () => { emailService, papaparse, logger, + errorService, }); // Register services // ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue); // ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); ServiceRegistry.register(JobQueue.SERVICE_NAME, superSimpleQueue); + ServiceRegistry.register(ErrorService.SERVICE_NAME, errorService); ServiceRegistry.register(MongoDB.SERVICE_NAME, db); ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService); ServiceRegistry.register(EmailService.SERVICE_NAME, emailService); @@ -285,6 +291,7 @@ const startApp = async () => { ServiceRegistry.register(InviteService.SERVICE_NAME, inviteService); ServiceRegistry.register(MaintenanceWindowService.SERVICE_NAME, maintenanceWindowService); ServiceRegistry.register(MonitorService.SERVICE_NAME, monitorService); + ServiceRegistry.register(DiagnosticService.SERVICE_NAME, diagnosticService); await translationService.initialize(); @@ -306,6 +313,7 @@ const startApp = async () => { stringService: ServiceRegistry.get(StringService.SERVICE_NAME), logger: logger, userService: ServiceRegistry.get(UserService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); const monitorController = new MonitorController({ @@ -315,6 +323,7 @@ const startApp = async () => { stringService: ServiceRegistry.get(StringService.SERVICE_NAME), emailService: ServiceRegistry.get(EmailService.SERVICE_NAME), monitorService: ServiceRegistry.get(MonitorService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); const settingsController = new SettingsController({ @@ -322,6 +331,7 @@ const startApp = async () => { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), stringService: ServiceRegistry.get(StringService.SERVICE_NAME), emailService: ServiceRegistry.get(EmailService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); const checkController = new CheckController({ @@ -329,11 +339,13 @@ const startApp = async () => { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), stringService: ServiceRegistry.get(StringService.SERVICE_NAME), checkService: ServiceRegistry.get(CheckService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); const inviteController = new InviteController({ stringService: ServiceRegistry.get(StringService.SERVICE_NAME), - inviteService, + inviteService: ServiceRegistry.get(InviteService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); const maintenanceWindowController = new MaintenanceWindowController({ @@ -341,23 +353,34 @@ const startApp = async () => { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), stringService: ServiceRegistry.get(StringService.SERVICE_NAME), maintenanceWindowService: ServiceRegistry.get(MaintenanceWindowService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const queueController = new QueueController(ServiceRegistry.get(JobQueue.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME)); + const queueController = new QueueController({ + jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME), + stringService: ServiceRegistry.get(StringService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), + }); - const logController = new LogController(logger); + const logController = new LogController({ logger, errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME) }); - const statusPageController = new StatusPageController(ServiceRegistry.get(MongoDB.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME)); + const statusPageController = new StatusPageController({ + db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + stringService: ServiceRegistry.get(StringService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), + }); const notificationController = new NotificationController({ notificationService: ServiceRegistry.get(NotificationService.SERVICE_NAME), stringService: ServiceRegistry.get(StringService.SERVICE_NAME), statusService: ServiceRegistry.get(StatusService.SERVICE_NAME), db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); const diagnosticController = new DiagnosticController({ - diagnosticService, + diagnosticService: ServiceRegistry.get(DiagnosticService.SERVICE_NAME), + errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); //Create routes diff --git a/server/middleware/handleErrors.js b/server/middleware/handleErrors.js index 1efce6f67..782d37ad9 100755 --- a/server/middleware/handleErrors.js +++ b/server/middleware/handleErrors.js @@ -3,7 +3,6 @@ import ServiceRegistry from "../service/system/serviceRegistry.js"; import StringService from "../service/system/stringService.js"; const handleErrors = (error, req, res, next) => { - console.log("ERROR", error); const status = error.status || 500; const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); const message = error.message || stringService.authIncorrectPassword; diff --git a/server/service/business/inviteService.js b/server/service/business/inviteService.js index b70c151d4..d23d725e4 100644 --- a/server/service/business/inviteService.js +++ b/server/service/business/inviteService.js @@ -1,14 +1,14 @@ const SERVICE_NAME = "inviteService"; -import { createServerError } from "../../utils/errorUtils.js"; class InviteService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, settingsService, emailService, stringService }) { + constructor({ db, settingsService, emailService, stringService, errorService }) { this.db = db; this.settingsService = settingsService; this.emailService = emailService; this.stringService = stringService; + this.errorService = errorService; } getInviteToken = async ({ invite, teamId }) => { @@ -27,7 +27,7 @@ class InviteService { }); const result = await this.emailService.sendEmail(inviteRequest.email, "Welcome to Uptime Monitor", html); if (!result) { - throw createServerError("Failed to send invite e-mail... Please verify your settings."); + throw this.errorService.createServerError("Failed to send invite e-mail... Please verify your settings."); } }; diff --git a/server/service/business/monitorService.js b/server/service/business/monitorService.js index d0e8f92ca..babc3122f 100644 --- a/server/service/business/monitorService.js +++ b/server/service/business/monitorService.js @@ -1,12 +1,10 @@ import { createMonitorsBodyValidation } from "../../validation/joi.js"; -import { createServerError } from "../../utils/errorUtils.js"; -import logger from "../../utils/logger.js"; const SERVICE_NAME = "MonitorService"; class MonitorService { SERVICE_NAME = SERVICE_NAME; - constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse }) { + constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService }) { this.db = db; this.settingsService = settingsService; this.jobQueue = jobQueue; @@ -14,14 +12,14 @@ class MonitorService { this.emailService = emailService; this.papaparse = papaparse; this.logger = logger; + this.errorService = errorService; + this.asyncHandler = errorService.asyncHandler; } verifyTeamAccess = async ({ teamId, monitorId }) => { const monitor = await this.db.getMonitorById(monitorId); if (!monitor?.teamId?.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; + throw this.errorService.createAuthorizationError(); } }; @@ -103,11 +101,11 @@ class MonitorService { complete: async ({ data, errors }) => { try { if (errors.length > 0) { - throw createServerError("Error parsing CSV"); + throw this.errorService.createServerError("Error parsing CSV"); } if (!data || data.length === 0) { - throw createServerError("CSV file contains no data rows"); + throw this.errorService.createServerError("CSV file contains no data rows"); } const enrichedData = data.map((monitor) => ({ @@ -156,7 +154,7 @@ class MonitorService { await this.db.deletePageSpeedChecksByMonitorId(monitor._id); await this.db.deleteNotificationsByMonitorId(monitor._id); } catch (error) { - logger.warn({ + this.logger.warn({ message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, service: SERVICE_NAME, method: "deleteAllMonitors", @@ -195,7 +193,7 @@ class MonitorService { const messageId = await this.emailService.sendEmail(to, subject, html); if (!messageId) { - throw createServerError("Failed to send test email."); + throw this.errorService.createServerError("Failed to send test email."); } return messageId; diff --git a/server/service/business/userService.js b/server/service/business/userService.js index 90906552d..bd1ea3fa6 100644 --- a/server/service/business/userService.js +++ b/server/service/business/userService.js @@ -1,15 +1,15 @@ const SERVICE_NAME = "userService"; -import { createAuthError, createError } from "../../utils/errorUtils.js"; class UserService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, emailService, settingsService, logger, stringService, jwt }) { + constructor({ db, emailService, settingsService, logger, stringService, jwt, errorService }) { this.db = db; this.emailService = emailService; this.settingsService = settingsService; this.logger = logger; this.stringService = stringService; this.jwt = jwt; + this.errorService = errorService; } issueToken = (payload, appSettings) => { @@ -80,7 +80,7 @@ class UserService { // Compare password const match = await user.comparePassword(password); if (match !== true) { - throw createAuthError(this.stringService.authIncorrectPassword); + throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword); } // Remove password from user object. Should this be abstracted to DB layer? @@ -109,7 +109,7 @@ class UserService { // If not a match, throw a 403 // 403 instead of 401 to avoid triggering axios interceptor if (!match) { - throw createError(this.stringService.authIncorrectPassword, 403); + throw this.errorService.createAuthorizationError(this.stringService.authIncorrectPassword); } // If a match, update the password updates.password = updates.newPassword; diff --git a/server/service/infrastructure/errorService.js b/server/service/infrastructure/errorService.js new file mode 100644 index 000000000..098c5b4ac --- /dev/null +++ b/server/service/infrastructure/errorService.js @@ -0,0 +1,161 @@ +class AppError extends Error { + constructor(message, status = 500, service = null, method = null, details = null) { + super(message); + this.status = status; + this.service = service; + this.method = method; + this.details = details; + + Error.captureStackTrace(this, this.constructor); + } +} + +class ValidationError extends AppError { + constructor(message, details = null, service = null, method = null) { + super(message, 422, service, method, details); + } +} + +class AuthenticationError extends AppError { + constructor(message, details = null, service = null, method = null) { + super(message, 401, service, method, details); + } +} + +class AuthorizationError extends AppError { + constructor(message, details = null, service = null, method = null) { + super(message, 403, service, method, details); + } +} + +class NotFoundError extends AppError { + constructor(message, details = null, service = null, method = null) { + super(message, 404, service, method, details); + } +} + +class ConflictError extends AppError { + constructor(message, details = null, service = null, method = null) { + super(message, 409, service, method, details); + } +} + +class DatabaseError extends AppError { + constructor(message, details = null, service = null, method = null) { + super(message, 500, service, method, details); + } +} + +class BadRequestError extends AppError { + constructor(message, details = null, service = null, method = null) { + super(message, 400, service, method, details); + } +} + +const SERVICE_NAME = "ErrorService"; +class ErrorService { + static SERVICE_NAME = SERVICE_NAME; + + constructor() {} + + createError = (message, status = 500, service = null, method = null, details = null) => { + return new AppError(message, status, service, method, details); + }; + + createValidationError = (message, details = null, service = null, method = null) => { + return new ValidationError(message, details, service, method); + }; + + createAuthenticationError = (message = "Unauthorized", details = null, service = null, method = null) => { + return new AuthenticationError(message, details, service, method); + }; + + createAuthorizationError = (message, details = null, service = null, method = null) => { + return new AuthorizationError(message, details, service, method); + }; + + createNotFoundError = (message, details = null, service = null, method = null) => { + return new NotFoundError(message, details, service, method); + }; + + createConflictError = (message, details = null, service = null, method = null) => { + return new ConflictError(message, details, service, method); + }; + + createDatabaseError = (message, details = null, service = null, method = null) => { + return new DatabaseError(message, details, service, method); + }; + + createServerError = (message, details = null, service = null, method = null) => { + return this.createError(message, 500, service, method, details); + }; + + createBadRequestError = (message = "BadRequest", details = null, service = null, method = null) => { + return new BadRequestError(message, details, service, method); + }; +} + +export default ErrorService; + +export const asyncHandler = (fn, serviceName, methodName) => { + return async (req, res, next) => { + try { + await fn(req, res, next); + } catch (error) { + // Handle validation errors + if (error.isJoi) { + const validationError = this.createValidationError(error.message, error.details, serviceName, methodName); + return next(validationError); + } + + if (error.name === "ValidationError") { + const validationError = this.createValidationError("Database validation failed", error.errors, serviceName, methodName); + return next(validationError); + } + + if (error.name === "CastError") { + const notFoundError = this.createNotFoundError( + "Invalid resource identifier", + { field: error.path, value: error.value }, + serviceName, + methodName + ); + return next(notFoundError); + } + + if (error.code === "11000") { + const conflictError = this.createConflictError("Resource already exists", { + originalError: error.message, + code: error.code, + }); + conflictError.service = serviceName; + conflictError.method = methodName; + return next(conflictError); + } + + if (error instanceof AppError) { + error.service = error.service || serviceName; + error.method = error.method || methodName; + return next(error); + } + + if (error.status) { + const appError = this.createError(error.message, error.status, serviceName, methodName, { + originalError: error.message, + stack: error.stack, + }); + return next(appError); + } + + // For unknown errors, create a server error + const appError = this.createServerError(error.message || "An unexpected error occurred", { + originalError: error.message, + stack: error.stack, + }); + appError.service = serviceName; + appError.method = methodName; + appError.stack = error.stack; // Preserve original stack + return next(appError); + } + }; +}; diff --git a/server/utils/errorUtils.js b/server/utils/errorUtils.js index 7d9a2959d..8b1378917 100644 --- a/server/utils/errorUtils.js +++ b/server/utils/errorUtils.js @@ -1,81 +1 @@ -class AppError extends Error { - constructor(message, status = 500, service = null, method = null, details = null) { - super(message); - this.status = status; - this.service = service; - this.method = method; - this.details = details; - Error.captureStackTrace(this, this.constructor); - } -} - -export const createError = (message, status = 500, service = null, method = null, details = null) => { - return new AppError(message, status, service, method, details); -}; - -export const createValidationError = (message, details = null, service = null, method = null) => { - return createError(message, 422, service, method, details); -}; - -export const createAuthError = (message, details = null, service = null, method = null) => { - return createError(message, 401, service, method, details); -}; - -export const createForbiddenError = (message, details = null, service = null, method = null) => { - return createError(message, 403, service, method, details); -}; - -export const createNotFoundError = (message, details = null, service = null, method = null) => { - return createError(message, 404, service, method, details); -}; - -export const createConflictError = (message, details = null, service = null, method = null) => { - return createError(message, 409, service, method, details); -}; - -export const createServerError = (message, details = null, service = null, method = null) => { - return createError(message, 500, service, method, details); -}; - -export const asyncHandler = (fn, serviceName, methodName) => { - return async (req, res, next) => { - try { - await fn(req, res, next); - } catch (error) { - // Handle validation errors - if (error.isJoi || error.name === "ValidationError") { - const validationError = createValidationError(error.message, error.details, serviceName, methodName); - return next(validationError); - } - - if (error instanceof AppError) { - error.service = error.service || serviceName; - error.method = error.method || methodName; - return next(error); - } - - if (error.code === "23505") { - const appError = createConflictError("Resource already exists", { - originalError: error.message, - code: error.code, - }); - appError.service = serviceName; - appError.method = methodName; - return next(appError); - } - - if (error.status) { - const appError = createError(error.message, error.status, serviceName, methodName, { originalError: error.message, stack: error.stack }); - return next(appError); - } - - // For unknown errors, create a server error - const appError = createServerError(error.message || "An unexpected error occurred", { originalError: error.message, stack: error.stack }); - appError.service = serviceName; - appError.method = methodName; - appError.stack = error.stack; // Preserve original stack - return next(appError); - } - }; -}; From dc321580c3cf110e65e3b28251eafd17732e00dc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 25 Jul 2025 10:16:29 -0700 Subject: [PATCH 103/106] refactor out common dependenceis --- server/controllers/announcementsController.js | 8 +- server/controllers/authController.js | 12 +-- server/controllers/baseController.js | 81 ++++++++++++++++++- server/controllers/checkController.js | 11 +-- server/controllers/diagnosticController.js | 8 +- server/controllers/inviteController.js | 12 +-- server/controllers/logController.js | 6 +- .../maintenanceWindowController.js | 8 +- server/controllers/monitorController.js | 8 +- server/controllers/notificationController.js | 8 +- server/controllers/queueController.js | 6 +- server/controllers/settingsController.js | 7 +- server/controllers/statusPageController.js | 7 +- server/index.js | 80 ++++++++---------- server/service/business/checkService.js | 55 +++++-------- .../business/maintenanceWindowService.js | 9 +-- server/service/business/monitorService.js | 1 - server/service/business/userService.js | 8 +- server/service/infrastructure/errorService.js | 65 +-------------- server/utils/logger.js | 3 + 20 files changed, 170 insertions(+), 233 deletions(-) diff --git a/server/controllers/announcementsController.js b/server/controllers/announcementsController.js index b17b8eb91..aeb7a3b9d 100755 --- a/server/controllers/announcementsController.js +++ b/server/controllers/announcementsController.js @@ -1,5 +1,4 @@ import { createAnnouncementValidation } from "../validation/joi.js"; -import { asyncHandler } from "../utils/errorUtils.js"; const SERVICE_NAME = "announcementController"; @@ -10,10 +9,9 @@ const SERVICE_NAME = "announcementController"; * @class AnnouncementController */ -class AnnouncementController { - constructor(db, stringService) { - this.db = db; - this.stringService = stringService; +class AnnouncementController extends BaseController { + constructor(commonDependencies) { + super(commonDependencies); this.createAnnouncement = this.createAnnouncement.bind(this); this.getAnnouncement = this.getAnnouncement.bind(this); } diff --git a/server/controllers/authController.js b/server/controllers/authController.js index 8dac9f028..00fc2caf5 100755 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -27,25 +27,19 @@ class AuthController extends BaseController { /** * Creates an instance of AuthController. * + * @param {Object} commonDependencies - Common dependencies injected into the controller * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.db - Database service for data operations * @param {Object} dependencies.settingsService - Service for application settings * @param {Object} dependencies.emailService - Service for email operations * @param {Object} dependencies.jobQueue - Service for job queue operations - * @param {Object} dependencies.stringService - Service for string/localization - * @param {Object} dependencies.logger - Logger service * @param {Object} dependencies.userService - User business logic service */ - constructor({ db, settingsService, emailService, jobQueue, stringService, logger, userService, errorService }) { - super(); - this.db = db; + constructor(commonDependencies, { settingsService, emailService, jobQueue, userService }) { + super(commonDependencies); this.settingsService = settingsService; this.emailService = emailService; this.jobQueue = jobQueue; - this.stringService = stringService; - this.logger = logger; this.userService = userService; - this.errorService = errorService; } /** diff --git a/server/controllers/baseController.js b/server/controllers/baseController.js index 81df59deb..a84806a24 100644 --- a/server/controllers/baseController.js +++ b/server/controllers/baseController.js @@ -1,8 +1,83 @@ -import { asyncHandler } from "../service/infrastructure/errorService.js"; +import { AppError } from "../service/infrastructure/errorService.js"; + +export const createCommonDependencies = (serviceRegistry, dbServiceName, loggerServiceName, errorServiceName, stringServiceName) => { + return { + db: serviceRegistry.get(dbServiceName), + errorService: serviceRegistry.get(errorServiceName), + logger: serviceRegistry.get(loggerServiceName), + stringService: serviceRegistry.get(stringServiceName), + }; +}; class BaseController { - constructor() { - this.asyncHandler = asyncHandler; + constructor({ db, logger, errorService, ...additionalDependencies }) { + this.db = db; + this.logger = logger; + this.errorService = errorService; + Object.assign(this, additionalDependencies); + + this.asyncHandler = (fn, serviceName, methodName) => { + return async (req, res, next) => { + try { + await fn(req, res, next); + } catch (error) { + // Handle validation errors + if (error.isJoi) { + const validationError = this.errorService.createValidationError(error.message, error.details, serviceName, methodName); + return next(validationError); + } + + if (error.name === "ValidationError") { + const validationError = this.errorService.createValidationError("Database validation failed", error.errors, serviceName, methodName); + return next(validationError); + } + + if (error.name === "CastError") { + const notFoundError = this.errorService.createNotFoundError( + "Invalid resource identifier", + { field: error.path, value: error.value }, + serviceName, + methodName + ); + return next(notFoundError); + } + + if (error.code === "11000") { + const conflictError = this.errorService.createConflictError("Resource already exists", { + originalError: error.message, + code: error.code, + }); + conflictError.service = serviceName; + conflictError.method = methodName; + return next(conflictError); + } + + if (error instanceof AppError) { + error.service = error.service || serviceName; + error.method = error.method || methodName; + return next(error); + } + + if (error.status) { + const appError = this.errorService.createError(error.message, error.status, serviceName, methodName, { + originalError: error.message, + stack: error.stack, + }); + return next(appError); + } + + // For unknown errors, create a server error + const appError = this.errorService.createServerError(error.message || "An unexpected error occurred", { + originalError: error.message, + stack: error.stack, + }); + appError.service = serviceName; + appError.method = methodName; + appError.stack = error.stack; // Preserve original stack + return next(appError); + } + }; + }; } } export default BaseController; diff --git a/server/controllers/checkController.js b/server/controllers/checkController.js index 693f5a32d..08b6c2de9 100755 --- a/server/controllers/checkController.js +++ b/server/controllers/checkController.js @@ -26,20 +26,15 @@ class CheckController extends BaseController { /** * Creates an instance of CheckController. * + * @param {Object} commonDependencies - Common dependencies injected into the controller * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.db - Database service for data operations * @param {Object} dependencies.settingsService - Service for application settings - * @param {Object} dependencies.stringService - Service for string/localization * @param {Object} dependencies.checkService - Check business logic service - * @param {Object} dependencies.errorService - Error service for error handling */ - constructor({ db, settingsService, stringService, checkService, errorService }) { - super(); - this.db = db; + constructor(commonDependencies, { settingsService, checkService }) { + super(commonDependencies); this.settingsService = settingsService; - this.stringService = stringService; this.checkService = checkService; - this.errorService = errorService; } /** diff --git a/server/controllers/diagnosticController.js b/server/controllers/diagnosticController.js index 308755109..91b90f96b 100755 --- a/server/controllers/diagnosticController.js +++ b/server/controllers/diagnosticController.js @@ -12,15 +12,13 @@ import BaseController from "./baseController.js"; class DiagnosticController extends BaseController { /** * Creates an instance of DiagnosticController. - * + * @param {Object} commonDependencies - Common dependencies injected into the controller * @param {Object} dependencies - The dependencies required by the controller * @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring - * @param {Object} dependencies.errorService - Service for error handling */ - constructor({ diagnosticService, errorService }) { - super(); + constructor(commonDependencies, { diagnosticService }) { + super(commonDependencies); this.diagnosticService = diagnosticService; - this.errorService = errorService; } /** diff --git a/server/controllers/inviteController.js b/server/controllers/inviteController.js index 4cf7ec8ed..3f237c6ec 100755 --- a/server/controllers/inviteController.js +++ b/server/controllers/inviteController.js @@ -9,16 +9,12 @@ const SERVICE_NAME = "inviteController"; class InviteController extends BaseController { /** * Creates a new InviteController instance - * @param {Object} dependencies - Dependencies injected into the controller - * @param {Object} dependencies.stringService - Service for internationalized strings - * @param {Object} dependencies.inviteServ ice - Service for invite-related operations - * @param {Object} dependencies.errorService - Service for error handling + * @param {Object} commonDependencies - Common dependencies injected into the controller + * @param {Object} dependencies.inviteService - Service for invite-related operations */ - constructor({ stringService, inviteService, errorService }) { - super(); - this.stringService = stringService; + constructor(commonDependencies, { inviteService }) { + super(commonDependencies); this.inviteService = inviteService; - this.errorService = errorService; } /** diff --git a/server/controllers/logController.js b/server/controllers/logController.js index 14f7a6d58..aa2780eb6 100644 --- a/server/controllers/logController.js +++ b/server/controllers/logController.js @@ -2,10 +2,8 @@ import BaseController from "./baseController.js"; const SERVICE_NAME = "LogController"; class LogController extends BaseController { - constructor({ logger, errorService }) { - super(); - this.logger = logger; - this.errorService = errorService; + constructor(commonDependencies) { + super(commonDependencies); } getLogs = this.asyncHandler( diff --git a/server/controllers/maintenanceWindowController.js b/server/controllers/maintenanceWindowController.js index 59ea10140..e54726ea3 100755 --- a/server/controllers/maintenanceWindowController.js +++ b/server/controllers/maintenanceWindowController.js @@ -12,14 +12,10 @@ import BaseController from "./baseController.js"; const SERVICE_NAME = "maintenanceWindowController"; class MaintenanceWindowController extends BaseController { - constructor({ db, settingsService, stringService, maintenanceWindowService, errorService }) { - super(); - this.db = db; + constructor(commonDependencies, { settingsService, maintenanceWindowService }) { + super(commonDependencies); this.settingsService = settingsService; - this.stringService = stringService; this.maintenanceWindowService = maintenanceWindowService; - this.errorService = errorService; - this.asyncHandler = errorService.asyncHandler; } createMaintenanceWindows = this.asyncHandler( diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 4e2c021d2..2187df5d0 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -18,16 +18,12 @@ import BaseController from "./baseController.js"; const SERVICE_NAME = "monitorController"; class MonitorController extends BaseController { - constructor({ db, settingsService, jobQueue, stringService, emailService, monitorService, errorService }) { - super(); - this.db = db; + constructor(commonDependencies, { settingsService, jobQueue, emailService, monitorService }) { + super(commonDependencies); this.settingsService = settingsService; this.jobQueue = jobQueue; - this.stringService = stringService; this.emailService = emailService; this.monitorService = monitorService; - this.errorService = errorService; - this.asyncHandler = errorService.asyncHandler; } async verifyTeamAccess(teamId, monitorId) { diff --git a/server/controllers/notificationController.js b/server/controllers/notificationController.js index 32ae22aae..6e85b97b8 100755 --- a/server/controllers/notificationController.js +++ b/server/controllers/notificationController.js @@ -4,14 +4,10 @@ import BaseController from "./baseController.js"; const SERVICE_NAME = "NotificationController"; class NotificationController extends BaseController { - constructor({ notificationService, stringService, statusService, db, errorService }) { - super(); + constructor(commonDependencies, { notificationService, statusService }) { + super(commonDependencies); this.notificationService = notificationService; - this.stringService = stringService; this.statusService = statusService; - this.db = db; - this.errorService = errorService; - this.asyncHandler = errorService.asyncHandler; } testNotification = this.asyncHandler( diff --git a/server/controllers/queueController.js b/server/controllers/queueController.js index 72864da38..757350652 100755 --- a/server/controllers/queueController.js +++ b/server/controllers/queueController.js @@ -2,11 +2,9 @@ import BaseController from "./baseController.js"; const SERVICE_NAME = "JobQueueController"; class JobQueueController extends BaseController { - constructor({ jobQueue, stringService, errorService }) { - super(); + constructor(commonDependencies, { jobQueue }) { + super(commonDependencies); this.jobQueue = jobQueue; - this.stringService = stringService; - this.errorService = errorService; } getMetrics = this.asyncHandler( diff --git a/server/controllers/settingsController.js b/server/controllers/settingsController.js index c54ba9e7e..d97c19e88 100755 --- a/server/controllers/settingsController.js +++ b/server/controllers/settingsController.js @@ -5,13 +5,10 @@ import BaseController from "./baseController.js"; const SERVICE_NAME = "SettingsController"; class SettingsController extends BaseController { - constructor({ db, settingsService, stringService, emailService, errorService }) { - super(); - this.db = db; + constructor(commonDependencies, { settingsService, emailService }) { + super(commonDependencies); this.settingsService = settingsService; - this.stringService = stringService; this.emailService = emailService; - this.errorService = errorService; } buildAppSettings = (dbSettings) => { diff --git a/server/controllers/statusPageController.js b/server/controllers/statusPageController.js index 79ebb50ab..09d1d8b5f 100755 --- a/server/controllers/statusPageController.js +++ b/server/controllers/statusPageController.js @@ -4,11 +4,8 @@ import BaseController from "./baseController.js"; const SERVICE_NAME = "statusPageController"; class StatusPageController extends BaseController { - constructor({ db, stringService, errorService }) { - super(); - this.db = db; - this.stringService = stringService; - this.errorService = errorService; + constructor(commonDependencies) { + super(commonDependencies); } createStatusPage = this.asyncHandler( diff --git a/server/index.js b/server/index.js index 7953d160b..b6f1dd3fe 100755 --- a/server/index.js +++ b/server/index.js @@ -7,11 +7,13 @@ import express from "express"; import helmet from "helmet"; import cors from "cors"; import compression from "compression"; +import { Logger } from "./utils/logger.js"; import logger from "./utils/logger.js"; import { verifyJWT } from "./middleware/verifyJWT.js"; import { handleErrors } from "./middleware/handleErrors.js"; import { responseHandler } from "./middleware/responseHandler.js"; import { fileURLToPath } from "url"; +import { createCommonDependencies } from "./controllers/baseController.js"; import AuthRoutes from "./routes/authRoute.js"; import AuthController from "./controllers/authController.js"; @@ -248,6 +250,7 @@ const startApp = async () => { db, settingsService, stringService, + errorService, }); const diagnosticService = new DiagnosticService(); const inviteService = new InviteService({ @@ -261,6 +264,7 @@ const startApp = async () => { db, settingsService, stringService, + errorService, }); const monitorService = new MonitorService({ db, @@ -275,6 +279,7 @@ const startApp = async () => { // Register services // ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue); // ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); + ServiceRegistry.register(Logger.SERVICE_NAME, logger); ServiceRegistry.register(JobQueue.SERVICE_NAME, superSimpleQueue); ServiceRegistry.register(ErrorService.SERVICE_NAME, errorService); ServiceRegistry.register(MongoDB.SERVICE_NAME, db); @@ -305,82 +310,63 @@ const startApp = async () => { process.on("SIGTERM", shutdown); //Create controllers - const authController = new AuthController({ - db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + + const commonDependencies = createCommonDependencies( + ServiceRegistry, + MongoDB.SERVICE_NAME, + Logger.SERVICE_NAME, + ErrorService.SERVICE_NAME, + StringService.SERVICE_NAME + ); + + const authController = new AuthController(commonDependencies, { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), emailService: ServiceRegistry.get(EmailService.SERVICE_NAME), jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), - logger: logger, userService: ServiceRegistry.get(UserService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const monitorController = new MonitorController({ - db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + const monitorController = new MonitorController(commonDependencies, { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), emailService: ServiceRegistry.get(EmailService.SERVICE_NAME), monitorService: ServiceRegistry.get(MonitorService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const settingsController = new SettingsController({ - db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + const settingsController = new SettingsController(commonDependencies, { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), emailService: ServiceRegistry.get(EmailService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const checkController = new CheckController({ - db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + const checkController = new CheckController(commonDependencies, { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), checkService: ServiceRegistry.get(CheckService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const inviteController = new InviteController({ - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), + const inviteController = new InviteController(commonDependencies, { inviteService: ServiceRegistry.get(InviteService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const maintenanceWindowController = new MaintenanceWindowController({ - db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + const maintenanceWindowController = new MaintenanceWindowController(commonDependencies, { settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), maintenanceWindowService: ServiceRegistry.get(MaintenanceWindowService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const queueController = new QueueController({ + const queueController = new QueueController(commonDependencies, { jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const logController = new LogController({ logger, errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME) }); + const logController = new LogController(commonDependencies); - const statusPageController = new StatusPageController({ - db: ServiceRegistry.get(MongoDB.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), - }); + const statusPageController = new StatusPageController(commonDependencies); - const notificationController = new NotificationController({ + const notificationController = new NotificationController(commonDependencies, { notificationService: ServiceRegistry.get(NotificationService.SERVICE_NAME), - stringService: ServiceRegistry.get(StringService.SERVICE_NAME), statusService: ServiceRegistry.get(StatusService.SERVICE_NAME), - db: ServiceRegistry.get(MongoDB.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); - const diagnosticController = new DiagnosticController({ + const diagnosticController = new DiagnosticController(commonDependencies, { diagnosticService: ServiceRegistry.get(DiagnosticService.SERVICE_NAME), - errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME), }); //Create routes @@ -393,9 +379,9 @@ const startApp = async () => { const queueRoutes = new QueueRoutes(queueController); const logRoutes = new LogRoutes(logController); const statusPageRoutes = new StatusPageRoutes(statusPageController); - const notificationRoutes = new NotificationRoutes(notificationController); const diagnosticRoutes = new DiagnosticRoutes(diagnosticController); + // Middleware app.use(express.static(frontendPath)); app.use(responseHandler); @@ -438,16 +424,16 @@ const startApp = async () => { //routes app.use("/api/v1/auth", authRoutes.getRouter()); - app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter()); - app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); - app.use("/api/v1/invite", inviteRoutes.getRouter()); - app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter()); - app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter()); app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter()); - app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); - app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter()); + app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter()); + app.use("/api/v1/invite", inviteRoutes.getRouter()); + app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter()); + app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); + app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter()); app.use("/api/v1/status-page", statusPageRoutes.getRouter()); + app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); + app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); app.use("/api/v1/health", (req, res) => { res.json({ diff --git a/server/service/business/checkService.js b/server/service/business/checkService.js index e52a4919a..3d8206644 100644 --- a/server/service/business/checkService.js +++ b/server/service/business/checkService.js @@ -3,37 +3,30 @@ const SERVICE_NAME = "checkService"; class CheckService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, settingsService, stringService }) { + constructor({ db, settingsService, stringService, errorService }) { this.db = db; this.settingsService = settingsService; this.stringService = stringService; + this.errorService = errorService; } getChecksByMonitor = async ({ monitorId, query, teamId }) => { if (!monitorId) { - throw new Error("No monitor ID in request"); + throw this.errorService.createBadRequestError("No monitor ID in request"); } if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("No team ID in request"); } const monitor = await this.db.getMonitorById(monitorId); if (!monitor) { - const err = new Error("Monitor not found"); - err.status = 404; - err.service = SERVICE_NAME; - err.method = "getChecksByMonitor"; - throw err; + throw this.errorService.createNotFoundError("Monitor not found"); } if (!monitor.teamId.equals(teamId)) { - const err = new Error("Unauthorized"); - err.status = 403; - err.service = SERVICE_NAME; - err.method = "getChecksByMonitor"; - throw err; + throw this.errorService.createAuthorizationError(); } let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query; @@ -55,7 +48,7 @@ class CheckService { let { sortOrder, dateRange, filter, ack, page, rowsPerPage } = query; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("No team ID in request"); } const checkData = await this.db.getChecksByTeam({ @@ -72,7 +65,7 @@ class CheckService { getChecksSummaryByTeamId = async ({ teamId }) => { if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("No team ID in request"); } const summary = await this.db.getChecksSummaryByTeamId({ teamId }); @@ -81,11 +74,11 @@ class CheckService { ackCheck = async ({ checkId, teamId, ack }) => { if (!checkId) { - throw new Error("No check ID in request"); + throw this.errorService.createBadRequestError("No check ID in request"); } if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("No team ID in request"); } const updatedCheck = await this.db.ackCheck(checkId, teamId, ack); @@ -95,20 +88,16 @@ class CheckService { ackAllChecks = async ({ monitorId, path, teamId, ack }) => { if (path === "monitor") { if (!monitorId) { - throw new Error("No monitor ID in request"); + throw this.errorService.createBadRequestError("No monitor ID in request"); } const monitor = await this.db.getMonitorById(monitorId); if (!monitor) { - throw new Error("Monitor not found"); + throw this.errorService.createNotFoundError("Monitor not found"); } if (!monitor.teamId.equals(teamId)) { - const err = new Error("Unauthorized"); - err.status = 403; - err.service = SERVICE_NAME; - err.method = "ackAllChecks"; - throw err; + throw this.errorService.createAuthorizationError(); } } @@ -118,29 +107,21 @@ class CheckService { deleteChecks = async ({ monitorId, teamId }) => { if (!monitorId) { - throw new Error("No monitor ID in request"); + throw this.errorService.createBadRequestError("No monitor ID in request"); } if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("No team ID in request"); } const monitor = await this.db.getMonitorById(monitorId); if (!monitor) { - const err = new Error("Monitor not found"); - err.status = 404; - err.service = SERVICE_NAME; - err.method = "deleteChecks"; - throw err; + throw this.errorService.createNotFoundError("Monitor not found"); } if (!monitor.teamId.equals(teamId)) { - const err = new Error("Unauthorized"); - err.status = 403; - err.service = SERVICE_NAME; - err.method = "deleteChecks"; - throw err; + throw this.errorService.createAuthorizationError(); } const deletedCount = await this.db.deleteChecks(monitorId); @@ -148,7 +129,7 @@ class CheckService { }; deleteChecksByTeamId = async ({ teamId }) => { if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("No team ID in request"); } const deletedCount = await this.db.deleteChecksByTeamId(teamId); diff --git a/server/service/business/maintenanceWindowService.js b/server/service/business/maintenanceWindowService.js index 6140a5682..0f58aca85 100644 --- a/server/service/business/maintenanceWindowService.js +++ b/server/service/business/maintenanceWindowService.js @@ -2,10 +2,11 @@ const SERVICE_NAME = "maintenanceWindowService"; class MaintenanceWindowService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, settingsService, stringService }) { + constructor({ db, settingsService, stringService, errorService }) { this.db = db; this.settingsService = settingsService; this.stringService = stringService; + this.errorService = errorService; } createMaintenanceWindow = async ({ teamId, body }) => { @@ -15,11 +16,7 @@ class MaintenanceWindowService { const unauthorizedMonitors = monitors.filter((monitor) => !monitor.teamId.equals(teamId)); if (unauthorizedMonitors.length > 0) { - const error = new Error("Unauthorized access to one or more monitors"); - error.status = 403; - error.service = SERVICE_NAME; - error.method = "createMaintenanceWindows"; - throw error; + throw this.errorService.createAuthorizationError(); } const dbTransactions = monitorIds.map((monitorId) => { diff --git a/server/service/business/monitorService.js b/server/service/business/monitorService.js index babc3122f..a2bf06dfd 100644 --- a/server/service/business/monitorService.js +++ b/server/service/business/monitorService.js @@ -13,7 +13,6 @@ class MonitorService { this.papaparse = papaparse; this.logger = logger; this.errorService = errorService; - this.asyncHandler = errorService.asyncHandler; } verifyTeamAccess = async ({ teamId, monitorId }) => { diff --git a/server/service/business/userService.js b/server/service/business/userService.js index bd1ea3fa6..12dd4207f 100644 --- a/server/service/business/userService.js +++ b/server/service/business/userService.js @@ -154,23 +154,23 @@ class UserService { deleteUser = async (user) => { const email = user?.email; if (!email) { - throw new Error("No email in request"); + throw this.errorService.createBadRequestError("No email in request"); } const teamId = user?.teamId; const userId = user?._id; if (!teamId) { - throw new Error("No team ID in request"); + throw this.errorService.createBadRequestError("No team ID in request"); } if (!userId) { - throw new Error("No user ID in request"); + throw this.errorService.createBadRequestError("No user ID in request"); } const roles = user?.role; if (roles.includes("demo")) { - throw new Error("Demo user cannot be deleted"); + throw this.errorService.createBadRequestError("Demo user cannot be deleted"); } // 1. Find all the monitors associated with the team ID if superadmin diff --git a/server/service/infrastructure/errorService.js b/server/service/infrastructure/errorService.js index 098c5b4ac..552816b73 100644 --- a/server/service/infrastructure/errorService.js +++ b/server/service/infrastructure/errorService.js @@ -1,4 +1,4 @@ -class AppError extends Error { +export class AppError extends Error { constructor(message, status = 500, service = null, method = null, details = null) { super(message); this.status = status; @@ -96,66 +96,3 @@ class ErrorService { } export default ErrorService; - -export const asyncHandler = (fn, serviceName, methodName) => { - return async (req, res, next) => { - try { - await fn(req, res, next); - } catch (error) { - // Handle validation errors - if (error.isJoi) { - const validationError = this.createValidationError(error.message, error.details, serviceName, methodName); - return next(validationError); - } - - if (error.name === "ValidationError") { - const validationError = this.createValidationError("Database validation failed", error.errors, serviceName, methodName); - return next(validationError); - } - - if (error.name === "CastError") { - const notFoundError = this.createNotFoundError( - "Invalid resource identifier", - { field: error.path, value: error.value }, - serviceName, - methodName - ); - return next(notFoundError); - } - - if (error.code === "11000") { - const conflictError = this.createConflictError("Resource already exists", { - originalError: error.message, - code: error.code, - }); - conflictError.service = serviceName; - conflictError.method = methodName; - return next(conflictError); - } - - if (error instanceof AppError) { - error.service = error.service || serviceName; - error.method = error.method || methodName; - return next(error); - } - - if (error.status) { - const appError = this.createError(error.message, error.status, serviceName, methodName, { - originalError: error.message, - stack: error.stack, - }); - return next(appError); - } - - // For unknown errors, create a server error - const appError = this.createServerError(error.message || "An unexpected error occurred", { - originalError: error.message, - stack: error.stack, - }); - appError.service = serviceName; - appError.method = methodName; - appError.stack = error.stack; // Preserve original stack - return next(appError); - } - }; -}; diff --git a/server/utils/logger.js b/server/utils/logger.js index 6fc240e4c..8cb57f9a7 100755 --- a/server/utils/logger.js +++ b/server/utils/logger.js @@ -2,7 +2,10 @@ import { createLogger, format, transports } from "winston"; import dotenv from "dotenv"; dotenv.config(); +const SERVICE_NAME = "Logger"; + class Logger { + static SERVICE_NAME = SERVICE_NAME; constructor() { this.logCache = []; this.maxCacheSize = 1000; From 3059e730e2495039635e742b82cfe74cf600e9b2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 25 Jul 2025 10:19:22 -0700 Subject: [PATCH 104/106] type errors --- server/service/business/monitorService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/service/business/monitorService.js b/server/service/business/monitorService.js index a2bf06dfd..2a2b27aa3 100644 --- a/server/service/business/monitorService.js +++ b/server/service/business/monitorService.js @@ -90,7 +90,7 @@ class MonitorService { if (["port", "interval"].includes(header)) { const num = parseInt(value, 10); if (isNaN(num)) { - throw new Error(`${header} should be a valid number, got: ${value}`); + throw this.errorService.createBadRequestError(`${header} should be a valid number, got: ${value}`); } return num; } @@ -237,10 +237,10 @@ class MonitorService { }; exportMonitorsToCSV = async ({ teamId }) => { - const monitors = await this.monitorService.getMonitorsByTeamId({ teamId }); + const monitors = await this.db.getMonitorsByTeamId({ teamId }); if (!monitors || monitors.length === 0) { - throw new Error("No monitors to export"); + throw this.errorService.createNotFoundError("No monitors to export"); } const csvData = monitors?.filteredMonitors?.map((monitor) => ({ From bb2ab8e08a35514518bb05434e55c58fc788cdd9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 25 Jul 2025 10:29:04 -0700 Subject: [PATCH 105/106] format --- server/utils/errorUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/utils/errorUtils.js b/server/utils/errorUtils.js index 8b1378917..e69de29bb 100644 --- a/server/utils/errorUtils.js +++ b/server/utils/errorUtils.js @@ -1 +0,0 @@ - From cafd9f9899394ae409449faca9e01bda7bcae4fc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 25 Jul 2025 10:29:40 -0700 Subject: [PATCH 106/106] remove empty file --- server/utils/errorUtils.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 server/utils/errorUtils.js diff --git a/server/utils/errorUtils.js b/server/utils/errorUtils.js deleted file mode 100644 index e69de29bb..000000000